20

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

class Base {}

class Left : Base {}

class Right : Base {}

我打电话给DBContext.Bases.ToList();

这会将所有完全类型化的实例返回Base到它们关联的继承类型中,正如一些人所注意到的,EF 在大型继承结构上的性能至少可以说不是很好。我在项目中的实际查询是 600 行,仅用于返回一个实体,生成需要 2 秒。

如果您告诉它要返回哪种类型,它们的查询运行速度会更快,因为它不必连接整个结构。例如

DBContext.Bases.OfType<Left>.ToList();
or
DBContext.Bases.OfType<Right>.ToList();

但是我现在只想返回基类。不幸的事

DBContext.Bases.OfType<Base>.ToList(); 

与 DBContext.Bases.ToList(); 一样

它获得了整个继承结构......有没有办法(没有在 EF 中创建新类型)在查看 Base 集合时只返回类 Base ?


对不起,我无法登录我的实际帐户...

也许我没有说清楚,我想带回所有对象(包括 Base、Left 和 Right),但我只想返回 Base 类,即使在数据库中它们是实际的 Left 和 Right 类。

OFTYPE 是一个很好的建议,但它过滤掉了我所有的实体,因为没有一个是实际的 Base 类型。但我只想返回 Base 类型对象中的 Base 类型值。

有任何想法吗?

4

6 回答 6

6

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 的提示 35

于 2014-05-29T16:16:52.743 回答
2

To answer the question that none of the above answers seem to take care of (that is, we are only filtering returned columns to only be the base type columns, but not filtering out the rows that have derived type information), there is a fairly straightforward way of doing this with anonymous types. See here for another stackoverflow question dealing with the specifics.

The idea is to do something like this:

db.BaseTypes.Select(o => new { Prop1 = o.Prop1, Prop2 = o.Prop2, ....})
.AsEnumerable()
.Select(a => new BaseType() { Prop1 = a.Prop1, Prop2 = a.Prop2, ...});

The Linq-to-Entities will return a list of anonymous objects, while the .AsEnumerable() returns you back to Linq-to-Objects and allows you to call new BaseType() with an object initializer list.

This has an unfortunate downside of being specific to the types. Somebody here at the office wants a generic one written, so I'll return soon and edit this answer with a fully generic version of this.

EDIT (tested, but not in production EntityFramework):

Thanks to this answer for the SelectDynamic code.

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));
        }
    }
}
于 2015-08-10T22:05:32.990 回答
1

假设您能够使用 LINQ,您可以使用以下快速而肮脏的示例中的内容吗?:

var result = from item in DBContext.Bases.ToList()
            where (!item.GetType().IsSubclassOf(typeof(Base)))
           select item;
于 2010-01-13T13:00:10.667 回答
0

不确定性能差异,但我可以想象这会比加载所有行更快(当数据库中有很多行时):

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();
于 2017-08-10T13:19:38.053 回答
-1

您可以使用DbSet.SqlQuery

DBContext.Bases.SqlQuery("select * from BaseTable").AsNoTracking().ToList();

请注意,不使用.AsNoTracking()迟早会让您陷入困境(如果已经将派生类型加载到上下文中,您将立即获得唯一的键违规/异常)。

于 2015-09-29T15:02:47.293 回答
-2

我目前使用以下 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>();
于 2013-12-29T20:58:12.300 回答