2010-01-13 2 views
13

Если у меня есть три класса в структуре сущности.Извлечь только базовый класс из Entity Framework

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

Однако теперь я хочу ТОЛЬКО вернуть базовый класс. Unfortunalty делает

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

делает то же самое, как DBContext.Bases.ToList();

Он получает структуру наследования WHOLE ... Есть ли способ (без создания нового типа в EF) ТОЛЬКО возврат класса Base при просмотре базы данных?


К сожалению, я не могу войти в мой фактический счет ...

Может быть, я не сделал себе ясно, я хочу, чтобы вернуть все объекты (в том числе базы, левый и правый), но я хочу только Базовый класс должен быть возвращен, даже если в базе данных они являются действительными классами Left и Right.

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

Любые идеи?

+0

Это, к сожалению, труднее, чем вы могли бы ожидайте, по крайней мере, в LINQ to Entities. В Entity SQL вы можете использовать 'OFTYPE (ТОЛЬКО ...)'. Алекс Джеймс объясняет, как это сделать [в этом совете] (http://blogs.msdn.com/alexj/archive/2009/09/17/tip-35-how-to-write-oftypeonly-tentity.aspx "Совет. 35 - Как написать OfTypeOnly <TEntity>() "). –

ответ

1

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

var result = from item in DBContext.Bases.ToList() 
      where (!item.GetType().IsSubclassOf(typeof(Base))) 
      select item; 
+1

'GetType()', к сожалению, не поддерживается в LINQ to Entities. :( –

+0

Хммм ... Странно. Я бы подумал, что ToList() означал бы, что тип возврата был бы в виде IEnumerable, который LINQ был бы вполне доволен. А что касается проблемы GetType() не поддерживается, это немного тревожно. Я ошибаюсь здесь, или это пример серьезного упущения в рамках? –

+2

Нет, я пропустил «ToList». Но теперь вы находитесь в L2O (а не в L2E), поэтому сервер БД возвращает все строки вместо того, чтобы делать это на сервере БД (как 'OfType ()' делает). Запрос, который вы даете, будет работать, хотя и медленно. 'GetType()' не поддерживается, потому что нет Метод CLR поддерживается, если для него нет специального перевода SQL, см. Http://msdn.microsoft.com/en-us/library/bb738681.aspx для полного списка. Я хочу, чтобы 'GetType()' поддерживались, но «Тип» имеет множество функций, поэтому для EF это будет непростой задачей. –

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>(); 
+1

Для этого также требуются объекты Linq-to-objects, а не Linq-to-entity. –

2

GetType() не понимают Entity Framework, но ключевое слово 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 с кодовым первой модели. Он сильно заимствует от Alex James' tip 35.

1

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

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

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

Linq к Entities будет возвращать список анонимных объектов, в то время как .AsEnumerable() возвращает вас обратно в Linq-на-объектов и позволяет вызовите new BaseType() с списком инициализатора объекта.

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

EDIT (протестировано, но не в производстве EntityFramework):

Благодаря this answer для 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)); 
     } 
    } 
} 
0

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

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

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

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