如果我在实体框架中有三个类。
class Base {}
class Left : Base {}
class Right : Base {}
我拨打DBContext.Bases.ToList();
这会将Base
完全键入的所有实例返回到其关联的继承类型中,正如有些人已经注意到的那样,EF对大型继承结构的性能至少可以说不是很好。我在项目中的实际查询长度为600行,仅用于返回一个实体,需要2秒才能生成。
如果您告诉它返回哪种类型,它们的查询运行速度要快得多,因为它不必连接整个结构。 e.g。
DBContext.Bases.OfType<Left>.ToList();
or
DBContext.Bases.OfType<Right>.ToList();
但是我现在想 ONLY 返回基类。不幸做了
DBContext.Bases.OfType<Base>.ToList();
也一样 DBContext.Bases.ToList();
它获得了WHOLE继承结构......在查看Base集合时, ONLY 返回类Base是否有任何方法(不在EF中创建新类型)?
抱歉,我无法登录我的实际帐户...
也许我没有说清楚,我想要带回所有对象(包括Base,Left和Right)但我只想返回Base类,即使在数据库中它们是实际的Left和Right类。
OFTYPE是一个很好的建议,但它过滤掉了我的所有实体,因为没有一个是实际的Base类型。但我想只返回Base类型对象中的Base类型值。
有什么想法吗?
答案 0 :(得分:3)
实体框架无法理解GetType()
,但关键字is
确实有效。因此,您可以构建表达式并将其应用于您的查询。这里的代码应该适用于EF5 +,以添加一个可以调用的扩展方法:query.OfOnlyType<Base, SubTypeWithDescendants>()
。 (或者,如果需要,可以使用相同的两个Type参数,但我的层次结构比这更复杂)
public static IQueryable<ReturnType> OfOnlyType<ReturnType, QueryType>
(this IQueryable<QueryType> query)
where ReturnType : QueryType {
// Look just for immediate subclasses as that will be enough to remove
// any generations below
var subTypes = typeof(ReturnType).Assembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(ReturnType)));
if (subTypes.Count() == 0) { return query.OfType<ReturnType>(); }
// Start with a parameter of the type of the query
var parameter = Expression.Parameter(typeof(ReturnType));
// Build up an expression excluding all the sub-types
Expression removeAllSubTypes = null;
foreach (var subType in subTypes) {
// For each sub-type, add a clause to make sure that the parameter is
// not of this type
var removeThisSubType = Expression.Not(Expression
.TypeIs(parameter, subType));
// Merge with the previous expressions
if (removeAllSubTypes == null) {
removeAllSubTypes = removeThisSubType;
} else {
removeAllSubTypes = Expression
.AndAlso(removeAllSubTypes, removeThisSubType);
}
}
// Convert to a lambda (actually pass the parameter in)
var removeAllSubTypesLambda = Expression
.Lambda(removeAllSubTypes, parameter);
// Filter the query
return query
.OfType<ReturnType>()
.Where(removeAllSubTypesLambda as Expression<Func<ReturnType, bool>>);
}
我只使用代码优先模型在EF6.1上进行了测试。它大量借鉴Alex James' tip 35。
答案 1 :(得分:1)
假设您能够使用LINQ,您是否可以使用以下快速而肮脏的示例中的内容?:
var result = from item in DBContext.Bases.ToList()
where (!item.GetType().IsSubclassOf(typeof(Base)))
select item;
答案 2 :(得分:1)
回答上述答案似乎都没有解决的问题(也就是说,我们只是将返回的列过滤为基本类型列,但不过滤掉具有派生类型信息的行),这是一种使用匿名类型执行此操作的相当简单的方法。有关具体细节的另一个stackoverflow问题,请参阅here。
这个想法是做这样的事情:
db.BaseTypes.Select(o => new { Prop1 = o.Prop1, Prop2 = o.Prop2, ....})
.AsEnumerable()
.Select(a => new BaseType() { Prop1 = a.Prop1, Prop2 = a.Prop2, ...});
Linq-to-Entities将返回一个匿名对象列表,而.AsEnumerable()
将返回Linq-to-Objects,并允许您使用对象初始化列表调用new BaseType()
。< / p>
这具有针对类型的特定的不幸缺点。办公室的某个人想要写一个通用的,所以我很快就会回来并用完全通用的版本来编辑这个答案。
编辑(已测试,但未在制作中进行EntityFramework):
感谢SelectDynamic代码的this answer。
public static class QueryableExtensions {
/// <summary>
/// Constructs a query that only selects the columns that are actually in the type <typeparamref name="T"/> as public properties.
///
/// Useful for inherited types when you only want the base type information.
/// </summary>
/// <remarks>
/// This function materializes the query. You'll want to call the where clauses BEFORE this call (since it is an optimization).
/// </remarks>
/// <typeparam name="T">Entity type.</typeparam>
/// <param name="query">Source query.</param>
/// <returns>An IEnumerable of items of type <typeparamref name="T"/>.</returns>
public static IEnumerable<T> FilterColumnsByType<T>(this IQueryable<T> query) where T : new() {
Type type = typeof(T);
List<string> selectedProps = type.GetProperties().Select(p => p.Name).ToList();
Tuple<IQueryable, Type> anonObjectTypePair = query.SelectDynamicAndType(selectedProps);
IQueryable anonObjects = anonObjectTypePair.Item1;
Type anonType = anonObjectTypePair.Item2;
return anonObjects.Cast<object>().AsEnumerable().Select(ob => {
var ret = new T();
selectedProps.ForEach(p =>
type.GetProperty(p).SetValue(ret, anonType.GetField(p).GetValue(ob)));
return ret;
});
}
/// <summary>
/// Constructs a query that selects only the <paramref name="propNames"/> given and returns an <see cref="IQueryable"/> of dynamic objects with only the selected fields.
///
/// Also returns the type information of the dynamic objects.
/// </summary>
/// <param name="source">Source query.</param>
/// <param name="propNames">The list of properties names to select.</param>
/// <returns>A query of anonymous types defined by the supplied <paramref name="propNames"/> and the actual <see cref="Type"/> used to construct anonymous type.</returns>
public static Tuple<IQueryable, Type> SelectDynamicAndType(this IQueryable source, IEnumerable<string> propNames) {
Dictionary<string, PropertyInfo> sourceProperties = propNames.ToDictionary(name => name, name => source.ElementType.GetProperty(name));
Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
ParameterExpression sourceItem = Expression.Parameter(source.ElementType, "t");
IEnumerable<MemberBinding> bindings = dynamicType.GetFields().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();
Expression selector = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
return Tuple.Create(source.Provider.CreateQuery(Expression.Call(typeof(Queryable), "Select", new Type[] { source.ElementType, dynamicType },
Expression.Constant(source), selector)), dynamicType);
}
/// <summary>
/// Constructs a query that selects only the <paramref name="propNames"/> given and returns an <see cref="IQueryable{dynamic}"/> of dynamic objects with only the selected fields.
/// </summary>
/// <param name="source">Source query.</param>
/// <param name="propNames">The list of properties names to select.</param>
/// <returns>A query of anonymous types defined by the supplied <paramref name="propNames"/>.</returns>
public static IQueryable<dynamic> SelectDynamic(this IQueryable source, IEnumerable<string> propNames) {
return source.SelectDynamicAndType(propNames).Item1.Cast<dynamic>();
}
static class LinqRuntimeTypeBuilder {
private static AssemblyName assemblyName = new AssemblyName() { Name = "DynamicLinqTypes" };
private static ModuleBuilder moduleBuilder = null;
private static Dictionary<string, Type> builtTypes = new Dictionary<string, Type>();
static LinqRuntimeTypeBuilder() {
moduleBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(assemblyName.Name);
}
private static string GetTypeKey(Dictionary<string, Type> fields) {
string key = string.Empty;
foreach (var field in fields.OrderBy(kvp => kvp.Key).ThenBy(kvp => kvp.Value.Name))
key += field.Key + ";" + field.Value.Name + ";";
return key;
}
private static Type GetDynamicType(Dictionary<string, Type> fields) {
if (null == fields)
throw new ArgumentNullException("fields");
if (0 == fields.Count)
throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");
try {
Monitor.Enter(builtTypes);
string className = GetTypeKey(fields);
if (builtTypes.ContainsKey(className))
return builtTypes[className];
TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
foreach (var field in fields)
typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);
builtTypes[className] = typeBuilder.CreateType();
return builtTypes[className];
} catch (Exception ex) {
//log.Error(ex);
Console.WriteLine(ex);
} finally {
Monitor.Exit(builtTypes);
}
return null;
}
public static Type GetDynamicType(IEnumerable<PropertyInfo> fields) {
return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
}
}
答案 3 :(得分:0)
我目前使用以下LINQ扩展,假设子类位于同一个程序集中。
public static class MyLinqExtensions
{
public static IQueryable<T> OfTypeOnly<T>(this IQueryable<T> query)
{
Type type = typeof (T);
IEnumerable<Type> derivedTypes = Assembly
.GetAssembly(type)
.GetTypes()
.Where(t => t.IsSubclassOf(type));
return query.ExceptTypes(derivedTypes.ToArray());
}
public static IQueryable<T> ExceptTypes<T>(this IQueryable<T> query, params Type[] excludedTypes)
{
if (excludedTypes == null)
return query;
return excludedTypes.Aggregate(query,
(current, excludedType) => current.Where(entity => entity.GetType() != excludedType));
}
}
用法:
var bases = DBContext.Bases.OfTypeOnly<Base>();
答案 4 :(得分:0)
您可以使用DbSet.SqlQuery
:
DBContext.Bases.SqlQuery("select * from BaseTable").AsNoTracking().ToList();
请注意,不使用.AsNoTracking()
迟早会让您陷入热水(如果已经加载到上下文中的派生类型,您将立即获得唯一的密钥违例/例外)。
答案 5 :(得分:0)
不确定性能差异,但我可以想象这比加载所有行更快(当很多行都在DB中时):
List<int> ids = DBContext.Rights.Select(x => x.Id).ToList();
ids.AddRange(DBContext.Lefts.Select(x => x.Id).ToList());
var bases = DBContext.Bases.Where(x => !ids.Contains(x.Id)).ToList();