2010-05-27 12 views
8

Considere el siguiente código, que está llamando contra un EF genera contexto de datos:Entity Framework .Include() con comprobación de tiempo de compilación?

var context = new DataContext(); 
var employees = context.Employees.Include("Department"); 

Si cambio el nombre de la relación Departamento continuación, este código se va a empezar a tirar un error de ejecución. Entonces, ¿hay alguna forma de llamar al método .Include() de manera segura, así que obtengo tiempo de compilación para verificar todas las relaciones a las que se hace referencia?

Respuesta

5

Tomando la idea de moi_meme un paso más allá, mi colega ha desarrollado el siguiente solución que funciona en todos los casos. Introdujo un nuevo método caled Includes() para tratar con relaciones uno a muchos y muchos a muchos. Se le permite escribir esto:

context.Customer 
    .Include("Address") 
    .Include("Orders") 
    .Include("Orders.OrderLines") 

como esto:

context.Customer 
    .Include(c => c.Address) 
    .Includes(c => c.Include(customer => customer.Orders) 
        .Include(order => order.OrderLines)) 

Todo el crédito va a https://stackoverflow.com/users/70427/bojan-resnik, por lo que ir darle un poco de amor, si te gusta la solución.

public static class ObjectQueryExtensions 
{ 
    public static ObjectQuery<T> Includes<T>(this ObjectQuery<T> query, Action<IncludeObjectQuery<T, T>> action) 
    { 
     var sb = new StringBuilder(); 
     var queryBuilder = new IncludeObjectQuery<T, T>(query, sb); 
     action(queryBuilder); 
     return queryBuilder.Query; 
    } 

    public static ObjectQuery<TEntity> Include<TEntity, TProperty>(this ObjectQuery<TEntity> query, Expression<Func<TEntity, TProperty>> expression) 
    { 
     var sb = new StringBuilder(); 
     return IncludeAllLevels(expression, sb, query); 
    } 

    static ObjectQuery<TQuery> IncludeAllLevels<TEntity, TProperty, TQuery>(Expression<Func<TEntity, TProperty>> expression, StringBuilder sb, ObjectQuery<TQuery> query) 
    { 
     foreach (var name in expression.GetPropertyLevels()) 
     { 
      sb.Append(name); 
      query = query.Include(sb.ToString()); 
      Debug.WriteLine(string.Format("Include(\"{0}\")", sb)); 
      sb.Append('.'); 
     } 
     return query; 
    } 

    static IEnumerable<string> GetPropertyLevels<TClass, TProperty>(this Expression<Func<TClass, TProperty>> expression) 
    { 
     var namesInReverse = new List<string>(); 

     var unaryExpression = expression as UnaryExpression; 
     var body = unaryExpression != null ? unaryExpression.Operand : expression.Body; 

     while (body != null) 
     { 
      var memberExpression = body as MemberExpression; 
      if (memberExpression == null) 
       break; 

      namesInReverse.Add(memberExpression.Member.Name); 
      body = memberExpression.Expression; 
     } 

     namesInReverse.Reverse(); 
     return namesInReverse; 
    } 

    public class IncludeObjectQuery<TQuery, T> 
    { 
     readonly StringBuilder _pathBuilder; 
     public ObjectQuery<TQuery> Query { get; private set; } 

     public IncludeObjectQuery(ObjectQuery<TQuery> query, StringBuilder builder) 
     { 
      _pathBuilder = builder; 
      Query = query; 
     } 

     public IncludeObjectQuery<TQuery, U> Include<U>(Expression<Func<T, U>> expression) 
     { 
      Query = ObjectQueryExtensions.IncludeAllLevels(expression, _pathBuilder, Query); 
      return new IncludeObjectQuery<TQuery, U>(Query, _pathBuilder); 
     } 

     public IncludeObjectQuery<TQuery, U> Include<U>(Expression<Func<T, EntityCollection<U>>> expression) where U : class 
     { 
      Query = ObjectQueryExtensions.IncludeAllLevels(expression, _pathBuilder, Query); 
      return new IncludeObjectQuery<TQuery, U>(Query, _pathBuilder); 
     } 
    } 
} 
+0

Muy impresionante, supongo que voy a tener que intentarlo :) ¡¡aplausos !! –

5

hice una pequeña extensión a ObjectQuery que dice así

public static ObjectQuery<TEntity> Include<TEntity, TProperty>(this ObjectQuery<TEntity> query, Expression<Func<TEntity, TProperty>> expression) where TEntity : class 
{ 
    string name = expression.GetPropertyName(); 
    return query.Include(name); 
} 

que también requiere

public static class ExpressionExtensions 
{ 
    public static string GetPropertyName<TObject, TProperty>(this Expression<Func<TObject, TProperty>> expression) where TObject : class 
    { 
     if (expression.Body.NodeType == ExpressionType.Call) 
     { 
      MethodCallExpression methodCallExpression = (MethodCallExpression)expression.Body; 
      string name = ExpressionExtensions.GetPropertyName(methodCallExpression); 
      return name.Substring(expression.Parameters[0].Name.Length + 1); 
     } 
     return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1); 
    } 

    private static string GetPropertyName(MethodCallExpression expression) 
    { 
     MethodCallExpression methodCallExpression = expression.Object as MethodCallExpression; 
     if (methodCallExpression != null) 
     { 
      return GetPropertyName(methodCallExpression); 
     } 
     return expression.Object.ToString(); 
    } 
} 

con que se puede hacer

var context = new DataContext();  
var employees = context.Employees.Include(e => e.Department); 

que va a ser cheque en tiempo de compilación Si recuerdo bien, estos métodos no funcionan para la relación de muchos a muchos, pero funciona para cosas como

var item = context.Employees.Include(e => e.Department.Manager); 

Buena suerte

+1

Todo lo que hace es tomar el nombre de la propiedad y ponerlo a la cadena para que su entidad debe tiene el mismo nombre que esa propiedad para que funcione, por lo que podría funcionar para muchos a muchos –

+0

Corrígeme si soy incorrecto, pero esto no funcionará para nav de doble anidación (por ejemplo, 'Orders.Details.Products') . – RPM1984

+0

@ RPM1984 no funcionará cuando se trata de una EntityCollection ... no puede referir las propiedades de una colección de entidades. –

2
var context = new DataContext(); 
var employees = context.Employees.Include(context.Department.EntitySet.Name); 
6

he utilizado con el siguiente marco de la entidad 5. La clave es incluir System.Data.Entity

using System.Data.Entity; 

context.Customer 
    .Include(c => c.Address) 
+1

¡Gracias, el Incluir es por qué otras soluciones no estaban funcionando! Esta es, de lejos, la solución más simple. –

1

En caso de que todavía está utilizando versiones anteriores a Entity Framework 5 , la buena noticia es a partir de C# 6, ahora puede usar nameof para recuperar el nombre de cualquier clase/objeto.

Así que ahora es posible hacer

var context = new DataContext(); 
var employees = context.Employees.Include(nameof(Employees.Department)); 

Si está utilizando FE> 5, a continuación, Xavier's answer is better

Cuestiones relacionadas