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();
}
我的数据模型有很多嵌套实体,我想急切地加载整个对象树...除了将按需显式加载的视图实体。
使用包含路径 我必须指定许多路径,每次添加新实体时我都必须调整这些包含路径。我目前使用我的存储库的以下方法来加载一个类型的所有实体:
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();
}