EntityFramework:急切加载而不是包含?

时间:2017-05-03 07:39:47

标签: c# entity-framework eager-loading

我的数据模型有很多嵌套实体,我想急切地加载整个对象树...除了要显式加载的视图实体外一经请求。

使用包含路径我必须指定许多路径,每次添加新实体时,我都必须调整这些包含路径。我目前使用我的存储库的以下方法来加载类型的所有实体:

public virtual IQueryable<TEntity> All(string commaSeperatedIncludePropertyPaths = "")
    {
      IQueryable<TEntity> initialQuery = Context.Set<TEntity>();
      string[] includePaths = commaSeperatedIncludePropertyPaths.Split(new[] { ','}, StringSplitOptions.RemoveEmptyEntries);    
      return includePaths.Aggregate(initialQuery, (currentQuery, includeProperty) => currentQuery.Include(includeProperty));
    }

传递的包含路径已经填满整个屏幕。

因此,我希望EntityFramework 自动加载所有导航属性,但我使用排除路径指定的属性除外:

public virtual IQueryable<TEntity> All(string commaSeperatedExcludePropertyPaths = "")
    {
      //... how to implement?
    }

排除路径有助于避免循环依赖,并过滤掉我不想急切加载的少数实体。指定排除而不是包含会减少我的样板代码。

这可以通过EF 6.1.3实现还是计划用于EF 7?如果没有,那么该选项的原因是什么?

是否有人已尝试阅读实体元数据并将其应用于&#34;自动预测加载&#34;并且失败了?

相关(旧)问题和文章:

  • 加载导航属性选项概述:

https://msdn.microsoft.com/en-us/magazine/hh205756.aspx

  • 自动加载

Entity framework auto eager load

Entity Framework - Is there a way to automatically eager-load child entities without Include()?

Entity framework linq query Include() multiple children entities

  • 类型保存包含

Entity Framework .Include() with compile time checking?

1 个答案:

答案 0 :(得分:1)

以下是解决方案的初稿。我仍然需要找出它是否切实可行......而且我会考虑重新加载加载方法(正如Lanorkin建议的那样)。谢谢您的意见。

修改

事实证明,虽然排除在开发应用程序时可能有意义......对域模型进行了许多更改...,排除并不比我刚才考虑的“真实世界示例”包含更优雅。

a)我浏览了我的实体并计算了包含和排除的导航属性的数量。排除的属性的平均数量并不比包含的属性数量小得多。

b)如果我确实考虑了排除的不同导航属性“foos”,我将被迫考虑Foo类型的子实体的排除...如果我根本不想使用它的属性。< / p>

另一方面,使用包含,我只需要指定导航属性“foos”,而不需要为子实体指定任何其他内容。

因此,虽然排除可能会保留一个级别的某些规格,但它们会在下一级别需要更多规格...(当排除某些中间实体而不仅仅是位于加载的对象树的叶子上的实体时) 。

c)此外,包含/排除可能不仅取决于实体的类型,还取决于用于访问实体的路径。然后需要指定一个排除,例如“在为一个目的加载实体时排除属性xy,并在为另一个目的加载实体时排除属性z”。

=&GT; 由于这些考虑,我将继续使用包含

我实现了基于包含词典而不是字符串的类型保存包含:

  private static readonly Inclusions<Person> _personInclusionsWithCompanyParent = new Inclusions<Person>(typeof(Company))
      {
        {e => e.Company, false},
        {e => e.Roles, true}        
      };

我有一个方法可以从包含列表中创建查询。该方法还检查字典中是否考虑了所有现有导航属性。如果我添加一个新实体并忘记指定相应的包含,则会抛出异常。

尽管如此,这是一个使用排除而不是包含的实验解决方案:

private const int MAX_EXPANSION_DEPTH = 10;

private DbContext Context { get; set; } //set during construction of my repository


 public virtual IQueryable<TEntity> AllExcluding(string excludeProperties = "")
    {
      var propertiesToExclude = excludeProperties.Split(new[]
                                                        {
                                                          ','
                                                        },
                                                        StringSplitOptions.RemoveEmptyEntries);


      IQueryable<TEntity> initialQuery = Context.Set<TEntity>();
      var elementType = initialQuery.ElementType;

      var navigationPropertyPaths = new HashSet<string>();
      var navigationPropertyNames = GetNavigationPropertyNames(elementType);
      foreach (var propertyName in navigationPropertyNames)
      {
        if (!propertiesToExclude.Contains(propertyName))
        {
          ExtendNavigationPropertyPaths(navigationPropertyPaths, elementType, propertyName, propertyName, propertiesToExclude, 0);
        }
      }

      return navigationPropertyPaths.Aggregate(initialQuery, (current, includeProperty) => current.Include(includeProperty));
    }

    private void ExtendNavigationPropertyPaths(ISet<string> navigationPropertyPaths,
                                               Type parentType,
                                               string propertyName,
                                               string propertyPath,
                                               ICollection<string> propertiesToExclude,
                                               int expansionDepth)
    {
      if (expansionDepth > MAX_EXPANSION_DEPTH)
      {
        return;
      }

      var propertyInfo = parentType.GetProperty(propertyName);

      var propertyType = propertyInfo.PropertyType;

      var isEnumerable = typeof(IEnumerable).IsAssignableFrom(propertyType);
      if (isEnumerable)
      {
        propertyType = propertyType.GenericTypeArguments[0];
      }

      var subNavigationPropertyNames = GetNavigationPropertyNames(propertyType);
      var noSubNavigationPropertiesExist = !subNavigationPropertyNames.Any();
      if (noSubNavigationPropertiesExist)
      {
        navigationPropertyPaths.Add(propertyPath);
        return;
      }

      foreach (var subPropertyName in subNavigationPropertyNames)
      {
        if (propertiesToExclude.Contains(subPropertyName))
        {
          navigationPropertyPaths.Add(propertyPath);
          continue;
        }

        var subPropertyPath = propertyPath + '.' + subPropertyName;
        ExtendNavigationPropertyPaths(navigationPropertyPaths,
                                      propertyType,
                                      subPropertyName,
                                      subPropertyPath,
                                      propertiesToExclude,
                                      expansionDepth + 1);
      }
    }

    private ICollection<string> GetNavigationPropertyNames(Type elementType)
    {
      var objectContext = ((IObjectContextAdapter)Context).ObjectContext;
      var entityContainer = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
      var entitySet = entityContainer.EntitySets.FirstOrDefault(item => item.ElementType.Name.Equals(elementType.Name));
      if (entitySet == null)
      {
        return new List<string>();
      }
      var entityType = entitySet.ElementType;
      return entityType.NavigationProperties.Select(np => np.Name)
                       .ToList();
    }