EntitySet <T>.Where (myPredicate) создает исключение NotSupportedException - PullRequest
5 голосов
/ 15 сентября 2009

РЕДАКТИРОВАТЬ : Давайте попробуем это снова. На этот раз я использовал пример базы данных AdventureWorks, чтобы вы все могли подыграть. Это исключит все сумасшедшее, что я сделал в своей собственной базе данных. Вот новый пример, демонстрирующий, что работает и что я ожидал бы работать (но не работает). Может кто-нибудь объяснить, почему это не работает, или предложить другой способ достижения моей цели (рефакторинг общего выражения, чтобы его можно было использовать в другом месте)?

using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
    // For simplicity's sake we'll just grab the first result.
    // The result should have the name of the SubCategory and an array of Products with ListPrice greater than zero.
    var result = db.ProductSubcategories.Select(subCategory => new
    {
        Name = subCategory.Name,
        ProductArray = subCategory.Products.Where(product => product.ListPrice > 0).ToArray()
    }).First();
    Console.WriteLine("There are {0} products in SubCategory {1} with ListPrice > 0.", result.ProductArray.Length, result.Name);
    // Output should say: There are 3 products in SubCategory Bib-Shorts with ListPrice > 0.

    // This won't work.  I want to pull the expression out so that I can reuse it in several other places.
    Expression<Func<Product, bool>> expression = product => product.ListPrice > 0;
    result = db.ProductSubcategories.Select(subCategory => new
    {
        Name = subCategory.Name,
        ProductArray = subCategory.Products.Where(expression).ToArray() // This won't compile because Products is an EntitySet<Product> and that doesn't have an overload of Where that accepts an Expression.
    }).First();
    Console.WriteLine("There are {0} products in SubCategory {1} with ListPrice > 0.", result.ProductArray.Length, result.Name);
}

</Edit>

Следующая LINQ to SQL работает нормально:

var result = from subAccount in db.SubAccounts
             select new ServiceTicket
             {
                 MaintenancePlans = subAccount.Maintenances.Where(plan => plan.CancelDate == null && plan.UpgradeDate == null).Select(plan => plan.ToString()).ToArray()
                 // Set other properties...
             };

Однако я хочу выделить предикат, переданный в Where, поскольку он используется во всем коде. Но если я попытаюсь передать определенный предикат в Where, произойдет сбой, например:

Func<DatabaseAccess.Maintenance, bool> activePlanPredicate = plan => plan.CancelDate == null && plan.UpgradeDate == null;
var result = from subAccount in db.SubAccounts
             select new ServiceTicket
             {
                 MaintenancePlans = subAccount.Maintenances.Where(activePlanPredicate).Select(plan => plan.ToString()).ToArray()
                 // Set other properties...
             };

Это не имеет смысла для меня. Кто-нибудь может объяснить, что происходит? Maintenances имеет тип EntitySet<DatabaseAccess.Maintenance>. Я получаю ошибку:

System.NotSupportedException: Неподдерживаемая перегрузка, используемая для запроса оператор 'Где' ..

EDIT : Для тех, кто заинтересован, вот что Reflector имеет для первого (рабочего) примера с оптимизацией, установленной на .NET 2.0:

