【问题标题】:Entity Framework - Is there a way to automatically eager-load child entities without Include()?实体框架 - 有没有办法在没有 Include() 的情况下自动预加载子实体?
【发布时间】:2013-01-08 20:31:01
【问题描述】:

有没有一种方法可以装饰您的 POCO 类以自动预先加载子实体,而不必每次加载它们时都使用 Include()

假设我有一辆汽车,具有轮子、门、发动机、保险杠、窗户、排气等复杂类型的属性。在我的应用程序中,我需要从我的 DbContext 20 个不同的地方用不同的查询加载我的汽车,等等。我不想在每次要装载汽车时都指定要包含所有属性。

我想说

List<Car> cars = db.Car
                .Where(x => c.Make == "Ford").ToList();

//NOT .Include(x => x.Wheels).Include(x => x.Doors).Include(x => x.Engine).Include(x => x.Bumper).Include(x => x.Windows)


foreach(Car car in cars)
{

//I don't want a null reference here.

String myString = car.**Bumper**.Title;
}

我能否以某种方式装饰我的 POCO 类或我的 OnModelCreating() 或在 EF 中设置一个配置,告诉它在我装载汽车时只装载汽车的所有部件?我很想这样做,所以我的理解是让我的导航属性虚拟化已经过时了。我知道 NHibernate 支持类似的功能。

只是想知道我是否遗漏了什么。提前致谢!

干杯,

内森

我喜欢下面的解决方案,但我想知道是否可以嵌套对扩展方法的调用。例如,假设我有类似的 Engine 情况,它有很多我没有的部分想包括任何地方。我可以做这样的事情吗? (我还没有找到一种可行的方法)。这样,如果以后我发现Engine需要FuelInjectors,我可以只在BuildEngine中添加它,而不必在BuildCar中也添加它。 另外,如果我可以嵌套调用,如何嵌套调用到集合?想在 BuildCar() 中为每个轮子调用 BuildWheel() 吗?

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.Include(x => x.Wheels).BuildWheel()
                 .Include(x => x.Doors)
                 .Include(x => x.Engine).BuildEngine()
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

public static IQueryable<Engine> BuildEngine(this IQueryable<Engine> query) {
     return query.Include(x => x.Pistons)
                 .Include(x => x.Cylendars);
}

//Or to handle a collection e.g.
 public static IQueryable<Wheel> BuildWheel(this IQueryable<Wheel> query) {
     return query.Include(x => x.Rim)
                 .Include(x => x.Tire);
}

这是另一个非常相似的线程,以防在这种情况下对其他人有帮助,但它仍然无法处理对扩展方法的下一个调用。

Entity framework linq query Include() multiple children entities

