Ответ 1
Как сказано в комментариях других, вы можете взять код EF6 для разбора ваших выражений и применить соответствующие вызовы Include
/ThenInclude
. В конце концов, это выглядит не так уж и сложно, но так как это не было моей идеей, я бы предпочел не ставить ответ с кодом для этого.
Вместо этого вы можете изменить свой шаблон для предоставления некоторого интерфейса, позволяющего вам указывать ваши включения от вызывающего абонента, не давая ему доступ к базовому запросу.
Это приведет к чему-то вроде:
using YourProject.ExtensionNamespace;
// ...
patientRepository.GetById(0, ip => ip
.Include(p => p.Addresses)
.ThenInclude(a=> a.Country));
Пространство имен using
on должно соответствовать имени пространства имен, содержащему методы расширения, определенные в последнем блоке кода.
GetById
был бы сейчас:
public static Patient GetById(int id,
Func<IIncludable<Patient>, IIncludable> includes)
{
return context.Patients
.IncludeMultiple(includes)
.FirstOrDefault(x => x.EndDayID == id);
}
Метод расширения IncludeMultiple
:
public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
Func<IIncludable<T>, IIncludable> includes)
where T : class
{
if (includes == null)
return query;
var includable = (Includable<T>)includes(new Includable<T>(query));
return includable.Input;
}
Includable
классы и интерфейсы, которые являются простыми "заполнителями", для которых дополнительные методы расширений будут выполнять работу, имитирующую методы EF Include
и ThenInclude
:
public interface IIncludable { }
public interface IIncludable<out TEntity> : IIncludable { }
public interface IIncludable<out TEntity, out TProperty> : IIncludable<TEntity> { }
internal class Includable<TEntity> : IIncludable<TEntity> where TEntity : class
{
internal IQueryable<TEntity> Input { get; }
internal Includable(IQueryable<TEntity> queryable)
{
// C# 7 syntax, just rewrite it "old style" if you do not have Visual Studio 2017
Input = queryable ?? throw new ArgumentNullException(nameof(queryable));
}
}
internal class Includable<TEntity, TProperty> :
Includable<TEntity>, IIncludable<TEntity, TProperty>
where TEntity : class
{
internal IIncludableQueryable<TEntity, TProperty> IncludableInput { get; }
internal Includable(IIncludableQueryable<TEntity, TProperty> queryable) :
base(queryable)
{
IncludableInput = queryable;
}
}
IIncludable
методы расширения:
using Microsoft.EntityFrameworkCore;
// others using ommitted
namespace YourProject.ExtensionNamespace
{
public static class IncludableExtensions
{
public static IIncludable<TEntity, TProperty> Include<TEntity, TProperty>(
this IIncludable<TEntity> includes,
Expression<Func<TEntity, TProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity>)includes).Input
.Include(propertySelector);
return new Includable<TEntity, TProperty>(result);
}
public static IIncludable<TEntity, TOtherProperty>
ThenInclude<TEntity, TOtherProperty, TProperty>(
this IIncludable<TEntity, TProperty> includes,
Expression<Func<TProperty, TOtherProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity, TProperty>)includes)
.IncludableInput.ThenInclude(propertySelector);
return new Includable<TEntity, TOtherProperty>(result);
}
public static IIncludable<TEntity, TOtherProperty>
ThenInclude<TEntity, TOtherProperty, TProperty>(
this IIncludable<TEntity, IEnumerable<TProperty>> includes,
Expression<Func<TProperty, TOtherProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity, IEnumerable<TProperty>>)includes)
.IncludableInput.ThenInclude(propertySelector);
return new Includable<TEntity, TOtherProperty>(result);
}
}
}
IIncludable<TEntity, TProperty>
почти похож на IIncludableQueryable<TEntity, TProperty>
из EF, но он не расширяет IQueryable
и не позволяет IQueryable
запроса.
Конечно, если вызывающая IIncludable
находится в той же сборке, она все равно может привести IIncludable
к Includable
и начать возиться с запросом. Но хорошо, если кто-то хочет ошибиться, мы не сможем помешать ему (рефлексия позволяет все). Что имеет значение, так это выставленный контракт.
Теперь, если вы не заботитесь о предоставлении IQueryable
вызывающей стороне (в чем я сомневаюсь), очевидно, просто измените аргумент params
для аргумента Func<Queryable<T>, Queryable<T>> addIncludes
и избегайте кодирования всех этих вещей выше.
И самое лучшее для конца: я не проверял это, я не использую Entity Framework в настоящее время!