Получить только базовый класс из Entity Framework - PullRequest
14 голосов
/ 13 января 2010

Если у меня есть три класса в рамках сущности.

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), но я хочу, чтобы возвращался только базовый класс, даже если в базе данных они являются действительными классами Left и Right.

OFTYPE было хорошим предложением, но оно отфильтровывает все мои сущности, потому что ни один из них не является действительным базовым типом. Но я хочу вернуть только значения базового типа в объект базового типа.

Есть идеи?

Ответы [ 6 ]

3 голосов
/ 29 мая 2014

Entity Framework не понимает GetType(), но ключевое слово is работает. Таким образом, вы можете построить выражение и применить его к вашему запросу. Код здесь должен работать для EF5 +, чтобы добавить метод расширения, который вы можете вызвать как: query.OfOnlyType<Base, SubTypeWithDescendants>(). (Или с теми же двумя аргументами типа, если вам нужно, моя иерархия более сложна, чем эта)

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 с моделью с первым кодом. Он сильно заимствует у чаевых Алекса Джеймса 35 .

1 голос
/ 11 августа 2015

Чтобы ответить на вопрос, который, по-видимому, не отвечает ни один из приведенных выше ответов (то есть мы только фильтруем возвращаемые столбцы, чтобы они были столбцами только базового типа, но не отфильтровываем строки, которые имеют информацию о производном типе), это довольно простой способ сделать это с анонимными типами. См. здесь для другого вопроса о стековом потоке, касающемся специфики.

Идея состоит в том, чтобы сделать что-то вроде этого:

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() со списком инициализаторов объектов.

Это имеет неприятный недостаток - быть специфичным для типов. Кто-то здесь, в офисе, хочет, чтобы был написан общий текст, поэтому я скоро вернусь и отредактирую этот ответ с помощью полностью общей версии этого.

РЕДАКТИРОВАТЬ (проверено, но не в производстве EntityFramework):

Благодаря этот ответ для кода SelectDynamic.

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));
        }
    }
}
1 голос
/ 13 января 2010

Предполагая, что вы можете использовать LINQ, не могли бы вы использовать что-то вроде следующего быстрого и грязного примера?

var result = from item in DBContext.Bases.ToList()
            where (!item.GetType().IsSubclassOf(typeof(Base)))
           select item;
0 голосов
/ 10 августа 2017

Не уверен насчет различий в производительности, но я мог бы представить, что это будет быстрее, чем загрузка всех строк (когда в БД много строк):

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();
0 голосов
/ 29 сентября 2015

Вы можете использовать DbSet.SqlQuery:

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

Имейте в виду, что если вы не используете .AsNoTracking(), вы рано или поздно попадете в горячую воду (если уже есть производные типы, уже загруженные в контекст, вы немедленно получите уникальные ключевые нарушения / исключения).

0 голосов
/ 30 декабря 2013

В настоящее время я использую следующее расширение 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>();
...