EntityFramework:使用排除而不是包含进行预加载?

EntityFramework: Eager loading with excludes instead of includes?

我的数据模型有很多嵌套实体,我想急切地加载整个对象树...除了将按需显式加载的视图实体。

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

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 自动加载所有导航属性 ,除了我用 排除路径 [=57] 指定的那些=]:

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

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

这在 EF 6.1.3 中可行还是计划在 EF 7 中实现?如果不是,反对该选项的理由是什么?

是否有人已经尝试读取实体 元数据 并将其应用于 "auto eager loading" 但失败了?

相关(旧)问题和文章:

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

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?

以下是解决方案的初稿。我仍然需要弄清楚它是否可行……而且我也会考虑重新设计加载方法(正如 Lanorkin 所建议的那样)。谢谢您的意见。

编辑

事实证明,虽然在开发应用程序时排除可能有意义...对域模型进行许多更改...,但排除并不比包含 "real world example" 更优雅,我只是经过考虑的。

a) 我检查了我的实体并计算了包含和排除的导航属性的数量。排除的属性的平均数量并不明显小于包含的属性的数量。

b) 如果我确实考虑了排除项的不同导航 属性 "foos",我将被迫考虑排除 Foo 类型的子实体 ... 如果我不想完全使用它的属性。

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

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

c) 此外,includes/excludes 可能不仅取决于实体的类型,还取决于用于访问它的路径。然后需要指定一个排除项,如 "exclude properties xy when loading the entity for one purpose and exclude properties z when loading the entity for another purpose".

=> 出于这些考虑,我将继续使用包含物

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

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