繁体   English   中英

如何在实体框架 6 中动态加载子实体

[英]How can I load Child entities dynamically in Entity Framework 6

我有一个 Provider 类和一个 Article one。 文章有一个int ProviderId{get;set}和一个public virtual Provider Provider {get;set;}属性。

我知道延迟加载以及为什么我无法在上下文之外访问 Article 中的 Provider 属性,但我有一个通用方法可以像这样返回下一个 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;
}

但是我无法在文章类中检索像 Provider 这样的子实体。 如何包含所有实体? 我应该更新方法并为每个实体创建一个吗?

我读过Eager loadingExplicit loading ,但我不知道如何在我的方法中实现这些。

笔记:

并非我的所有实体都有实体子级,而且我有更多方法,例如Previous<T>()First<T>()Last<T>()可以完成您期望的工作。

您可以创建接受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);

有关更多信息和完整示例,请参阅以下博客文章。

使用实体框架实现通用数据访问层: https : //blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/

您可以扩展您的方法以获取可能包含的列表。 例如:

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);

所以你有一个ProviderArticle之间的一对多关系:每个Provider有零个或多个Articles ,并且每个Article都只属于一个Provider

如果您在 Entity Framework 中正确配置了这种一对多关系,则提供者应该引用其许多Articles

public virtual ICollection<Article> Articles{get; set;}

此外,您有一个函数Next ,它将类T的对象作为输入,该类是实现IModel的类。 Next返回 Ts 的 DbSet 中的下一个元素,假设您对下一个元素有正确的定义。

显然,您想调整 Next 函数,以便您可以使用此函数来获取下一个Provider及其所有Articles

更通用:如果 T 是一种在其中正确设计了一对多关系的类型,并且您有一个类型为 T 的对象,则您希望 T 的第一个 Id 高于当前 T 的 Id,包括它的所有收藏。

我已将其划分为更小的功能:

  • 一个函数,给定类型 T 返回实现 ICollection 的所有属性
  • 一个函数,给定一个 DbSet 返回 DbSet 的所有元素,包括它的所有 ICollections

鉴于这两个,以及像OrderByFirstOrDefault这样的函数,您将能够获得第一个 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;
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM