2010-01-13 17 views
13

Si tengo tres clases en el marco de la entidad.Recuperar solo la clase base de Entity Framework

class Base {} 

class Left : Base {} 

class Right : Base {} 

y llamo DBContext.Bases.ToList();

Esto devuelve todas las instancias de Base totalmente escrito en sus tipos heredados asociados, ya que algunas personas han notado, el rendimiento de EF en las estructuras de herencia grandes no es muy grande por decir lo menos . Mi consulta real en mi proyecto tiene 600 líneas de longitud, solo para devolver una entidad y tarda 2 segundos en generarla.

La consulta se ejecuta mucho más rápido si le dices qué tipo devolver, ya que no tiene que unirse a toda la estructura. p.ej.

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

Sin embargo ahora quiero SOLO devolver la clase base. Unfortunalty haciendo

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

hace lo mismo que DBContext.Bases.ToList();

Obtiene la estructura de herencia TODO ... ¿Hay alguna manera (sin hacer un nuevo tipo en EF) de SOLO devolviendo la clase Base al mirar a través de la colección Base?


En este momento no puedo acceder a mi cuenta real ...

Tal vez Yo aún no ha quedado claro, quiero traer de vuelta todos los objetos (incluyendo Base, izquierda y derecha), pero yo sólo quiero que la Clase base que se devolverá, incluso si en la base de datos son clases reales izquierda y derecha.

OFTYPE fue una buena sugerencia pero filtra todas mis entidades porque ninguna es el tipo de base real. Pero quiero devolver solo los valores de tipo Base en el objeto tipo Base.

¿Alguna idea?

+0

Esto es, por desgracia, más difícil de lo que podría esperar, al menos en LINQ to Entities. En Entity SQL puede usar 'OFTYPE (SOLO ...)'. Alex James explica cómo hacerlo [en este consejo] (http://blogs.msdn.com/alexj/archive/2009/09/17/tip-35-how-to-write-oftypeonly-tentity.aspx "Sugerencia 35 - Cómo escribir OfTypeOnly <TEntity>() "). –

Respuesta

1

Asumiendo que son capaces de utilizar LINQ, podrías usar algo en la línea de la siguiente ejemplo rápido y sucio ?:

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

'GetType()', lamentablemente, no es compatible con LINQ to Entities. :( –

+0

Hmmm ... Extraño. Hubiera pensado que un ToList() hubiera significado que el tipo de devolución hubiera sido en forma de IEnumerable, con lo que LINQ estaría muy satisfecho. Y en cuanto al tema de GetType() no ser compatible, eso es un poco más alarmante. ¿Estoy equivocado aquí, o es este un ejemplo de una omisión grave en el marco? –

+2

No, me perdí el 'ToList'. Pero ahora estás en L2O (en lugar de L2E), por lo que el servidor DB devuelve todas las filas en lugar de hacerlo en el servidor DB (como 'OfType ()' lo hace). La consulta que proporcione funcionará, aunque lentamente. 'GetType()' no se admite porque no El método CLR es compatible a menos que haya una traducción SQL específica para él, consulte http://msdn.microsoft.com/en-us/library/bb738681.aspx para obtener una lista completa. Me gustaría que 'GetType()' fuera compatible, pero 'Type' tiene muchas funciones, por lo que no sería una tarea pequeña para EF. –

0

Actualmente utilizo la siguiente extensión de LINQ, asumiendo sub-clases se encuentran en el mismo ensamblaje

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

Uso:

var bases = DBContext.Bases.OfTypeOnly<Base>(); 
+1

Esto también requiere Linq-to-objects no Linq-to-entities. –

2

El GetType() no se entiende por Marco de la entidad, pero la palabra clave is funciona. Como tal, puedes construir una Expresión y aplicarla a tu consulta. El código aquí debería funcionar para EF5 + para agregar un método de extensión al que puede llamar como: query.OfOnlyType<Base, SubTypeWithDescendants>(). (O con los mismos dos argumentos de tipo si es necesario, mi jerarquía es más complicado de lo que aunque)

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

sólo he probado en EF6.1 con un modelo de código primero. Toma prestado fuertemente de Alex James' tip 35.

1

Para responder a la pregunta de que ninguna de las respuestas anteriores parece ocuparse (es decir, solo filtramos las columnas devueltas para que solo sean las columnas de tipo base, pero no filtren las filas que tienen información de tipo derivado), hay una forma bastante sencilla de hacerlo con tipos anónimos. Vea here para otra pregunta de stackoverflow que trata con los detalles.

La idea es hacer algo como esto:

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

El LINQ-a-Entidades se devolverá una lista de objetos anónimos, mientras que los .AsEnumerable() de nuevo a LINQ a objetos que vuelve y le permite llame al new BaseType() con una lista de inicializadores de objetos.

Esto tiene un desafortunado inconveniente de ser específico para los tipos. Alguien aquí en la oficina quiere que se escriba uno genérico, así que volveré pronto y edito esta respuesta con una versión completamente genérica de esto.

EDIT (probado, pero no en la producción de ADO.NET Entity Framework):

Gracias a this answer para el código 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

Puede utilizar DbSet.SqlQuery:

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

Tenga en cuenta que no usar .AsNoTracking() le conseguirá en agua caliente antes o después (si no se derivan tipos ya cargados en el contexto obtendrá clave única violaciones/excepciones inmediatamente).

0

No está seguro acerca de las diferencias de rendimiento, pero yo podía imaginar que esto sería más rápido que cargar todas las filas (cuando una gran cantidad de filas están en 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(); 
Cuestiones relacionadas