【问题标题】:In EF Code First, can Foo have a collection of Bars and also a "FavouriteBar"?在 EF Code First 中,Foo 可以拥有 Bars 的集合以及“FavouriteBar”吗?
【发布时间】:2012-10-09 17:49:04
【问题描述】:

为了说明我的问题,假设我有一个数据模型,其中有一组书籍,每本书都有一个或多个草稿,还有一个“当前”草稿。我希望我的模型看起来像这样:

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

    public string Title { get; set; }

    public virtual ICollection<Draft> Drafts { get; set; }

    public virtual Draft CurrentDraft { get; set; }
}

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

    public virtual int BookId { get; set; }
    public virtual Book Book { get; set; }

    public string Description { get; set; }
}

我有一个如下所示的 DbContext:

class TestDbContext : DbContext
{
    public DbSet<Book> Books { get; set; }
    public DbSet<Draft> Drafts { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

        base.OnModelCreating(modelBuilder);
    }
}

...还有一个像这样的简单程序:

static void Main(string[] args)
{
    Database.SetInitializer<TestDbContext>(new DropCreateDatabaseAlways<TestDbContext>());

    using (var db = new TestDbContext())
    {
        var book = new Book() { Title = "War and Peace", Drafts = new List<Draft>() };


        var draft1 = new Draft() { Book = book, Description = "First Draft" };

        book.Drafts.Add(draft1);


        var draft2 = new Draft() { Book = book, Description = "Second Draft" };

        book.Drafts.Add(draft2);


        book.CurrentDraft = draft2;


        db.Books.Add(book);

        db.SaveChanges();


        foreach (var b in db.Books)
        {
            Console.WriteLine("Book {0}: {1}", b.Id, b.Title);

            foreach (var d in book.Drafts)
                Console.WriteLine("\tDraft ID {0}: {1} from Book ID {2}", d.Id, d.Description, d.BookId);

            Console.WriteLine("Current draft has ID {0}", b.CurrentDraft.Id);
        }

        Console.ReadKey();
    }
}

当 EF 创建 DB 时,它看起来像这样:

[我对额外的 Book_Id 列并不感兴趣,但如果它有效,我可以接受它。]

可悲的是,当我运行测试程序时,它在SaveChanges 调用时失败了,但有一个例外:

保存不公开外键的实体时出错 他们关系的属性。 EntityEntries 属性将 返回 null,因为无法将单个实体标识为源 的例外。可以进行保存时的异常处理 通过在实体类型中公开外键属性更容易。看 InnerException 了解详情。

我尝试在 OnModelCreating 中使用流畅的 API,但是当我尝试以这种方式配置它时,它会因为 Books 和 Drafts 之间存在两种 FK 关系而感到困惑。我对流利的东西(或一般的 EF)不够熟悉,不知道如何正确地做到这一点。

这甚至可以在 EF 代码中实现吗?

【问题讨论】:

  • 内部异常说明了什么?是否抛出了 SQL 异常?
  • 我通常以 db-first 工作,所以可能不符合标准,但 Book 不想要 CurrentDraftId 属性吗?

标签: c# entity-framework ef-code-first entity-framework-5


【解决方案1】:

您的内部例外是 EF 无法确定将内容保存到数据库以正确创建内容的顺序。如果你在多个地方调用 save changes 来强制你的测试应用运行的顺序:

            var book = new Book() { Title = "War and Peace", Drafts = new List<Draft>() };
            db.Books.Add(book);
            db.SaveChanges();
            var draft1 = new Draft() { Book = book, Description = "First Draft" };
            book.Drafts.Add(draft1);
            var draft2 = new Draft() { Book = book, Description = "Second Draft" };
            book.Drafts.Add(draft2);
            book.CurrentDraft = draft2;
            db.SaveChanges();

关于第二个 book_id - 你可以使用这个流利的:

        modelBuilder.Entity<Book>()
            .HasMany(b => b.Drafts)
            .WithRequired(d => d.Book)
            .HasForeignKey(d => d.BookId);

        modelBuilder.Entity<Book>()
            .HasOptional(b => b.CurrentDraft)
            .WithOptionalDependent()
            .Map(m => m.MapKey("CurrentDraftId"));

产生这个数据库:

【讨论】:

  • 它将非规范化数据库。
  • 我已经更新了 fluent,所以 BookId 不需要为空 - 但是为了强制创建顺序(书然后草稿) CurrentDraft 需要是可选的。
  • @MarkOreta:非常感谢。这很有效,因为我现在拥有了我期望的数据库结构。可惜的是额外的 SaveChanges。我将如何使两个 SaveChanges 调用原子化?将它们包装在 TransactionScope 中?
  • 仅供参考,第一个流畅的位(配置主 Book:Drafts 关系)似乎没有必要;如果我删除它,它没有任何区别。看来,EF 自己计算了这么多。
  • 是的,要使它们原子化,您可以将它们包装在事务范围中 - 请记住,这可能会调用 DTC。
【解决方案2】:

如果我是你,我将在 Draft 添加一个属性,即 bool IsCurrent{get;set;}。还有

public Draft CurrentDraft { get{return Drafts.SingleOrDefault(c=>c.IsCurrent)} }

在这种情况下,书籍和草稿之间将有一个外键。

【讨论】:

  • 我希望在 Book 对象上有一个 CurrentDraft 属性的原因是会有很多很多的草稿,而且当前的草稿会被非常频繁地访问。 (几乎每次访问 Book 对象时)。换句话说,这是一种优化——我不认为像你建议的那样增加一列在这方面会那么好。
  • 我意识到这是一年后的事,但我遇到了同样的问题,我正在使用 IsCurrent 方法来解决错误。您正在用一列换另一列。 Drafts 会多一个,Books 会少一个(因为它是一个计算值,应该在 EF 中设置为忽略)。
猜你喜欢
  • 2018-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
  • 1970-01-01
  • 1970-01-01
  • 2012-06-30
  • 1970-01-01
相关资源
最近更新 更多