Использование шаблона репозитория для активной загрузки объектов с помощью ThenIclude
мое приложение использует Entity Framework 7 и шаблон репозитория.
метод GetById в репозитории поддерживает нетерпеливую загрузку дочерних сущностей:
public virtual TEntity GetById(int id, params Expression<Func<TEntity, object>>[] paths)
{
var result = this.Set.Include(paths.First());
foreach (var path in paths.Skip(1))
{
result = result.Include(path);
}
return result.FirstOrDefault(e => e.Id == id);
}
использование заключается в следующем, чтобы получить продукт (чей id равен 2) вместе с заказами и частями, связанными с этим продуктом:
productRepository.GetById(2, p => p.Orders, p => p.Parts);
Я хочу улучшить этот метод для поддержки нетерпеливой загрузки сущностей, вложенных глубже одного уровня. Например, предположим Order
своя коллекция LineItem
'ы.
до EF7 я считаю, что можно было бы также получить LineItems, связанные с каждым порядком:
productRepository.GetById(2, p => p.Orders.Select(o => o.LineItems), p => p.Parts);
однако это, похоже, не поддерживается в EF7. Вместо этого существует новый метод ThenInclude, который извлекает дополнительные уровни вложенных сущностей:
https://github.com/aspnet/EntityFramework/wiki/Design-Meeting-Notes:-January-8,-2015
Я не знаю, как чтобы обновить мой репозиторий для поддержки извлечения нескольких уровней нетерпеливых загруженных объектов, используя ThenInclude
.
2 ответов
это немного старый вопрос, но поскольку у него нет принятого ответа, я подумал, что опубликую свое решение.
я использую EF Core и хотел сделать именно это, получить доступ к нетерпеливой загрузке из-за пределов моего класса репозитория, чтобы я мог указать свойства навигации для загрузки каждый раз, когда я вызываю метод репозитория. Поскольку у меня есть большое количество таблиц и данных, я не хотел стандартный набор нетерпеливо загружаемых сущностей, так как некоторые из моих запросов нуждались только в родителе сущности и некоторым понадобилось целое дерево.
моя текущая реализация поддерживает только IQueryable
метод (т. е. FirstOrDefault
, Where
, в основном стандартные лямбда-функции), но я уверен, что вы можете использовать его для перехода к вашим конкретным методам репозитория.
я начал с исходного кода для EF Core EntityFrameworkQueryableExtensions.cs
где Include
и ThenInclude
определены методы расширения. К сожалению, EF использует внутренний класс IncludableQueryable
чтобы держать дерево предыдущие свойства для включения strongly type later включают. Однако реализация для этого не более чем IQueryable
с дополнительным универсальным типом для предыдущего объекта.
я создал свою собственную версию, которую я назвал IncludableJoin
что происходит IIncludableQueryable
в качестве параметра конструктора и сохраняет его в закрытом поле для последующего доступа:
public interface IIncludableJoin<out TEntity, out TProperty> : IQueryable<TEntity>
{
}
public class IncludableJoin<TEntity, TPreviousProperty> : IIncludableJoin<TEntity, TPreviousProperty>
{
private readonly IIncludableQueryable<TEntity, TPreviousProperty> _query;
public IncludableJoin(IIncludableQueryable<TEntity, TPreviousProperty> query)
{
_query = query;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<TEntity> GetEnumerator()
{
return _query.GetEnumerator();
}
public Expression Expression => _query.Expression;
public Type ElementType => _query.ElementType;
public IQueryProvider Provider => _query.Provider;
internal IIncludableQueryable<TEntity, TPreviousProperty> GetQuery()
{
return _query;
}
}
обратите внимание на внутренние GetQuery
метод. Это будет важно позже.
далее, в моем родовом IRepository
интерфейс, я определил отправную точку для нетерпеливой загрузки:
public interface IRepository<TEntity> where TEntity : class
{
IIncludableJoin<TEntity, TProperty> Join<TProperty>(Expression<Func<TEntity, TProperty>> navigationProperty);
...
}
на TEntity
универсальный тип -интерфейс моей сущности EF. Реализовать в Join
метод в моем общем репозитории выглядит так:
public abstract class SecureRepository<TInterface, TEntity> : IRepository<TInterface>
where TEntity : class, new()
where TInterface : class
{
protected DbSet<TEntity> DbSet;
protected SecureRepository(DataContext dataContext)
{
DbSet = dataContext.Set<TEntity>();
}
public virtual IIncludableJoin<TInterface, TProperty> Join<TProperty>(Expression<Func<TInterface, TProperty>> navigationProperty)
{
return ((IQueryable<TInterface>)DbSet).Join(navigationProperty);
}
...
}
теперь для части, которая фактически позволяет несколько Include
и ThenInclude
. У меня есть несколько методов расширения, которые принимают и возвращают и IIncludableJoin
чтобы разрешить цепочку методов. Внутри которого я называю EF Include
и ThenInclude
методы на DbSet:
public static class RepositoryExtensions
{
public static IIncludableJoin<TEntity, TProperty> Join<TEntity, TProperty>(
this IQueryable<TEntity> query,
Expression<Func<TEntity, TProperty>> propToExpand)
where TEntity : class
{
return new IncludableJoin<TEntity, TProperty>(query.Include(propToExpand));
}
public static IIncludableJoin<TEntity, TProperty> ThenJoin<TEntity, TPreviousProperty, TProperty>(
this IIncludableJoin<TEntity, TPreviousProperty> query,
Expression<Func<TPreviousProperty, TProperty>> propToExpand)
where TEntity : class
{
IIncludableQueryable<TEntity, TPreviousProperty> queryable = ((IncludableJoin<TEntity, TPreviousProperty>)query).GetQuery();
return new IncludableJoin<TEntity, TProperty>(queryable.ThenInclude(propToExpand));
}
public static IIncludableJoin<TEntity, TProperty> ThenJoin<TEntity, TPreviousProperty, TProperty>(
this IIncludableJoin<TEntity, IEnumerable<TPreviousProperty>> query,
Expression<Func<TPreviousProperty, TProperty>> propToExpand)
where TEntity : class
{
var queryable = ((IncludableJoin<TEntity, IEnumerable<TPreviousProperty>>)query).GetQuery();
var include = queryable.ThenInclude(propToExpand);
return new IncludableJoin<TEntity, TProperty>(include);
}
}
в этих методах, я получаю внутреннее IIncludableQueryable
свойство с использованием вышеупомянутого GetQuery
метод, вызывающий соответствующий Include
или ThenInclude
метод, возвращающий новый IncludableJoin
объект для поддержки цепочки методов.
и это все. Использование этого таково:
IAccount account = _accountRepository.Join(x=>x.Subscription).Join(x=>x.Addresses).ThenJoin(x=>x.Address).FirstOrDefault(x => x.UserId == userId);
вышеуказанное загрузило бы базу Account
сущности, это один-в-один ребенок Subscription
, это один-ко-многим список детей Addresses
и это ребенок!--32-->. Каждая лямбда-функция строго типизирована и поддерживается intellisense для отображения свойств, доступных для каждой сущности.
Вы можете изменить его на что-то вроде этого:
public virtual TEntity GetById<TEntity>(int id, Func<IQueryable<TEntity>, IQueryable<TEntity>> func)
{
DbSet<TEntity> result = this.Set<TEntity>();
IQueryable<TEntity> resultWithEagerLoading = func(result);
return resultWithEagerLoading.FirstOrDefault(e => e.Id == id);
}
И вы можете использовать его так:
productRepository.GetById(2, x => x.Include(p => p.Orders)
.ThenInclude(o => o.LineItems)
.Include(p => p.Parts))