【问题讨论】:

  • Nathan,非常有趣的挑战。请解释为什么您希望/需要避免使用 include() 等主动加载技术。
  • 不,你不能,但没有什么能阻止你在 DbContext 或存储库类中创建像 DbQuery&lt;Car&gt; CompleteCars { get { return ... (你的长 Include 链)这样的属性。如果可以的话,这不会切断从数据库中获取“裸车”的可能性吗?
  • 我想避免这种情况,因为我有一个复杂的模型,但记录很少,所以我希望数据能够快速加载,但不想错过应用程序中任何包含孙子的任何内容。

标签: entity-framework-4.1 entity code-first entity-framework-4.3


【解决方案1】:

不,你cannot do that in mapping。典型的解决方法是简单的扩展方法:

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.Include(x => x.Wheels)
                 .Include(x => x.Doors)
                 .Include(x => x.Engine)
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

现在,每次您想查询 Car 的所有关系时,您都会这样做:

var query = from car in db.Cars.BuildCar()
            where car.Make == "Ford"
            select car;

编辑:

您不能以这种方式嵌套调用。包含适用于您正在使用的核心实体 - 该实体定义了查询的形状,因此在您调用 Include(x =&gt; Wheels) 后,您仍在使用 IQueryable&lt;Car&gt;,并且您无法调用 IQueryable&lt;Engine&gt; 的扩展方法。您必须再次以Car 开头:

public static IQueryable<Car> BuildCarWheels(this IQuerable<Car> query) {
    // This also answers how to eager load nested collections 
    // Btw. only Select is supported - you cannot use Where, OrderBy or anything else
    return query.Include(x => x.Wheels.Select(y => y.Rim))
                .Include(x => x.Wheels.Select(y => y.Tire));
}

你会以这种方式使用该方法:

public static IQueryable<Car> BuildCar(this IQueryable<Car> query) {
     return query.BuildCarWheels()
                 .Include(x => x.Doors)
                 .Include(x => x.Engine)
                 .Include(x => x.Bumper)
                 .Include(x => x.Windows);
}

该用法不调用Include(x =&gt; x.Wheels),因为它应该在您请求急切加载其嵌套实体时自动添加。

当心这种复杂的急切加载结构所产生的复杂查询。这可能会导致性能很差,并且a lot of duplicate data transferred 来自数据库。

【讨论】:

  • 这正是我所需要的!谢谢!
  • 我可以嵌套调用这些子实体的扩展方法吗? (查看更新的问题)
  • 拥有 [Include] 属性可以让我投票! (见答案中的链接)
  • 那么这意味着每当我需要装载公共汽车、汽车、卡车等时,我仍然需要编写一个关于装载轮辋和轮胎的单独方法。我将它们包含在我的 LoadBus、LoadTruck、LoadCar 中方法,但我试图避免让那些方法需要知道关于提尔的孩子的任何事情。太糟糕了,但至少我有我的答案。谢谢!
  • 哪个对性能有好处lazy loadingeager loading 我可以从here 看到它的lazy loading,实际上我正在尝试搜索与其他表相关的产品,你怎么看?
【解决方案2】:

遇到同样的问题,看到另一个链接提到了Include 属性。我的解决方案假定您创建了一个名为IncludeAttribute 的属性。使用以下扩展方法和utility method

    public static IQueryable<T> LoadRelated<T>(this IQueryable<T> originalQuery)
    {
        Func<IQueryable<T>, IQueryable<T>> includeFunc = f => f;
        foreach (var prop in typeof(T).GetProperties()
            .Where(p => Attribute.IsDefined(p, typeof(IncludeAttribute))))
        {
            Func<IQueryable<T>, IQueryable<T>> chainedIncludeFunc = f => f.Include(prop.Name);
            includeFunc = Compose(includeFunc, chainedIncludeFunc);
        }
        return includeFunc(originalQuery);
    }

    private static Func<T, T> Compose<T>(Func<T, T> innerFunc, Func<T, T> outerFunc)
    {
        return arg => outerFunc(innerFunc(arg));
    }

【讨论】:

    【解决方案3】:

    如果您真的需要包含所有导航属性(您可能会遇到性能问题),您可以使用这段代码。
    如果您还需要急切地加载集合(即使是最糟糕的主意),您可以删除代码中的 continue 语句。
    上下文是您的上下文(如果您在上下文中插入此代码,则为这个)

        public IQueryable<T> IncludeAllNavigationProperties<T>(IQueryable<T> queryable)
        {
            if (queryable == null)
                throw new ArgumentNullException("queryable");
    
            ObjectContext objectContext = ((IObjectContextAdapter)Context).ObjectContext;
            var metadataWorkspace = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace();
    
            EntitySetMapping[] entitySetMappingCollection = metadataWorkspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace).Single().EntitySetMappings.ToArray();
    
            var entitySetMappings = entitySetMappingCollection.First(o => o.EntityTypeMappings.Select(e => e.EntityType.Name).Contains(typeof(T).Name));
    
            var entityTypeMapping = entitySetMappings.EntityTypeMappings[0];
    
            foreach (var navigationProperty in entityTypeMapping.EntityType.NavigationProperties)
            {
                PropertyInfo propertyInfo = typeof(T).GetProperty(navigationProperty.Name);
                if (propertyInfo == null)
                    throw new InvalidOperationException("propertyInfo == null");
                if (typeof(System.Collections.IEnumerable).IsAssignableFrom(propertyInfo.PropertyType))
                    continue;
    
                queryable = queryable.Include(navigationProperty.Name);
            }
    
            return queryable;
        }
    

    【讨论】:

      【解决方案4】:

      进一步研究 Lunyx 的回答,我添加了一个重载以使其与字符串集合一起使用(其中字符串是属性名称,例如 "propertyA" 或 "propertyA.propertyOfAA"):

        public static IQueryable<T> LoadRelated<T>(this IQueryable<T> originalQuery, ICollection<string> includes)
          {
              if (includes == null || !includes.Any()) return originalQuery;
      
              Func<IQueryable<T>, IQueryable<T>> includeFunc = f => f;
              foreach (var include in includes)
              {
                  IQueryable<T> ChainedFunc(IQueryable<T> f) => f.Include(include);
                  includeFunc = Compose(includeFunc, ChainedFunc);
              }
      
              return includeFunc(originalQuery);
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-20
        • 2016-01-03
        • 2016-05-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多