如何在 Entity Framework 6 中动态加载子实体
How can I load Child entities dynamically in Entity Framework 6
我有一个提供商 class 和一个文章。文章有一个 int ProviderId{get;set}
和一个 public virtual Provider Provider {get;set;}
属性。
我知道延迟加载以及为什么我无法在上下文之外访问 Article 中的 Provider 属性 但我有一个通用方法 returns 下一个 T 如下所示:
public static T Next<T>(T currentElement) where T : class, IModel {
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities()) {
data = context.Set<T>().Single(el => el.Id == currentElement.Id + 1);
}
return data;
}
但是我无法在文章 Class 中检索像 Provider 这样的子实体。如何包含所有实体?我应该更新方法并为每个实体制作一个吗?
我读到 Eager loading and Explicit loading 但我不知道如何在我的方法中实现这些。
注:
并非我所有的实体都有子实体,我有更多方法,如 Previous<T>()
、First<T>()
或 Last<T>()
可以完成您期望的工作。
您可以扩展您的方法以获取可能包含的列表。例如:
public static T Next<T>(T currentElement, params Expression<Func<T, object>>[] includes)
where T : class, IModel
{
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
IQueryable<T> query = context.Set<T>();
//Deferred execution allows us to build up the query
foreach (var include in includes)
{
query = query.Include(include);
}
return query.Single(el => el.Id == currentElement.Id + 1);
}
}
并像这样使用它:
var someEntity = ...;
var nextEntity = Next(someEntity,
e => e.Childcollection1,
e => e.Childcollection2,
//
e => e.ChildcollectionN)
另外一点,要获得下一个实体,您不应依赖 ID 值的顺序,例如,如果条目被删除,它们将乱序。相反,考虑这样的事情:
return query.OrderBy(el => el.Id).First(el => el.Id > currentElement.Id);
您可以为接受 Expression<Func<T, object>>
:
的 Next
方法创建重载
public static T Next<T>(T currentElement, Expression<Func<T, object>> navigationProperty) where T : class, IModel
{
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
IQueryable<T> dbQuery = context.Set<T>();
if (navigationProperty != null)
dbQuery = dbQuery.Include<T, object>(navigationProperty);
data = dbQuery.AsNoTracking().Single(el => el.Id == currentElement.Id + 1);
}
return data;
}
用法:
var entity = Next(instance, x => x.Provider);
有关详细信息和完整示例,请参阅以下博客 post。
使用 Entity Framework 实现通用数据访问层: https://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/
所以你在 Provider
和 Article
之间有一个一对多的关系:每个 Provider
有零个或多个 Articles
,并且每个 Article
正好属于一个 Provider
.
如果您在 Entity Framework 中正确配置了这种一对多关系,Provider 应该会引用其多个 Articles
:
public virtual ICollection<Article> Articles{get; set;}
此外,您还有一个函数 Next
,它将 class T
的对象作为输入,该对象是实现 IModel
的 class。 Next
returns 来自 Ts 的 DbSet 的下一个元素,假设您对下一个元素有正确的定义。
显然您想调整函数 Next 以便您可以使用此函数获取下一个 Provider
及其所有 Articles
.
更通用:如果T是一个在其中设计了适当的一对多关系的类型,并且你有一个T类型的对象,你希望T的第一个Id高于当前的Id T,包括它的所有 ICollections。
我把它分成几个更小的功能:
- 一个函数,给定类型 T returns 所有实现 ICollection
的属性
- 一个函数,给定一个 DbSet returns DbSet 的所有元素包括它的所有 ICollections
鉴于这两个,以及像 OrderBy
和 FirstOrDefault
这样的函数,您将能够获得 ID 大于当前提供者及其所有文章的第一个提供者。
static class QueryableExtensions
{
// returns true if PropertyInfo implements ICollection<...>
public static bool ImplementsICollection(this PropertyInfo propertyInfo)
{
return propertyInfo.IsGenericType
&& propertyInfo.GetGenericTypeDefinition() == typeof(ICollection<>);
}
// returns all readable & writable PropertyInfos of type T that implement ICollection<...>
public static IEnumerable<PropertyInfo> CollectionProperties<T>()
{
return typeof(T).GetProperties()
.Where(prop => prop.CanRead
&& prop.CanWrite
&& prop.PropertyType.ImplementICollection();
}
// given an IQueryable<T>, adds the Include of all ICollection<...> T has
public static IQueryable<T> IncludeICollection<T>(IQueryable<T> query)
{
var iCollectionProperties = CollectionProperties<T>();
foreach (var collectionProperty in collectionProperties)
{
query = query.Include(prop.Name);
}
return query;
}
// given a IQueryable<T> and a T return the first T in the query with Id higher than T
// for this we need to be sure every T has an IComparable property Id
// so T should implement interface IId (see below)
public T Next<T>(this IQueryable<T> query, T currentItem)
where T : IId // makes sure every T has aproperty Id
{
T nextElement = query
// keep only those DbSet items that have larger Ids:
.Where(item => currentItem.Id < item.Id)
// sort in ascending Id:
.OrderBy(item => item.Id
// keep only the first element of this sorted collection:
.FirstOrDefault();
return T
}
}
我需要确保每个T都有一个Id,否则你获取不到下一个Id。可能你的 IModel 界面中有这个:
public Interface IId
{
public int Id {get;}
}
使用这些函数后,您的查询将如下所示:
public static T Next<T>(T currentElement) where T : class, IModel, IId
{
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
return context.Set<T>().Next(currentElement);
}
}
我不想重复一个答案,但这里有一个函数结合了一点反射,可以根据模型声明自动加载所有与外键相关的数据。我将其编写为一种通过主键以外的方式加载对象的方法,但如果需要,可以对其进行修改。
private IQueryable<T> GetFullSet()
{
IEnumerable<IEntityType> entities = _dbContext.Model.GetEntityTypes();
IEntityType thisEntity = entities.SingleOrDefault(x => x.ClrType.Name == typeof(T).Name);
IQueryable<T> set = _dbContext.Set<T>();
foreach (IForeignKey fKey in thisEntity.GetReferencingForeignKeys())
{
set = set.Include(fKey.PrincipalToDependent.Name);
}
return set;
}
我有一个提供商 class 和一个文章。文章有一个 int ProviderId{get;set}
和一个 public virtual Provider Provider {get;set;}
属性。
我知道延迟加载以及为什么我无法在上下文之外访问 Article 中的 Provider 属性 但我有一个通用方法 returns 下一个 T 如下所示:
public static T Next<T>(T currentElement) where T : class, IModel {
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities()) {
data = context.Set<T>().Single(el => el.Id == currentElement.Id + 1);
}
return data;
}
但是我无法在文章 Class 中检索像 Provider 这样的子实体。如何包含所有实体?我应该更新方法并为每个实体制作一个吗?
我读到 Eager loading and Explicit loading 但我不知道如何在我的方法中实现这些。
注:
并非我所有的实体都有子实体,我有更多方法,如 Previous<T>()
、First<T>()
或 Last<T>()
可以完成您期望的工作。
您可以扩展您的方法以获取可能包含的列表。例如:
public static T Next<T>(T currentElement, params Expression<Func<T, object>>[] includes)
where T : class, IModel
{
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
IQueryable<T> query = context.Set<T>();
//Deferred execution allows us to build up the query
foreach (var include in includes)
{
query = query.Include(include);
}
return query.Single(el => el.Id == currentElement.Id + 1);
}
}
并像这样使用它:
var someEntity = ...;
var nextEntity = Next(someEntity,
e => e.Childcollection1,
e => e.Childcollection2,
//
e => e.ChildcollectionN)
另外一点,要获得下一个实体,您不应依赖 ID 值的顺序,例如,如果条目被删除,它们将乱序。相反,考虑这样的事情:
return query.OrderBy(el => el.Id).First(el => el.Id > currentElement.Id);
您可以为接受 Expression<Func<T, object>>
:
Next
方法创建重载
public static T Next<T>(T currentElement, Expression<Func<T, object>> navigationProperty) where T : class, IModel
{
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
IQueryable<T> dbQuery = context.Set<T>();
if (navigationProperty != null)
dbQuery = dbQuery.Include<T, object>(navigationProperty);
data = dbQuery.AsNoTracking().Single(el => el.Id == currentElement.Id + 1);
}
return data;
}
用法:
var entity = Next(instance, x => x.Provider);
有关详细信息和完整示例,请参阅以下博客 post。
使用 Entity Framework 实现通用数据访问层: https://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/
所以你在 Provider
和 Article
之间有一个一对多的关系:每个 Provider
有零个或多个 Articles
,并且每个 Article
正好属于一个 Provider
.
如果您在 Entity Framework 中正确配置了这种一对多关系,Provider 应该会引用其多个 Articles
:
public virtual ICollection<Article> Articles{get; set;}
此外,您还有一个函数 Next
,它将 class T
的对象作为输入,该对象是实现 IModel
的 class。 Next
returns 来自 Ts 的 DbSet 的下一个元素,假设您对下一个元素有正确的定义。
显然您想调整函数 Next 以便您可以使用此函数获取下一个 Provider
及其所有 Articles
.
更通用:如果T是一个在其中设计了适当的一对多关系的类型,并且你有一个T类型的对象,你希望T的第一个Id高于当前的Id T,包括它的所有 ICollections。
我把它分成几个更小的功能:
- 一个函数,给定类型 T returns 所有实现 ICollection 的属性
- 一个函数,给定一个 DbSet returns DbSet 的所有元素包括它的所有 ICollections
鉴于这两个,以及像 OrderBy
和 FirstOrDefault
这样的函数,您将能够获得 ID 大于当前提供者及其所有文章的第一个提供者。
static class QueryableExtensions
{
// returns true if PropertyInfo implements ICollection<...>
public static bool ImplementsICollection(this PropertyInfo propertyInfo)
{
return propertyInfo.IsGenericType
&& propertyInfo.GetGenericTypeDefinition() == typeof(ICollection<>);
}
// returns all readable & writable PropertyInfos of type T that implement ICollection<...>
public static IEnumerable<PropertyInfo> CollectionProperties<T>()
{
return typeof(T).GetProperties()
.Where(prop => prop.CanRead
&& prop.CanWrite
&& prop.PropertyType.ImplementICollection();
}
// given an IQueryable<T>, adds the Include of all ICollection<...> T has
public static IQueryable<T> IncludeICollection<T>(IQueryable<T> query)
{
var iCollectionProperties = CollectionProperties<T>();
foreach (var collectionProperty in collectionProperties)
{
query = query.Include(prop.Name);
}
return query;
}
// given a IQueryable<T> and a T return the first T in the query with Id higher than T
// for this we need to be sure every T has an IComparable property Id
// so T should implement interface IId (see below)
public T Next<T>(this IQueryable<T> query, T currentItem)
where T : IId // makes sure every T has aproperty Id
{
T nextElement = query
// keep only those DbSet items that have larger Ids:
.Where(item => currentItem.Id < item.Id)
// sort in ascending Id:
.OrderBy(item => item.Id
// keep only the first element of this sorted collection:
.FirstOrDefault();
return T
}
}
我需要确保每个T都有一个Id,否则你获取不到下一个Id。可能你的 IModel 界面中有这个:
public Interface IId
{
public int Id {get;}
}
使用这些函数后,您的查询将如下所示:
public static T Next<T>(T currentElement) where T : class, IModel, IId
{
T data;
if (currentElement.Id >= GetLastId<T>())
return currentElement;
using (DatabaseEntities context = new DatabaseEntities())
{
return context.Set<T>().Next(currentElement);
}
}
我不想重复一个答案,但这里有一个函数结合了一点反射,可以根据模型声明自动加载所有与外键相关的数据。我将其编写为一种通过主键以外的方式加载对象的方法,但如果需要,可以对其进行修改。
private IQueryable<T> GetFullSet()
{
IEnumerable<IEntityType> entities = _dbContext.Model.GetEntityTypes();
IEntityType thisEntity = entities.SingleOrDefault(x => x.ClrType.Name == typeof(T).Name);
IQueryable<T> set = _dbContext.Set<T>();
foreach (IForeignKey fKey in thisEntity.GetReferencingForeignKeys())
{
set = set.Include(fKey.PrincipalToDependent.Name);
}
return set;
}