简体   繁体   English

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

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

I have a Provider class and an Article one.我有一个 Provider 类和一个 Article one。 Article has a int ProviderId{get;set} and a public virtual Provider Provider {get;set;} properties.文章有一个int ProviderId{get;set}和一个public virtual Provider Provider {get;set;}属性。

I know about Lazy loading and why I can't access the Provider property in Article outside the context but I have a generic method that returns the next T like this:我知道延迟加载以及为什么我无法在上下文之外访问 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;
}

But I can't retrieve Child entities like Provider in Articles Class.但是我无法在文章类中检索像 Provider 这样的子实体。 How can I include all entities?如何包含所有实体? Should I update the method and make one per entity?我应该更新方法并为每个实体创建一个吗?

I read about Eager loading and Explicit loading but I don't know how can I implement these in my method.我读过Eager loadingExplicit loading ,但我不知道如何在我的方法中实现这些。

Note:笔记:

Not all my entities have entity children and I have more methods like Previous<T>() , First<T>() or Last<T>() that do the work you expect.并非我的所有实体都有实体子级,而且我有更多方法,例如Previous<T>()First<T>()Last<T>()可以完成您期望的工作。

You could create an overload of your Next method that accepts an Expression<Func<T, object>> :您可以创建接受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;
}

Usage:用法:

var entity = Next(instance, x => x.Provider);

Please refer to the following blog post for more information and a complete example.有关更多信息和完整示例,请参阅以下博客文章。

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

You could extend your method to take a list of possible includes.您可以扩展您的方法以获取可能包含的列表。 For example:例如:

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

And use it like this:并像这样使用它:

var someEntity = ...;

var nextEntity = Next(someEntity, 
    e => e.Childcollection1,
    e => e.Childcollection2,
    //
    e => e.ChildcollectionN)

As an additional point, to get your next entity, you shouldn't rely on the ID values being sequential, for example, they will get out of sequence if entries are deleted.另外一点,要获得下一个实体,您不应该依赖于顺序的 ID 值,例如,如果条目被删除,它们将失去顺序。 Instead, consider something like this:相反,考虑这样的事情:

return query.OrderBy(el => el.Id).First(el => el.Id > currentElement.Id);

So you have a one-to-many relation between Provider and Article : Every Provider has zero or more Articles , and every Article belongs to exactly one Provider .所以你有一个ProviderArticle之间的一对多关系:每个Provider有零个或多个Articles ,并且每个Article都只属于一个Provider

If you have configured this one-to-many relationship correctly in Entity Framework, a Provider should have a reference to its many Articles :如果您在 Entity Framework 中正确配置了这种一对多关系,则提供者应该引用其许多Articles

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

Furthermore you have a function Next , that takes as input an object of class T , which is a class that implements IModel .此外,您有一个函数Next ,它将类T的对象作为输入,该类是实现IModel的类。 Next returns the next element from the DbSet of Ts, assuming you have a proper definition for the next element. Next返回 Ts 的 DbSet 中的下一个元素,假设您对下一个元素有正确的定义。

Apparently you want to adapt function Next such that you can use this function to get the next Provider with all its Articles .显然,您想调整 Next 函数,以便您可以使用此函数来获取下一个Provider及其所有Articles

More generic: if T is a type that has properly designed one-to-many relationships in it, and you have an object of type T, you want the T with the first Id higher than the Id of the current T, inclusive all its ICollections.更通用:如果 T 是一种在其中正确设计了一对多关系的类型,并且您有一个类型为 T 的对象,则您希望 T 的第一个 Id 高于当前 T 的 Id,包括它的所有收藏。

I've divided this into sever smaller functions:我已将其划分为更小的功能:

  • A function that, given a type T returns all properties that implement ICollection一个函数,给定类型 T 返回实现 ICollection 的所有属性
  • A function that, given a DbSet returns all elements of the DbSet inclusive all its ICollections一个函数,给定一个 DbSet 返回 DbSet 的所有元素,包括它的所有 ICollections

Given those two, and functions like OrderBy and FirstOrDefault you will be able to get the first Provider with Id larger than current provider with all its Articles.鉴于这两个,以及像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
    }
}

I need to be sure that every T has an Id, otherwise you can't get the next Id.我需要确保每个T都有一个Id,否则你无法获得下一个Id。 Probably you have this in your IModel interface:可能你的 IModel 界面中有这个:

public Interface IId
{
     public int Id {get;}
}

After these functions your query will be like:在这些功能之后,您的查询将类似于:

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

I'm not looking to rehash an answer, but here is a function that combines a little bit of reflection to automagically load all foreign key related data based on model declarations.我不打算重新回答一个答案,但这里有一个函数,它结合了一点反射来根据模型声明自动加载所有与外键相关的数据。 I've written it as a way to load an object by something other than its primary key, but it can be modified to do so if needed.我已将其编写为通过主键以外的其他内容加载对象的方法,但可以根据需要对其进行修改以执行此操作。

 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