Jak powiązać 1 obiekt z 2 polami w bazie danych

0

Model bazy danych utworzonych przez EntityFramework wygląda tak, że każdy użytkownik ma listę książek, oraz jedną ulubioną:

public class User
{
    public int Id { get; set; }
    public string Name {get; set;}
  
    public ICollection<Book> Books { get; set; }
    public Book FavouriteBook { get; set; }
}

public class Book
{
    public int Id { get; set; }
    public string Title {get; set;}
}

Dodanie użytkownika z listą książek bez ulubionej wykona się poprawnie:

var book1 = new Book()
{
    Title = "book1"
};

var book2 = new Book()
{
    Title = "book2"
};

var user = new User()
{
    Name = "user",
};

user.Books = new List<Book>() { book1, book2 };

context.Users.Add(user);
context.SaveChanges();

Natomiast gdy spróbuję dodać użytkownika, który posiada listę książek i jedną ulubioną:

user.Books = new List<Book>() { book1, book2 };
user.FavouriteBook = book2;

context.Users.Add(user);
context.SaveChanges();

To rzucany jest wtedy wyjątek:

System.Data.Entity.Infrastructure.DbUpdateException HResult=0x80131501 Message=An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.

Inner Exception 1: UpdateException: Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.

Zamiarem było to, by dodały się 2 książki (book1, oraz book2), a ten sam rekord w bazie dotyczący book2 będzie przypisany do użytkownika, jako ulubiona książka.

Co tutaj jest nie tak?

0

Wydaje mi się musisz skonfigurować EF i powiedzieć co jest jakim FK.

Coś z tego tematu https://www.learnentityframeworkcore.com/configuration/fluent-api/hasforeignkey-method

0

@WeiXiao:
Zrobiłem coś takiego:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    public ICollection<Book> Books { get; set; }


    [ForeignKey("FavouriteBook")]
    public int FavouriteBookId { get; set; }
    public Book FavouriteBook { get; set; }
}

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }

    [ForeignKey("User")]
    public int UserId;
    public User User { get; set; }
}

ale nie pomogło :/

0

A jest inny błąd, czy nadal ten sam?

0

Jak dodałem te {get; set;} do UserId to leci teraz inny błąd:

Introducing FOREIGN KEY constraint 'FK_dbo.Users_dbo.Books_FavouriteBookId' on table 'Users' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

Could not create constraint or index. See previous errors.

0

Generalnie czy Ty na pewno chcesz tam mieć relacje 1:N, a nie N:N? np. User1 ma książkę A,B oraz User 2 ma A,B

anyway, udało mi się tak, ale potestuj sobie :)

public class Context : DbContext
{
    public Context(DbContextOptions options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<User>().HasMany(x => x.Books).WithOne(x => x.User).HasForeignKey(x => x.UserId);
        modelBuilder.Entity<User>().HasOne<Book>(x => x.FavouriteBook);
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Book> Books { get; set; }
}
static void Main(string[] args)
{
    var builder = new DbContextOptionsBuilder();
    builder.EnableSensitiveDataLogging(true);
    builder.UseSqlite("filename=db.db");

    using (var ctx = new Context(builder.Options))
    {
        ctx.Database.EnsureDeleted();
        ctx.Database.EnsureCreated();

        var book1 = new Book()
        {
            Title = "book1"
        };

        var book2 = new Book()
        {
            Title = "book2"
        };

        var user = new User()
        {
            Name = "user",
        };

        user.Books = new List<Book>() { book1, book2 };
        user.FavouriteBook = book2;

        ctx.Users.Add(user);
        ctx.SaveChanges();
    }

    using (var ctx = new Context(builder.Options))
    {
        var users = ctx.Users.Include(x => x.Books).ToList();

        foreach (var user in users)
        {
            Console.WriteLine(user.Name);
            Console.WriteLine($"favourite: {user.FavouriteBook.Title}");

            foreach (var book in user.Books)
            {
                Console.WriteLine($"\t{book.Title}");
            }
        }
    }
}
public class User
{
    public int Id { get; set; }

    public string Name { get; set; }

    public List<Book> Books { get; set; } = new List<Book>();

    public Book FavouriteBook { get; set; }
}

public class Book
{
    public int Id { get; set; }

    public string Title { get; set; }

    public int UserId { get; set; }

