Как загрузить сущность в EF Core с помощью метода AsNoTracking в сочетании с явной загрузкой связанных сущностей - PullRequest
1 голос
/ 20 апреля 2020

В настоящее время я использую этот подход для загрузки сущностей и связанных с ними сущностей с помощью 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: ссылка

Это работает , но этот запрос очень медленный, после миграции на 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: ссылка

К сожалению, я попытался переписать этот пример с помощью метода AsNoTracking, но он не работает - связанные объекты не загружаются.

Как я могу переписать свой исходный запрос с помощью более быстрой производительности с помощью подхода AsNoTracking?

Мне не нужно отслеживать клиентскую сущность для моего варианта использования.

Ответы [ 2 ]

0 голосов
/ 07 мая 2020

Поскольку в документации 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();
...
...

И так до тех пор, пока вы не получите все необходимые ресурсы. Это будет имитировать c предыдущее поведение. Если вам действительно нужно использовать свойства навигации, то вы, к сожалению, не можете использовать .AsNoTracking(). Если моя идея выглядит слишком наивной, я могу думать только о том, чтобы отсоединить сущности от контекста отслеживания после использования свойств навигации.

Так что после реализации кода из второй ссылки вам нужно будет go через энтиты в вашем объекте и пометить их как отдельные DbContext.Entry(entity).State = EntityState.Detached;

0 голосов
/ 20 апреля 2020

Простой ответ: вы не можете и не должны. Во-первых, опасно то, что вы делаете в исходном вызове, какова цель? Тем не менее, включает в себя медленные и требуют объединения. Но я помню, что AsNoTracking всегда должен go сначала сразу после вызова объекта. А потом включает в себя, например. То же самое относится и к случаю «Где», поместите это непосредственно после AsNoTracking, чтобы увидеть, имеет ли это значение.

Но внимательно прочтите это Документы по связанным данным, 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...