EntitySet <t> .Where(myPredicate)抛出NotSupportedException </t>

时间:2009-09-14 22:38:24

标签: c# .net linq-to-sql

编辑:让我们再试一次。这次我使用了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:   用于查询的不支持的重载   运营商'哪里'..

编辑:对于那些感兴趣的人,这里是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>();
}

编辑:第二个示例(使用谓词)的Reflector输出大致相似。最大的区别在于,在致Enumerable.Where时,不是传递Expression.Lambda,而是传递Expression.Constant(activePlanPredicate)

3 个答案:

答案 0 :(得分:2)

我并不完全理解Linq to Entities的内容,但是有一个开源(可在专有软件中使用)工具包,专门用于帮助解决这个问题,称为LinqKit,与O'Reilly相关的文章相关联:

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

由于我不完全理解胆量,我只是引用它们:

  

实体框架的查询处理管道无法处理调用表达式,这就是您需要在查询中的第一个对象上调用AsExpandable的原因。通过调用AsExpandable,可以激活LINQKit的表达式访问者类,它使用Entity Framework可以理解的更简单的结构替换调用表达式。

这是a direct link to 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
    );

结果是:

  

SubCategory Bib-Shorts有3件商品,ListPrice&gt; 0

是的,也不例外,我们可以提取谓词!

答案 1 :(得分:0)

我会像这样重构原作

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

然后你的Where子句是Where(m => IsYourPredicateSatisfied(m))

答案 2 :(得分:0)

试试这个:

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的{​​{1}}扩展名,但只有Where可以获得Func<T,bool>扩展名。