仅从Entity Framework中检索基类

时间:2010-01-13 11:02:05

标签: c# entity-framework linq-to-entities entity-framework-4

如果我在实体框架中有三个类。

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类型值。

有什么想法吗?

6 个答案:

答案 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();