Entity Framework.Include() с проверкой времени компиляции?

Рассмотрим следующий код, вызывающий контекст данных сгенерированных EF:

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

Если я изменю имя отношения Департамента, тогда этот код начнет метать ошибку времени выполнения. Так можно ли каким-либо образом вызвать метод .Include() безопасным образом, поэтому я проверяю время компиляции для всех ссылок, на которые ссылаются?

Ответ 1

Принимая идею moi_meme еще дальше, мой коллега разработал следующее решение, которое работает во всех случаях. Он применил новый метод Includes() для решения отношений "один ко многим" и "многие ко многим". Это позволяет вам написать следующее:

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

как это:

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

Все кредиты относятся к https://stackoverflow.com/users/70427/bojan-resnik, поэтому дайте ему немного любви, если вам нравится решение.

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

Ответ 2

Я использовал следующее с Entity Framework 5. Ключ должен включать System.Data.Entity

using System.Data.Entity;

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

Ответ 3

Я сделал небольшое расширение для ObjectQuery, которое выглядит как

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

который также требует

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

с тем, что вы можете сделать

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

который будет проверяться во время компиляции. Если я правильно помню, эти методы не работают для отношений "многие ко многим", но они работают для таких вещей, как

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

Удачи вам

Ответ 4

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

Ответ 5

Если вы все еще используете версии до Entity Framework 5, хорошая новость с С# 6, теперь вы можете использовать nameof для получения имени любого класса/объекта.

Итак, теперь вы можете сделать

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

Если вы используете EF > 5, тогда ответ Xavier лучше