如何结合显式加载相关实体,使用 'AsNoTracking' 方法在 EF Core 中加载实体

How to load entity in EF Core with the 'AsNoTracking' method in combination with explicitly loading of related entities

我目前正在使用这种方法来加载实体及其相关实体 AsNoTracking:

await DbContext.Clients
                .Include(x => x.AllowedGrantTypes)
                .Include(x => x.RedirectUris)
                .Include(x => x.PostLogoutRedirectUris)
                .Include(x => x.AllowedScopes)
                .Include(x => x.ClientSecrets)
                .Include(x => x.Claims)
                .Include(x => x.IdentityProviderRestrictions)
                .Include(x => x.AllowedCorsOrigins)
                .Include(x => x.Properties)
                .Where(x => x.Id == clientId)
                .AsNoTracking()
                .SingleOrDefaultAsync();

Github 上的代码详细信息:link

这可行,但在迁移到 EF Core 3.0 后,此查询非常慢。

我发现可以通过像这样显式加载相关实体来解决此性能问题:

IQueryable<Entities.Client> baseQuery = Context.Clients
                .Where(x => x.Id == clientId)
                .Take(1);

            var client = await baseQuery.FirstOrDefaultAsync();
            if (client == null) return null;

            await baseQuery.Include(x => x.AllowedCorsOrigins).SelectMany(c => c.AllowedCorsOrigins).LoadAsync();
            await baseQuery.Include(x => x.AllowedGrantTypes).SelectMany(c => c.AllowedGrantTypes).LoadAsync();
            await baseQuery.Include(x => x.AllowedScopes).SelectMany(c => c.AllowedScopes).LoadAsync();
            await baseQuery.Include(x => x.Claims).SelectMany(c => c.Claims).LoadAsync();
            await baseQuery.Include(x => x.ClientSecrets).SelectMany(c => c.ClientSecrets).LoadAsync();
            await baseQuery.Include(x => x.IdentityProviderRestrictions).SelectMany(c => c.IdentityProviderRestrictions).LoadAsync();
            await baseQuery.Include(x => x.PostLogoutRedirectUris).SelectMany(c => c.PostLogoutRedirectUris).LoadAsync();
            await baseQuery.Include(x => x.Properties).SelectMany(c => c.Properties).LoadAsync();
            await baseQuery.Include(x => x.RedirectUris).SelectMany(c => c.RedirectUris).LoadAsync();

Github 上的代码详细信息:link

不幸的是,我曾尝试用 AsNoTracking 方法重写此示例,但它不起作用 - 相关实体未加载。

如何使用 AsNoTracking 方法通过更快的性能重写我的原始查询?

我不需要为我的用例跟踪客户端实体。

简单的答案是你不能也不应该。 首先,你在原来的电话中所做的事情看起来很危险,目的是什么? 然而,包含很慢并且需要连接。但我记得 AsNoTracking 总是应该在实体调用之后直接进行。然后包括例如。对于 Where 情况也是如此,将其直接放在 AsNoTracking 之后以查看它是否有所作为。

但是请仔细阅读这篇文章Related Data Docs, EF Core 特别是关于 显式加载:

的部分
using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    context.Entry(blog)
        .Collection(b => b.Posts)
        .Load();

    context.Entry(blog)
        .Reference(b => b.Owner)
        .Load();
}

和相关实体 这允许您对相关实体执行 运行 聚合运算符等操作,而无需将它们加载到内存中。

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var postCount = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Count();
}

正如 EF Core 文档所说,v3 现在生成连接,此查询是 笛卡尔爆炸问题 https://docs.microsoft.com/en-us/ef/core/querying/related-data

的受害者

文档还说,在以前的版本中,EF 为每个包含生成单独的查询。所以在我看来好的解决方案是

var baseEntity = await DbContext.Clients.AsNoTracking().SingleOrDefaultAsync(x => x.Id == clientId);
baseEntity.AllowedGrantTypes = await DbContext.ClientCorsOrigins.AsNoTracking().Where(x => x.ClientId == clientID).ToListAsync();
baseEntity.RedirectUris = await DbContext.ClientRedirectUris.AsNoTracking().Where(x => x.ClientId == clientID).ToListAsync();
...
...

依此类推,直到您获得所需的所有相关资源。它将模仿以前的行为。 如果您真的需要使用导航属性,那么不幸的是您不能使用 .AsNoTracking()。如果我的想法看起来太天真了——我能想到的唯一其他方法是在使用导航属性后将实体从跟踪上下文中分离出来。

因此,在实现第二个 link 的代码后,您需要遍历对象中的实体并将它们标记为分离的 DbContext.Entry(entity).State = EntityState.Detached;