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

Jen*_*nan 5 .net-core asp.net-core ef-core-3.0

我目前正在使用这种方法来加载实体及其相关实体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();
Run Code Online (Sandbox Code Playgroud)

Github 上的代码详细信息:链接

这可行,但迁移到 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();

Run Code Online (Sandbox Code Playgroud)

Github 上的代码详细信息:链接

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

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

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

小智 1

正如 EF Core 文档所说,v3 现在生成联接,并且此查询是笛卡尔爆炸问题 的受害者https://learn.microsoft.com/en-us/ef/core/querying/lated-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();
...
...
Run Code Online (Sandbox Code Playgroud)

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

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