如何在EF6中映射冗余关系

时间:2017-09-13 17:48:51

标签: entity-framework-6

我有一个引用对象,其中包含 QuoteAnswer 对象的集合。我还想要一个最新的QuoteAnswer的快捷方式。所以我建模了(为了简洁省略了不相关的代码):

public class Quote
{
    public int Id { get; set; }
    public ICollection<QuoteAnswer> Answers { get; set; }

    public QuoteAnswer LatestAnswer { get; set; }
    public int LatestAnswerId { get; set; }
}

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

    public Quote Quote { get; set; }
    public int QuoteId { get; set; }

    /* Did not map this, not interested/not needed
     * public Quote LastAnswerFor { get; set; }
     * public int LastAnswerForId { get; set; }
    */
}

我希望能够做到这一点:

var quotes = context.Quotes
    .Include(x => x.LatestAnswer)
    .ToList();

而不是:

var quotes = context.Quotes
    .Include(x => x.Answers)
    .ToList();

foreach (var q in quotes)
{
    var latestAnswer = q.Answers.OrderByDescending(x => x.Date).FirstOrDefault();
}

这显然会迫使我加载不必要的数据。

问题

当我尝试映射这个数据库代码(添加迁移)时,我得到一个新属性,我不知道它来自哪里。

生成的迁移代码(为简洁起见省略了部分):

CreateTable(
    "dbo.QuoteAnswer",
    c => new
        {
            Id = c.Int(nullable: false),
            QuoteId = c.Int(nullable: false),
            QuoteId1 = c.Int(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.Quote", t => t.QuoteId)
    .ForeignKey("dbo.Quote", t => t.QuoteId1)
    .Index(t => t.QuoteId)
    .Index(t => t.QuoteId1);

AddColumn("dbo.Quote", "LatestAnswerId", c => c.Int());
CreateIndex("dbo.Quote", "LatestAnswerId");
AddForeignKey("dbo.Quote", "LatestAnswerId", "dbo.QuoteAnswer", "Id");

QuoteId1 是什么东西?我得到 QuoteId ,但我不认识 QuoteId1

我如何实现此映射?这甚至在EF6中得到支持吗?

1 个答案:

答案 0 :(得分:1)

首先,它是可能的。应删除显式FK属性:

public class Quote
{
    public int Id { get; set; }
    public string Data { get; set; }
    public ICollection<QuoteAnswer> Answers { get; set; }

    public QuoteAnswer LatestAnswer { get; set; }
}

并且应该使用流畅的API映射新关系:

modelBuilder.Entity<Quote>()
    .HasOptional(e => e.LatestAnswer)
    .WithOptionalDependent()
    .Map(m => m.MapKey("LatestAnswerId"));

但我不建议你这样做,因为它会引入很多维护问题 - 除了明显需要保持最新,它会创建循环FK依赖,所以基本上所有的CUD操作都是有问题(如果工作的话)。

我认为你试图解决“加载不必要的数据”问题是一种错误的方法。您可以使用简单的投影来实现相同的目标:

var quotesWithLatestAnswer = context.Quotes
    .Select(q => new { Quote = q, LatestAnswer = q.Answers.OrderByDescending(a => a.Date).FirstOrDefault() })
    .ToList();

请注意,Select中的代码将被转换为SQL并在数据库中执行,只返回所需的数据。

要将最新答案作为实体的一部分返回,您可以将其标记为未映射:

public class Quote
{
    public int Id { get; set; }
    public string Data { get; set; }
    public ICollection<QuoteAnswer> Answers { get; set; }
    [NotMapped]    
    public QuoteAnswer LatestAnswer { get; set; }
}

并使用LiNQ与实体(SQL)和LINQ to Objects查询的组合:

var quotes = context.Quotes
    .Select(q => new { Quote = q, LatestAnswer = q.Answers.OrderByDescending(a => a.Date).FirstOrDefault() })
    .AsEnumerable() // end of db query
    .Select(qi => { qi.Quote.LatestAnswer = qi.LatestAnswer; return qi.Quote; })
    .ToList();

通过这种方式,您可以轻松,易于维护关系数据库模型,并有效地检索所需数据。