有没有办法使用 dbcontext 来包含()所有内容?
Is there a way to Include() all with dbcontext?
使用预先加载查询 DbContext
时,需要 Include("Navigation")
才能填充导航属性。但是在某些情况下,我想简单地 Include
all 实体的导航属性。有没有办法,或者有办法做到这一点?我假设你可以反思,但我宁愿避免这种情况。
我知道的:
var entity = db.Table.Include("Navigation1").Include("Navigation2").First();
我想要的:
var entity = db.Table.IncludeAll().First();
没有。那没有。 Entity Framework 有意 使您明确表示要急切加载的内容,因为添加连接会使您的查询变得更重和更慢。这是为了保护您免受自己的伤害。如果您需要连接,那很好,但至少当您明确指定它们时,您至少会确切知道发生了多少以及为什么。
一个简单的选择是使用反射来检查虚拟属性。
public static IQueryable<T> IncludeAlla<T>(this IQueryable<T> queryable) where T : class
{
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
var isVirtual = property.GetGetMethod().IsVirtual;
if (isVirtual)
{
queryable = queryable.Include(property.Name);
}
}
return queryable;
}
无论 Entity Framework 的设计者怎么想,我发现 是 合法的 use-case 用于递归地、急切地加载所有数据库项目:创建可以通过自动化测试轻松恢复的数据库内容的快照。如果那是您所追求的,您可能会发现 this article 以及此扩展方法非常有趣:
public static class EfExtensions
{
public static IQueryable<TEntity> IncludeAllRecursively<TEntity>(this IQueryable<TEntity> queryable,
int maxDepth = int.MaxValue, bool addSeenTypesToIgnoreList = true, HashSet<Type>? ignoreTypes = null)
where TEntity : class
{
var type = typeof(TEntity);
var includes = new List<string>();
ignoreTypes ??= new HashSet<Type>();
GetIncludeTypes(ref includes, prefix: string.Empty, type, ref ignoreTypes, addSeenTypesToIgnoreList, maxDepth);
foreach (var include in includes)
{
queryable = queryable.Include(include);
}
return queryable;
}
private static void GetIncludeTypes(ref List<string> includes, string prefix, Type type, ref HashSet<Type> ignoreSubTypes,
bool addSeenTypesToIgnoreList = true, int maxDepth = int.MaxValue)
{
var properties = type.GetProperties();
foreach (var property in properties)
{
var getter = property.GetGetMethod();
if (getter != null)
{
var isVirtual = getter.IsVirtual;
if (isVirtual)
{
var propPath = (prefix + "." + property.Name).TrimStart('.');
if (maxDepth <= propPath.Count(c => c == '.')) { break; }
includes.Add(propPath);
var subType = property.PropertyType;
if (ignoreSubTypes.Contains(subType))
{
continue;
}
else if (addSeenTypesToIgnoreList)
{
// add each type that we have processed to ignore list to prevent recursions
ignoreSubTypes.Add(type);
}
var isEnumerableType = subType.GetInterface(nameof(IEnumerable)) != null;
var genericArgs = subType.GetGenericArguments();
if (isEnumerableType && genericArgs.Length == 1)
{
// sub property is collection, use collection type and drill down
var subTypeCollection = genericArgs[0];
if (subTypeCollection != null)
{
GetIncludeTypes(ref includes, propPath, subTypeCollection, ref ignoreSubTypes, addSeenTypesToIgnoreList, maxDepth);
}
}
else
{
// sub property is no collection, drill down directly
GetIncludeTypes(ref includes, propPath, subType, ref ignoreSubTypes, addSeenTypesToIgnoreList, maxDepth);
}
}
}
}
}
}
注意:在遍历数据库项时避免循环是必不可少的。默认情况下,它是使用忽略列表 ignoreSubTypes
完成的:添加每个看到的类型。这可能并不总是有效(例如,当项目“信件”包含类型为 Person
的“发件人”和“收件人”时)。在这种情况下,您可以尝试使用 maxDepth
。祝你好运,但不要自杀!
使用预先加载查询 DbContext
时,需要 Include("Navigation")
才能填充导航属性。但是在某些情况下,我想简单地 Include
all 实体的导航属性。有没有办法,或者有办法做到这一点?我假设你可以反思,但我宁愿避免这种情况。
我知道的:
var entity = db.Table.Include("Navigation1").Include("Navigation2").First();
我想要的:
var entity = db.Table.IncludeAll().First();
没有。那没有。 Entity Framework 有意 使您明确表示要急切加载的内容,因为添加连接会使您的查询变得更重和更慢。这是为了保护您免受自己的伤害。如果您需要连接,那很好,但至少当您明确指定它们时,您至少会确切知道发生了多少以及为什么。
一个简单的选择是使用反射来检查虚拟属性。
public static IQueryable<T> IncludeAlla<T>(this IQueryable<T> queryable) where T : class
{
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
var isVirtual = property.GetGetMethod().IsVirtual;
if (isVirtual)
{
queryable = queryable.Include(property.Name);
}
}
return queryable;
}
无论 Entity Framework 的设计者怎么想,我发现 是 合法的 use-case 用于递归地、急切地加载所有数据库项目:创建可以通过自动化测试轻松恢复的数据库内容的快照。如果那是您所追求的,您可能会发现 this article 以及此扩展方法非常有趣:
public static class EfExtensions
{
public static IQueryable<TEntity> IncludeAllRecursively<TEntity>(this IQueryable<TEntity> queryable,
int maxDepth = int.MaxValue, bool addSeenTypesToIgnoreList = true, HashSet<Type>? ignoreTypes = null)
where TEntity : class
{
var type = typeof(TEntity);
var includes = new List<string>();
ignoreTypes ??= new HashSet<Type>();
GetIncludeTypes(ref includes, prefix: string.Empty, type, ref ignoreTypes, addSeenTypesToIgnoreList, maxDepth);
foreach (var include in includes)
{
queryable = queryable.Include(include);
}
return queryable;
}
private static void GetIncludeTypes(ref List<string> includes, string prefix, Type type, ref HashSet<Type> ignoreSubTypes,
bool addSeenTypesToIgnoreList = true, int maxDepth = int.MaxValue)
{
var properties = type.GetProperties();
foreach (var property in properties)
{
var getter = property.GetGetMethod();
if (getter != null)
{
var isVirtual = getter.IsVirtual;
if (isVirtual)
{
var propPath = (prefix + "." + property.Name).TrimStart('.');
if (maxDepth <= propPath.Count(c => c == '.')) { break; }
includes.Add(propPath);
var subType = property.PropertyType;
if (ignoreSubTypes.Contains(subType))
{
continue;
}
else if (addSeenTypesToIgnoreList)
{
// add each type that we have processed to ignore list to prevent recursions
ignoreSubTypes.Add(type);
}
var isEnumerableType = subType.GetInterface(nameof(IEnumerable)) != null;
var genericArgs = subType.GetGenericArguments();
if (isEnumerableType && genericArgs.Length == 1)
{
// sub property is collection, use collection type and drill down
var subTypeCollection = genericArgs[0];
if (subTypeCollection != null)
{
GetIncludeTypes(ref includes, propPath, subTypeCollection, ref ignoreSubTypes, addSeenTypesToIgnoreList, maxDepth);
}
}
else
{
// sub property is no collection, drill down directly
GetIncludeTypes(ref includes, propPath, subType, ref ignoreSubTypes, addSeenTypesToIgnoreList, maxDepth);
}
}
}
}
}
}
注意:在遍历数据库项时避免循环是必不可少的。默认情况下,它是使用忽略列表 ignoreSubTypes
完成的:添加每个看到的类型。这可能并不总是有效(例如,当项目“信件”包含类型为 Person
的“发件人”和“收件人”时)。在这种情况下,您可以尝试使用 maxDepth
。祝你好运,但不要自杀!