using (BugsDatabaseDataContext db = new BugsDatabaseDataContext())
{
    ParameterExpression CS$0$0001;
    ParameterExpression CS$0$0006;
    ParameterExpression CS$0$0010;
    return db.SubAccounts.Select<SubAccount, ServiceTicket>(Expression.Lambda<Func<SubAccount, ServiceTicket>>(
        Expression.MemberInit(
            Expression.New(
                (ConstructorInfo) methodof(ServiceTicket..ctor), 
                new Expression[0]), 
                new MemberBinding[] 
                { 
                    Expression.Bind(
                        (MethodInfo) methodof(ServiceTicket.set_MaintenancePlans), 
                        Expression.Call(
                            null, 
                            (MethodInfo) methodof(Enumerable.ToArray), 
                            new Expression[] 
                            { 
                                Expression.Call(
                                    null, 
                                    (MethodInfo) methodof(Enumerable.Select), 
                                    new Expression[] 
                                    { 
                                        Expression.Call(
                                            null, 
                                            (MethodInfo) methodof(Enumerable.Where), 
                                            new Expression[] 
                                            { 
                                                Expression.Property(CS$0$0001 = Expression.Parameter(typeof(SubAccount), "subAccount"), (MethodInfo) methodof(SubAccount.get_Maintenances)), 
                                                Expression.Lambda<Func<Maintenance, bool>>(
                                                    Expression.AndAlso(
                                                        Expression.Equal(
                                                            Expression.Property(CS$0$0006 = Expression.Parameter(typeof(Maintenance), "plan"), (MethodInfo) methodof(Maintenance.get_CancelDate)), 
                                                            Expression.Convert(Expression.Constant(null, typeof(DateTime?)), typeof(DateTime?)), false, (MethodInfo) methodof(DateTime.op_Equality)
                                                        ), 
                                                        Expression.Equal(
                                                            Expression.Property(CS$0$0006, (MethodInfo) methodof(Maintenance.get_UpgradeDate)), 
                                                            Expression.Convert(Expression.Constant(null, typeof(DateTime?)), typeof(DateTime?)), false, (MethodInfo) methodof(DateTime.op_Equality)
                                                        )
                                                    ), 
                                                    new ParameterExpression[] { CS$0$0006 }
                                                ) 
                                            }
                                        ), 
                                        Expression.Lambda<Func<Maintenance, string>>(
                                            Expression.Call(
                                                CS$0$0010 = Expression.Parameter(typeof(Maintenance), "plan"), 
                                                (MethodInfo) methodof(object.ToString), 
                                                new Expression[0]
                                            ), 
                                            new ParameterExpression[] { CS$0$0010 }
                                        ) 
                                    }
                                ) 
                            }
                        )
                    )
                }
            ), 
            new ParameterExpression[] { CS$0$0001 }
        )
    ).ToList<ServiceTicket>();
}

EDIT : Выходные данные Reflector для второго примера (с использованием предиката) в основном аналогичны. Самая большая разница в том, что при вызове Enumerable.Where вместо передачи Expression.Lambda он проходит Expression.Constant(activePlanPredicate).

Ответы [ 3 ]

2 голосов
/ 18 октября 2011

Я не совсем понимаю внутренности Linq to Entities, но есть набор инструментов с открытым исходным кодом (применимый в проприетарном программном обеспечении), специально разработанный для решения этой проблемы, который называется LinqKit, связанный с этой статьей, связанной с O'Reilly:

http://www.albahari.com/nutshell/predicatebuilder.aspx

Так как я не совсем понимаю смелость, я просто процитирую их:

Конвейер обработки запросов Entity Framework не может обработать выражения вызова, поэтому вам нужно вызвать AsExpandable для первого объекта в запросе. Вызывая AsExpandable, вы активируете класс посетителя выражений LINQKit, который заменяет выражения вызова более простыми конструкциями, понятными Entity Framework.

Вот прямая ссылка на LinqKit .

А вот тип кода, который позволяет этот проект:

using LinqKit;

// ...

Expression<Func<Product, bool>> expression = product => product.ListPrice > 0;

var result = db.ProductSubcategories
    .AsExpandable() // This is the magic that makes it all work
    .Select(
        subCategory => new
        {
            Name = subCategory.Name,
            ProductArray = subCategory.Products
                // Products isn't IQueryable, so we must call expression.Compile
                .Where(expression.Compile())
        })
    .First();

Console.WriteLine("There are {0} products in SubCategory {1} with ListPrice > 0."
    , result.ProductArray.Count()
    , result.Name
    );

Результат:

В подкатегории 3 шорты с надписью ListPrice> 0.

Да, не исключение, и мы можем извлечь предикат!

0 голосов
/ 15 сентября 2009

Попробуйте это:

Expression<Func<DatabaseAccess.Maintenance, bool>> activePlanPredicate = plan => plan.CancelDate == null && plan.UpgradeDate == null;
var result = from subAccount in db.SubAccounts
         select new ServiceTicket
         {
             MaintenancePlans = subAccount.Maintenances.Where(activePlanPredicate).Select(plan => plan.ToString()).ToArray()
             // Set other properties...
         };

У меня нет VisualStudio передо мной, так что может потребоваться некоторая настройка. Проблема, с которой вы сталкиваетесь, заключается в том, что вы хотите получить доступ к расширению IQueryable для Where, но просто наличие Func<T,bool> дает вам расширение IEnumerable.

0 голосов
/ 15 сентября 2009

Я бы рефакторинг оригинала, как это

private bool IsYourPredicateSatisfied(Maintenance plan)
{
  return plan.CancelDate == null && plan.UpgradeDate == null;
}

Тогда ваше предложение Where будет Where(m => IsYourPredicateSatisfied(m))

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...