    public User User { get; set; }
}
user
favourite: book2
        book1
        book2
0

Generalnie czy Ty na pewno chcesz tam mieć relacje 1:N, a nie N:N? np. User1 ma książkę A,B oraz User 2 ma A,B

1:N. Te książki, które user ma, nie może mieć nikt inny. Wynikiem będzie coś takiego:

Users
+----+------+-----------------+
| Id | Name | FavouriteBookId |
+----+------+-----------------+
| 1  | user | 2               |
+----+------+-----------------+

Books
+----+-------+--------+
| Id | Title | UserId |
+----+-------+--------+
| 1  | book1 | 1      |
+----+-------+--------+
| 2  | book2 | 1      |
+----+-------+--------+

W Entity Framework 6.4.4 niestety to nie działa :/ Chyba jedyna różnica to to, że WithOne zmieniłem na WithRequired, a HasOne, na HasRequired. Znowu leci to:

Message=Introducing FOREIGN KEY constraint 'FK_dbo.Users_dbo.Books_FavouriteBook_Id' on table 'Users' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

class Program
{
    static void Main(string[] args)
    {
        var context = new ConfigurationContext();

        var book1 = new Book()
        {
            Title = "book1"
        };
		
        var book2 = new Book()
        {
            Title = "book2"
        };

        var user = new User()
        {
            Name = "foo",
        };

        user.Books = new List<Book>() { book1, book2 };
        user.FavouriteBook = book2;
        
        context.Users.Add(user);
        context.SaveChanges();
    }
}

public class ConfigurationContext : DbContext
{
    public ConfigurationContext() : base(@"Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=testdb;Integrated Security=SSPI;")
    {

    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<User>()
            .HasMany(x => x.Books)
            .WithRequired(x => x.User)
            .HasForeignKey(x => x.UserId);

        modelBuilder.Entity<User>()
            .HasRequired<Book>(x => x.FavouriteBook);
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Book> Books { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Book> Books { get; set; }
    public Book FavouriteBook { get; set; }
}

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int UserId { get; set; }
    public User User { get; set; }
}
0

nigdy więcej ef 6 xd

static void Main(string[] args)
{
    var context = new ConfigurationContext();

    context.Database.CreateIfNotExists();

    var book1 = new Book()
    {
        Title = "book1"
    };

    var book2 = new Book()
    {
        Title = "book2"
    };

    var user = new User()
    {
        Name = "foo",
    };

    context.Books.Add(book1);
    context.Books.Add(book2);
    context.SaveChanges();

    user.Books.Add(book1);
    user.Books.Add(book2);
    user.FavouriteBook = book2;

    context.Users.Add(user);

    context.SaveChanges();
}
public class User
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Name { get; set; }

    public List<Book> Books { get; set; } = new List<Book>();

    public int? FavouriteBookId { get; set; }

    public Book FavouriteBook { get; set; }
}

public class Book
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Title { get; set; }

    public int? UserId { get; set; }

    public virtual User User { get; set; }
}
public class ConfigurationContext : DbContext
{
    public ConfigurationContext() : base(@"Server=localhost\SQLEXPRESS;Database=test;Trusted_Connection=True;")
    {

    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<User>()
            .HasMany(x => x.Books)
            .WithOptional(x => x.User)
            .HasForeignKey(x => x.UserId);

        modelBuilder.Entity<User>()
            .HasOptional(x => x.FavouriteBook)
            .WithMany()
            .HasForeignKey(x => x.FavouriteBookId);
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Book> Books { get; set; }
}

screenshot-20210819004709.png
screenshot-20210819004856.png

0

Właśnie mi chodziło o to, by działało to przy użyciu jednego SaveChanges. Jak w Twoim kodzie zmienię z:

context.Books.Add(book1);
context.Books.Add(book2);
context.SaveChanges();

user.Books.Add(book1);
user.Books.Add(book2);
user.FavouriteBook = book2;

context.Users.Add(user);

context.SaveChanges();

na:

context.Books.Add(book1);
context.Books.Add(book2);
// context.SaveChanges(); <-- zmiana

user.Books.Add(book1);
user.Books.Add(book2);
user.FavouriteBook = book2;

context.Users.Add(user);

context.SaveChanges();

to otrzymuję błąd, który otrzymywałem na samym początku:

System.Data.Entity.Infrastructure.DbUpdateException
HResult=0x80131501
Message=Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.

This exception was originally thrown at this call stack:
[External Code]

Inner Exception 1:
UpdateException: Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.

Już mnie to zirytowało, że chyba zostawię to tak jak było pierwotnie z taką sztuczką:

using (var transaction = context.Database.BeginTransaction())
{
    user.Books = new List<Book>() { book1, book2 };
    context.Users.Add(user);
    context.SaveChanges();

    user.FavouriteBook = book2;
    context.SaveChanges();
    transaction.Commit();
}
0

Nie wiem, ten EF6 jakiś upośledzony, na EFC nie kojarzę takich problemów

Szkoda że nie możesz podnieść :P

0

@bethccc: Placzesz jak dziecko. Jak ID nie maja wartosci to jak ma sie domyslić?
EF wysyła inserty A żaden z nich nie ma wartości kluczy przed zapisem do db.

0

@WeiXiao: DO komentarza: a co jeżeli jako id mam guidy wygenerowane po client sidzie?

Wtedy powinno zadziałać. Nawet jak przypiszesz INT-owe ID ręcznie i wartości kluczy obcych będą się zgadzać po wygenerowaniu ID przez bazę to powinno działać.

Order z listą OrderItem

var o = new Order { Id = 1, Nr = "1/111" };

                db.Orders.Add(o);

                var oi = new OrderItem { Id = 1, Item = "item", OrderId = 1 }; // tu ustawiam ręcznie OrderId dla OrderItem na 1 bo wiem, że takie ID baza wygeneruje (pusta tabela)
                db.OrderItems.Add(oi);

                o.Items.Add(oi);
                db.SaveChanges();

1 użytkowników online, w tym zalogowanych: 0, gości: 1