【问题标题】:Strange behaviour using EF + LINQ: Include(...).Where(...)使用 EF + LINQ 的奇怪行为: Include(...).Where(...)
【发布时间】:2014-09-17 19:48:42
【问题描述】:

我有点做噩梦,想弄清楚为什么以下Include(...) 后跟Where(...) 的组合没有产生预期的结果:

  • 我通过Where(...)将数据库中的项目分成两部分。
  • Where(...) 条件是相互排斥和互补的。
  • Where(...) 调用依赖于相关对象,因此我使用Include(...)
  • 我知道这两个部分都包含元素。

代码:

using (var db = new FeedDbContext("My-Database-Connection"))
{
    var queryWithout = db.FeedEntries
                         .Include(f => f.MetadataFile)
                         .Where(f => f.MetadataFile == null);
    var queryWith    = db.FeedEntries
                         .Include(f => f.MetadataFile)
                         .Where(f => f.MetadataFile != null);

    //-- using ToList().Count
    var totalCount   = db.FeedEntries.ToList().Count;
    var countWithout = queryWithout.ToList().Count;
    var countWith    = queryWith.ToList().Count;

    Console.WriteLine("totalCount using ToList().Count: {0}", totalCount);
    Console.WriteLine("countWithout using ToList().Count: {0}", countWithout);
    Console.WriteLine("countWith using ToList().Count: {0}", countWith);

    //-- using Count()
    totalCount   = db.FeedEntries.Count();
    countWithout = queryWithout.Count();
    countWith    = queryWith.Count();

    Console.WriteLine("totalCount using Count(): {0}", totalCount);
    Console.WriteLine("countWithout using Count(): {0}", countWithout);
    Console.WriteLine("countWith using Count(): {0}", countWith);

    //-- using CountAsync()
    totalCount   = await db.FeedEntries.CountAsync();
    countWithout = await queryWithout.CountAsync();
    countWith    = await queryWith.CountAsync();

    Console.WriteLine("totalCount using CountAsync(): {0}", totalCount);
    Console.WriteLine("countWithout using CountAsync(): {0}", countWithout);
    Console.WriteLine("countWith using CountAsync(): {0}", countWith);
}

打印的输出是:

totalCount using ToList().Count: 8372
countWithout using ToList().Count: 8372
countWith using ToList().Count: 0

totalCount using Count(): 8372
countWithout using Count(): 8372
countWith using Count(): 7908

totalCount using CountAsync(): 8372
countWithout using CountAsync(): 8372
countWith using CountAsync(): 7908

totalCount除外:

  1. 在这两种情况下,使用ToList().Count 的部分查询的计数都是错误的。

  2. IQueryable<T> 扩展方法(CountAsync() 来自 EntityFrameworkCount() 来自 System.Core)在两种情况下产生相同的结果。

    • 带有 MetadataFile 的项目数正确

    • 没有MetadataFile 的项目计数不正确

  3. 没有MetadataFile 的项目数应等于 8372 - 7908 = 464,已在数据库中验证。

这表明我调用Include() 扩展方法的方式有问题。

  • 这是因为我枚举多次相同的查询并且我保持相同的连接吗?

  • 幕后是否有一些我不知道的奇怪性质并解释了这种看似奇怪的行为???

请多指教!!!


EF 型号详情

这就是我定义实体和映射的方式:

public partial class FeedEntry
{
    public int Id { get; set; }
    ...
    public virtual MetadataFile MetadataFile { get; set; }
    public virtual ICollection<ImageFile> ImageFiles { get; set; }
    public virtual ICollection<VideoFile> VideoFiles { get; set; }
    ...
}

public partial class MetadataFile : FileBase
{
    ...
    public virtual FeedEntry FeedEntry { get; set; }
    ...
}

和映射:

modelBuilder.Entity<FeedEntry>()
            .HasOptional(t => t.MetadataFile)
            .WithRequired(t => t.FeedEntry);
modelBuilder.Entity<FeedEntry>()
            .HasMany(t => t.ImageFiles)
            .WithRequired(t => t.FeedEntry);
modelBuilder.Entity<FeedEntry>()
            .HasMany(t => t.VideoFiles)
            .WithRequired(t => t.FeedEntry);

特别是,这具有定义Optional:Required 关系的效果

FeedEntry 1 ←—→ 0..1 MetadataFile

其中FeedEntry 自动成为关系中的主体,MetadataFile 自动成为依赖关系。


所以问题已经得到解答,所有的解释都如answer below 所描述的那样,并且我使用所描述的模型来针对新数据库和现有数据库这一事实。

【问题讨论】:

  • 你能告诉我们你是如何在 FeedEntry 类中声明 MetadataFile 的吗?
  • 刚刚更新了 EF 映射信息...谢谢
  • 你用的是什么版本的EF?
  • 来自 NuGet 的实体框架 6.1.1

标签: c# linq entity-framework extension-methods iqueryable


【解决方案1】:

有两件事要尝试,第一件事是 EF 如何加载数据。

我相信它认为由于延迟加载它不需要MetadataFile,所以将其关闭,看看它是否有任何改变:

db.ContextOptions.LazyLoadingEnabled = false;

还可以尝试使用成员 .Include("MetadataFile"). 的字符串文字,这可能是键。

【讨论】:

  • 感谢您的评论。我首先单独尝试了db.Configuration.LazyLoadingEnabled = false(我使用的是Code-First),然后只尝试了"MetadataFile",然后两者都尝试了。结果相同。
【解决方案2】:

Entity Framework 不支持这种方式的 1:1 或 1:0..1 关系。只有当有一个共同的共享主键时才支持它们。这意味着两个表必须具有相同的主键名称,并且一个 PK 必须是另一个 PK 的外键(这也意味着至少一个 PK 不能是 IDENTITY 字段)

原因是EF不支持唯一性约束,因此不能保证外键字段中没有重复的id。

从技术上讲,EF6.1 支持唯一索引,但它们尚未增强 EF 以允许使用它来实现 1:1 关系。

EF 根本不支持这种映射,虽然它在某些情况下似乎可以工作,但您会遇到这种奇怪而古怪的行为并且无法信任它。

ToList() 方法不起作用而其他方法不起作用的原因是 ToList() 方法需要实际映射对象并检索它们。这会导致模型混乱。而 non-ToList() 版本只执行计数而不实际映射任何对象(它只是生成 sql 并返回整数计数结果,不需要对象模型映射)。

【讨论】:

  • 非常感谢您的评论。事实上,几个小时前我刚刚了解到,EF 通过 PK 实现了所有这些 1←→1 或 1←→0..1 或 0..1←→0..1 关系。我的错误是我的目标是一个现有数据库,它肯定使用了一个单独的字段MetadataFile.FeedEntry_Id 来表示FK。所以这绝对解释了为什么模型变得令人难以置信。你的回答值得追授。 :D
  • 然后我找到了这个.Map(t =&gt; t.MapKey("FeedEntry_Id"))fluent 方法,它可以让我强制EF使用现有的FK。但这给我留下了设计问题,我将在另一个问题中介绍。
  • 最后一点:确实,计算所有FeedEntries MetadataFile,计算所有MetadataFiles 就足够了,根据定义,MetadataFile 只能与唯一关联的FeedEntry 存在。因此,只计算MetadataFile 表中的所有行就足够了,这是打印的唯一正确计数。 EF 可以轻松优化此 SQL。
猜你喜欢
  • 1970-01-01
  • 2017-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多