c# - Entity Framework 包含指令未获得所有预期的相关行

标签 c# .net entity-framework entity-framework-6

在调试一些性能问题时,我发现 Entity 框架通过延迟加载加载了大量记录(900 次额外的查询调用并不快!)但我确信我有正确的包含。我已经设法将其简化为一个很小的测试用例来证明我所遇到的困惑,实际用例更复杂,所以我没有太多的空间来重新处理我的签名我在做,但希望这是我遇到的问题的一个明显例子。

文档有许多相关的 MetaInfo 行。我想获取按具有特定值的 MetaInfo 行分组的所有文档,但我希望包含所有 MetaInfo 行,这样我就不必为所有 Documents MetaInfo 发出新请求。

所以我得到了以下查询。

ctx.Configuration.LazyLoadingEnabled = false;

var DocsByCreator = ctx.Documents
    .Include(d => d.MetaInfo) // Load all the metaInfo for each object
    .SelectMany(d => d.MetaInfo.Where(m => m.Name == "Author") // For each Author
        .Select(m => new { Doc = d, Creator = m })) // Create an object with the Author and the Document they authored.
    .ToList(); // Actualize the collection

我希望这包含所有文档/作者对,并填充所有文档 MetatInfo 属性。

事实并非如此,我得到了 Document 对象和 Authors 就好了,但是 Documents MetaInfo 属性只有 Name == "Author"的 MetaInfo 对象

如果我将 where 子句从 select many 中移出,它会做同样的事情,除非我将它移到实现之后(虽然这里可能没什么大不了的,但它在实际应用程序中,因为这意味着我们得到比我们想要处理的多得多的数据。)

在尝试了很多不同的方法之后,我认为问题确实出在您执行 select(...new...) 以及 where 和 include 时。在实现后执行选择或 Where 子句使数据按我预期的方式显示。

我认为这是被过滤的 Document 的 MetaInfo 属性的问题,所以我按如下方式重写它以测试理论,并惊讶地发现这也给出了相同的(我认为是错误的)结果。

ctx.Configuration.LazyLoadingEnabled = false;

var DocsByCreator = ctx.Meta
    .Where(m => m.Name == "Author")
    .Include(m => m.Document.MetaInfo) // Load all the metaInfo for Document
    .Select(m => new { Doc = m.Document, Creator = m })
    .ToList(); // Actualize the collection

由于我们没有将 where 放在 Document.MetaInfo 属性上,我希望这可以绕过这个问题,但奇怪的是,文档似乎仍然只有“Author”MetaInfo 对象。

我已经创建了一个简单的测试项目并将其与一堆测试用例一起上传到 github,据我所知它们应该全部通过,只有过早实现通过的错误.

https://github.com/Robert-Laverick/EFIncludeIssue

有人有什么理论吗?我是否以某种方式滥用了 EF/SQL?有什么我可以做不同的事情来获得相同的结果组织吗?这是 EF 中的一个错误,它只是被默认情况下启用的 LazyLoad 从 View 中隐藏了,而且它是一种奇怪的组类型操作吗?

最佳答案

这是 EF 中的一个限制,因为如果返回实体的范围从引入 include 的地方发生变化,Includes 将被忽略。

我找不到 EF6 对此的引用,但它记录在 EF Core 中。 ( https://learn.microsoft.com/en-us/ef/core/querying/related-data )(请参阅“忽略包含”)我怀疑在某些情况下阻止 EF 的 SQL 生成完全擅离职守是一个限制。

所以 var docs = context.Documents.Include(d => d.Metas)将返回急切加载到文档中的元数据;只要你.SelectMany()您正在更改 EF 应该返回的内容,因此 Include 语句将被忽略。

如果你想返回所有文档,并包含一个作为作者的属性:

var DocsByCreator = ctx.Documents
    .Include(d => d.MetaInfo)
    .ToList() // Materialize the documents and their Metas.
    .SelectMany(d => d.MetaInfo.Where(m => m.Name == "Author") // For each Author
        .Select(m => new { Doc = d, Creator = m })) // Create an object with the Author and the Document they authored.
    .ToList(); // grab your collection of Doc and Author.

如果您只想要有作者的文档:

var DocsByCreator = ctx.Documents
    .Include(d => d.MetaInfo)
    .Where(d => d.MetaInfo.Any(m => m.Name == "Author")
    .ToList() // Materialize the documents and their Metas.
    .SelectMany(d => d.MetaInfo.Where(m => m.Name == "Author") // For each Author
        .Select(m => new { Doc = d, Creator = m })) // Create an object with the Author and the Document they authored.
    .ToList(); // grab your collection of Doc and Author.

这意味着您需要确保所有过滤逻辑都在第一个 'ToList() 之上完成称呼。或者,您可以考虑在查询之后解析 Author 元数据,例如填充 View 模型时,或者解析它的 Document 上未映射的“Author”属性。尽管我通常会避免未映射的属性,因为如果它们的使用滑入 EF 查询中,您会在运行时遇到严重错误。

编辑:根据 skip & take 的要求,我建议使用 View 模型来返回数据而不是返回实体。使用 View 模型,您可以指示 EF 仅返回您需要的原始数据,使用简单的填充代码或使用 Automapper 组合 View 模型,Automapper 可以很好地与 IQueryable 和 EF 配合使用,并且可以处理像这样的大多数延迟情况。

例如:

public class DocumentViewModel
{
    public int DocumentId { get; set; }
    public string Name { get; set; }
    public ICollection<MetaViewModel> Metas { get; set; } = new List<MetaViewModel>();
    [NotMapped]
    public string Author // This could be update to be a Meta, or specialized view model.
    {
        get { return Metas.SingleOrDefault(x => x.Name == "Author")?.Value; }
    }
}

public class MetaViewModel
{
    public int MetaId { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
}

然后查询:

var viewModels = context.Documents
    .Select(x => new DocumentViewModel
    {
        DocumentId = x.DocumentId,
        Name = x.Name,
        Metas = x.Metas.Select(m => new MetaViewModel
        {
            MetaId = m.MetaId,
            Name = m.Name,
            Value = m.Value
         }).ToList()
    }).Skip(pageNumber*pageSize)
    .Take(PageSize)
    .ToList();

“作者”与文档的关系在数据级别是隐含的,而不是强制的。该解决方案使实体模型保持“纯”数据表示,并让代码处理将隐含关系转换为公开文档作者的过程。

.Select() Automapper 可以使用 .ProjectTo<TViewModel>() 处理人口.

通过返回 View 模型而不是实体,您可以避免像这样的问题,其中 .Include() 操作变得无效,还可以避免由于在不同上下文之间分离和重新附加实体的诱惑而产生的问题,还可以通过仅选择来提高性能和资源使用并传输所需的数据,并在您忘记禁用延迟加载或意外的#null 数据时避免延迟加载序列化问题。

关于c# - Entity Framework 包含指令未获得所有预期的相关行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52746212/

相关文章:

c# - 当排除列表为空时,从 Linq 查询中排除所有结果

c# - 如何将连接字符串注入(inject) IDbContextFactory<T> 的实例?

c# - 在 Azure 上运行时的输出与在本地构建上运行时的输出不同

c# - 流畅的 nHibernate 配置

c# - 在 Azure Durable Functions 中,我如何适本地确定大量并行事件的进程?

c# - Quartz.Net 依赖注入(inject) .Net Core

.net - 如何从 TimeSpan 生成 double 值

c# - 如何以编程方式突出显示 DateTimePicker 中的特定部分?

c# - "MetadataException, Schema specified is not valid"

.net - 在生产中使用 Entity Framework (代码优先)迁移