Это немного старый вопрос, но так как это Безразлично У меня есть принятый ответ. Я думал, что смогу опубликовать свое решение.
Я использую EF Core и хотел сделать именно это, получить доступ к загрузке из-за пределов моего класса репозитория, чтобы я мог указать свойства навигации для загрузки каждый раз, когда я вызываю метод репозитория. Поскольку у меня большое количество таблиц и данных, мне не нужен стандартный набор загружаемых объектов, так как некоторым моим запросам нужен только родительский объект, а некоторые нужны всему дереву.
Моя текущая реализация поддерживает только IQueryable
метода (то есть. FirstOrDefault
, Where
, в основном стандартные функции лямбды), но я уверен, что вы могли бы использовать его, чтобы пройти через ваши конкретные методы хранилища.
Я начал с исходного кода для EF Core EntityFrameworkQueryableExtensions.cs
, где определены методы расширения Include
и ThenInclude
. К сожалению, EF использует внутренний класс IncludableQueryable
, чтобы удерживать дерево предыдущих свойств, чтобы включить сильный тип позже. Однако реализация для этого не более чем 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 объекта. Implmentation метода 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
, чтобы обеспечить цепочку методов. Внутри который я называю ЭФ 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
ребенок один-ко-многим, и это ребенок Address
. Каждая функция лямбда вдоль пути строго типизирована и поддерживается intellisense для отображения свойств, доступных для каждого объекта.
Будет ли недостаток производительности для присоединения сначала, а затем выбор (через FirstOrDefault() по сравнению с первым выбором? – phhbr
@phhbr Я так не думаю? Не думаю, что это имеет значение, потому что когда вы сидите в «IQueryable», запрос не выполняется до тех пор, пока вы не назовете «.ToList» или подобное. Таким образом, порядок операций, вероятно, не имеет значения, но я его не тестировал. – Steve