我有一个扩展方法,可以让您在EF中一般包含数据:
public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params Expression<Func<T, object>>[] includes)
where T : class
{
if (includes != null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
return query;
}
这允许我在我的存储库中有这样的方法:
public Patient GetById(int id, params Expression<Func<Patient, object>>[] includes)
{
return context.Patients
.IncludeMultiple(includes)
.FirstOrDefault(x => x.PatientId == id);
}
我相信扩展方法在EF Core之前有效,但现在包括“children”就像这样:
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author);
有没有办法改变我的通用扩展方法以支持EF Core的新ThenInclude()
练习?
答案 0 :(得分:9)
正如其他人在comments中所述,您可以EF6 code解析表达式并应用相关的Include
/ ThenInclude
来电。毕竟它看起来并不那么难,但由于这不是我的想法,我宁愿不回答它的代码。
您可以更改模式以显示某些界面,从而允许您从调用者指定包含而不让它访问基础查询。
这会导致类似:
using YourProject.ExtensionNamespace;
// ...
patientRepository.GetById(0, ip => ip
.Include(p => p.Addresses)
.ThenInclude(a=> a.Country));
命名空间上的using
必须与包含最后一个代码块中定义的扩展方法的命名空间名称匹配。
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
课程&amp;接口,这是简单的&#34;占位符&#34;其他扩展方法将用于模仿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>
几乎与EF IIncludableQueryable<TEntity, TProperty>
相似,但它不会扩展IQueryable
,也不允许重新整形查询。
当然,如果调用者在同一个程序集中,它仍然可以将IIncludable
强制转换为Includable
并开始摆弄可查询对象。但是,如果有人想弄错,我们就无法阻止他这样做(反射允许任何事情)。重要的是暴露的合同。
现在如果你不关心将IQueryable
暴露给调用者(我怀疑),显然只需更改params
参数的Func<Queryable<T>, Queryable<T>> addIncludes
参数,并避免编码所有这些事情上方。
最好的结果:我没有测试过,我目前没有使用Entity Framework!
答案 1 :(得分:7)
对于后代而言,另一个不太有说服力但更简单的解决方案是利用使用Include()
的{{1}}重载:
navigationPropertyPath
存储库调用是:
public static class BlogIncludes
{
public const string Posts = "Posts";
public const string Author = "Posts.Author";
}
internal static class DataAccessExtensions
{
internal static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
params string[] includes) where T : class
{
if (includes != null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
return query;
}
}
public Blog GetById(int ID, params string[] includes)
{
var blog = context.Blogs
.Where(x => x.BlogId == id)
.IncludeMultiple(includes)
.FirstOrDefault();
return blog;
}
答案 2 :(得分:3)
当然有,
你可以遍历原始参数的表达式树,以及任何嵌套的包含,将它们添加为
2017-03-27 19:00:00.6866682 +03:00
但它不是微不足道的,但绝对是非常可行的,请分享,如果你这样做,因为它可以明确地重复使用!
答案 3 :(得分:2)
您可以执行以下操作:
public Patient GetById(int id, Func<IQueryable<Patient>, IIncludableQueryable<Patient, object>> includes = null)
{
IQueryable<Patient> queryable = context.Patients;
if (includes != null)
{
queryable = includes(queryable);
}
return queryable.FirstOrDefault(x => x.PatientId == id);
}
var patient = GetById(1, includes: source => source.Include(x => x.Relationship1).ThenInclude(x => x.Relationship2));
答案 4 :(得分:1)
我制作了这种方法来进行动态包含。这样,可以在lambda中使用“选择”命令以像以前一样包含它。
该呼叫的工作方式如下:
repository.IncludeQuery(query, a => a.First.Second.Select(b => b.Third), a => a.Fourth);
private IQueryable<TCall> IncludeQuery<TCall>(
params Expression<Func<TCall, object>>[] includeProperties) where TCall : class
{
IQueryable<TCall> query;
query = context.Set<TCall>();
foreach (var property in includeProperties)
{
if (!(property.Body is MethodCallExpression))
query = query.Include(property);
else
{
var expression = property.Body as MethodCallExpression;
var include = GenerateInclude(expression);
query = query.Include(include);
}
}
return query;
}
private string GenerateInclude(MethodCallExpression expression)
{
var result = default(string);
foreach (var argument in expression.Arguments)
{
if (argument is MethodCallExpression)
result += GenerateInclude(argument as MethodCallExpression) + ".";
else if (argument is MemberExpression)
result += ((MemberExpression)argument).Member.Name + ".";
else if (argument is LambdaExpression)
result += ((MemberExpression)(argument as LambdaExpression).Body).Member.Name + ".";
}
return result.TrimEnd('.');
}
答案 5 :(得分:0)
我坚持使用更简单的解决方案,该解决方案利用了使用字符串NavigationPropertyPath的Include()重载。我能写的最简单的是下面的扩展方法。
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace MGame.Data.Helpers
{
public static class IncludeBuilder
{
public static IQueryable<TSource> Include<TSource>(this IQueryable<TSource> queryable, params string[] navigations) where TSource : class
{
if (navigations == null || navigations.Length == 0) return queryable;
return navigations.Aggregate(queryable, EntityFrameworkQueryableExtensions.Include); // EntityFrameworkQueryableExtensions.Include method requires the constraint where TSource : class
}
}
}
答案 6 :(得分:0)
public TEntity GetByIdLoadFull(string id, List<string> navigatonProoperties)
{
if (id.isNullOrEmpty())
{
return null;
}
IQueryable<TEntity> query = dbSet;
if (navigationProperties != null)
{
foreach (var navigationProperty in navigationProperties)
{
query = query.Include(navigationProperty.Name);
}
}
return query.SingleOrDefault(x => x.Id == id);
}
这是一个更简单的解决方案,想法是将dbset强制转换为iqueryable,然后以递归方式包含属性
答案 7 :(得分:-1)
public Task<List<TEntity>> GetAll()
{
var query = _Db.Set<TEntity>().AsQueryable();
foreach (var property in _Db.Model.FindEntityType(typeof(TEntity)).GetNavigations())
query = query.Include(property.Name);
return query.ToListAsync();
}