Entity Framework Core 忽略 .Include(..) 而没有 .ToList(..) 间接
Posted
技术标签:
【中文标题】Entity Framework Core 忽略 .Include(..) 而没有 .ToList(..) 间接【英文标题】:Entity Framework Core ignoring .Include(..) without .ToList(..) indirectly 【发布时间】:2017-09-18 09:16:36 【问题描述】:如in "Loading Related Data" from EF Core Documentation 所述,我们可以使用.Include(..)
从DbSet
(或通用IQueryable<T>
链接回EF 上下文)急切加载导航属性。
这意味着,给定以下模型:
public class TestEntityA
public int Id get; set;
public int TestEntityBId get; set;
public TestEntityB TestEntityB get; set;
public string BProperty get return TestEntityB.Property;
public class TestEntityB
public int Id get; set;
public string Property get; set;
.. 那么下面的代码应该可以工作:
context.TestEntityAs
.Include(m => m.TestEntityB)
.Any(m => m.BProperty == "Hello World");
/*
* Note that the below DOES work by using the nav property directly
* in the query, but that is not always going to be an option for
* whatever reason. If it's .Included it should be available through
* INNER JOINing it into the base query before the .Any runs.
* .Any(m => m.TestEntityB.Property == "Hello World");
*/
其实不然。
我注意到有一个警告,如果查询不返回最初请求的类型,.Include()
可能会被忽略:
如果您更改查询以使其不再返回查询开始时使用的实体类型的实例,则包含运算符将被忽略。 [snip] 默认情况下,当忽略包含运算符时,EF Core 将记录一个警告。
我不确定在上面对.Any()
的调用中是如何相关的。是的,查询没有返回原始类型(它当然返回bool
),但同时也没有记录警告以告知它被忽略。
我的问题是:
这是一个预期可行的用例吗?我应该在 EF Core 中提出错误吗? 如果不是预期的,a 解决方法如下(调用.ToList()
),但这显然会加载所有内容,以查明.Any()
上是否有任何内容,这很容易被一个查询(在 EF6 中也是如此)。有什么解决方法可以让这个.Any()
在服务器端工作,从而不需要 ToList 将其放入内存?
解决方法:
context.TestEntityAs
.Include(m => m.TestEntityB)
.ToList()
.Any(m => m.BProperty == "Hello World");
完整的可重复样本:https://gist.github.com/rudiv/3aa3e1bb65b86ec78ec6f5620ee236ab
【问题讨论】:
第一个不起作用的查询生成的 SQL 是什么?您不需要为此添加任何内容,它应该可以正常工作。 我想我在您的代码中看到了问题 - 您正在使用未映射的属性m => m.BProperty
。只是不要那样做 - 改用m => m.TestEntityB.Property
。
@RudiVisser:BProperty
在您的情况下是一个计算属性,并且不映射到表列,这就是为什么它不能用于将表达式转换为 T-SQL 等。您使用的一切在IQueryable<T>
的扩展方法中必须可映射到表列,因为它会被转换为查询。 EF Core 不知道 BProperty
指向 B 属性
@Tseng 我认为 OP 的观点(我应该同意)是因为这不能被翻译成 SQL,它应该在内存中进行评估(EF Core 功能,EF6 会简单地抛出不支持例外),因此 Include
应该生效 - 就像你这样做 .AsEnumerable().Any(...)
顺便说一句有效。
@Tseng 在这种情况下我同意,但在类似context.TestEntityAs.Include(m => m.TestEntityB).Select(m => m.BProperty)
的情况下,它会更有意义。但我认为 EF 很难决定 Include
何时与最终结果相关。我认为他们不会支持这种情况。
【参考方案1】:
按照命名约定,它应该可以工作 您可以尝试使用这段代码手动配置模型之间的关系
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<TestEntityA>()
.HasOne(x => x.TestEntityB)
.WithMany()
.HasForeignKey(x => x.TestEntityBId);
【讨论】:
【参考方案2】:该行为是预期的,但您可以使用显式加载来进行更有效的查询,如下所示。
2 个单独的查询,但不需要加载所有的 TestEntityB
// First query
var testEntityAs = context.TestEntityAs.ToList();
var testEntityAsIds = testEntityAs.Select(t => t.Id);
// Second query, can apply filter of Hello World without loading all TestEntityBs
context.TestEntityBs
.Where(t => testEntityAsIds.Contains(t.Id) && t.Property == "Hello World")
.Load();
// In memory check
var isAny = testEntityAs.Any(t => !string.IsNullOrEmpty(t.BProperty));
【讨论】:
【参考方案3】:根据数据,实现这一目标的有效方法可能是加载单个记录:
context.TestEntityAs
.Include(m => m.TestEntityB)
.Where(m => m.BProperty == "Hello World")
.FirstOrDefault() != null;
【讨论】:
【参考方案4】:我不确定,但请尝试 [ForeignKey(nameof(TestEntityBId ))]
public class TestEntityA
public int Id get; set;
public int TestEntityBId get; set;
[ForeignKey(nameof(TestEntityBId ))] public TestEntityB TestEntityB get; set;
public string BProperty get return TestEntityB.Property;
public class TestEntityB
public int Id get; set;
public string Property get; set;
【讨论】:
【参考方案5】:我不确定这是否能回答您的问题,但这是一种解决方法。
将BProperty
更改为getBProperty
之类的方法
public string getBProperty (TestEntityB tb) return tb.Property;
然后像这样写你的查询
context.TestEntityAs
//.Include(m => m.TestEntityB) --- you don't need include anymore!
.Any(m => m.getBProperty(m.TestEntityB)) == "Hello World");
【讨论】:
以上是关于Entity Framework Core 忽略 .Include(..) 而没有 .ToList(..) 间接的主要内容,如果未能解决你的问题,请参考以下文章
EF6 System.Data.Entity.Core.EntityKey 在 Entity Framework Core 中的等价物是啥?