使用存储库模式使用 ThenIclude 急切加载实体
Using repository pattern to eager load entities using 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
检索多级预加载实体。
你可以改成这样:
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))
这是一个有点老的问题,但由于它没有公认的答案,我想我会 post 我的解决方案。
我正在使用 EF Core 并希望做到这一点,从我的存储库外部访问预先加载 class 这样我就可以指定每次调用存储库方法时要加载的导航属性。因为我有大量的表和数据,所以我不想要一组标准的急切加载实体,因为我的一些查询只需要父实体而一些需要整个树。
我当前的实现仅支持 IQueryable
方法(即 FirstOrDefault
、Where
,基本上是标准的 lambda 函数),但我相信您可以使用它传递给您的特定存储库方法。
我从 EF Core 的 EntityFrameworkQueryableExtensions.cs
的源代码开始,其中定义了 Include
和 ThenInclude
扩展方法。不幸的是,EF 使用内部 class 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 实体的接口。 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
的部分。我有几个扩展方法,它们采用 return 和 IIncludableJoin
来允许方法链接。我在其中调用 DbSet 上的 EF Include
和 ThenInclude
方法:
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);
}
}
在这些方法中,我使用上述 GetQuery
方法获取内部 IIncludableQueryable
属性,调用相关的 Include
或 ThenInclude
方法,然后return创建一个新的 IncludableJoin
对象以支持方法链接。
就是这样。 this的用法是这样的:
IAccount account = _accountRepository.Join(x=>x.Subscription).Join(x=>x.Addresses).ThenJoin(x=>x.Address).FirstOrDefault(x => x.UserId == userId);
以上将加载基础 Account
实体,它是一对一的子实体 Subscription
,它是一对多的子列表 Addresses
和它的子实体 Address
.沿途的每个 lambda 函数都是强类型的,并受智能感知支持,以显示每个实体的可用属性。
我的应用程序使用 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
检索多级预加载实体。
你可以改成这样:
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))
这是一个有点老的问题,但由于它没有公认的答案,我想我会 post 我的解决方案。
我正在使用 EF Core 并希望做到这一点,从我的存储库外部访问预先加载 class 这样我就可以指定每次调用存储库方法时要加载的导航属性。因为我有大量的表和数据,所以我不想要一组标准的急切加载实体,因为我的一些查询只需要父实体而一些需要整个树。
我当前的实现仅支持 IQueryable
方法(即 FirstOrDefault
、Where
,基本上是标准的 lambda 函数),但我相信您可以使用它传递给您的特定存储库方法。
我从 EF Core 的 EntityFrameworkQueryableExtensions.cs
的源代码开始,其中定义了 Include
和 ThenInclude
扩展方法。不幸的是,EF 使用内部 class 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 实体的接口。 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
的部分。我有几个扩展方法,它们采用 return 和 IIncludableJoin
来允许方法链接。我在其中调用 DbSet 上的 EF Include
和 ThenInclude
方法:
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);
}
}
在这些方法中,我使用上述 GetQuery
方法获取内部 IIncludableQueryable
属性,调用相关的 Include
或 ThenInclude
方法,然后return创建一个新的 IncludableJoin
对象以支持方法链接。
就是这样。 this的用法是这样的:
IAccount account = _accountRepository.Join(x=>x.Subscription).Join(x=>x.Addresses).ThenJoin(x=>x.Address).FirstOrDefault(x => x.UserId == userId);
以上将加载基础 Account
实体,它是一对一的子实体 Subscription
,它是一对多的子列表 Addresses
和它的子实体 Address
.沿途的每个 lambda 函数都是强类型的,并受智能感知支持,以显示每个实体的可用属性。