2009-09-14 48 views
5

EDIT:讓我們再試一次。這一次我使用了AdventureWorks示例數據庫,所以你可以一起玩。這將排除我在我自己的數據庫中做的任何瘋狂事情。下面是一個新的例子,演示什麼可行,什麼我期望工作(但不)。任何人都可以解釋爲什麼它不起作用,或者提出了實現我的目標的不同方式(重構常用表達式以便可以在其他地方重用)?EntitySet <T>.Where(myPredicate)throws NotSupportedException

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到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... 
      }; 

這是沒有意義的我。任何人都可以解釋發生了什麼? MaintenancesEntitySet<DatabaseAccess.Maintenance>的類型。我得到的錯誤是:

System.NotSupportedException: Unsupported overload used for query operator 'Where'..

編輯:對於那些有興趣,這裏是反射器具有與優化第一(工作),例如設置爲.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>(); 
} 

編輯 :第二個示例(使用謂詞)的反射器輸出大部分類似。最大的區別在於,在致電Enumerable.Where時,不是通過Expression.Lambda而是通過Expression.Constant(activePlanPredicate)

+0

使用第一部分編譯並查看如何使用反射器生成代碼。這將有助於每個人瞭解謂詞的實際類型。 – shahkalpesh 2009-09-15 00:13:48

+0

上面添加了反射器輸出。如果我誤解了,請告訴我。 – Ecyrb 2009-09-15 12:35:08

回答

2

我並不完全瞭解Linq to Entities的膽量,但是有一個專門用來解決這個問題的開源(可用於專有軟件)工具包,名爲LinqKit,與O'Reilly相關的文章:

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

因爲我不完全理解的膽量,我只是說出來了:

Entity Framework's query processing pipeline cannot handle invocation expressions, which is why you need to call AsExpandable on the first object in the query. By calling AsExpandable, you activate LINQKit's expression visitor class which substitutes invocation expressions with simpler constructs that Entity Framework can understand.

這裏是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 
    ); 

結果是:

There are 3 products in SubCategory Bib-Shorts with ListPrice > 0.

耶,也不例外,我們可以提取謂詞!

+0

請注意,我不確定您的示例中的ToArray()將永遠無法工作。即使不嘗試提取謂詞,我也無法使其工作...... – 2011-10-18 08:44:31

0

我重構原來這樣

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

那麼你的WHERE子句Where(m => IsYourPredicateSatisfied(m))

+0

看來他的目標是能夠傳遞一個自定義謂詞。你在這裏做相反的事情。 – 2009-09-14 23:00:38

+0

我確實嘗試過,但會導致相同的錯誤。 – Ecyrb 2009-09-14 23:03:56

+0

呃,對不起,我第一次誤讀。它仍然不起作用,但我得到的錯誤是:System.NotSupportedException:方法'布爾測試(DatabaseAccess.Maintenance)'沒有支持轉換到SQL .. – Ecyrb 2009-09-14 23:18:43

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中在我的面前,所以這可能需要一些調整。您遇到的問題是您想要訪問WhereIQueryable分機,但只有Func<T,bool>會爲您提供IEnumerable分機。

+0

這就是我會想到的,但'EntitySet '執行'IEnumerable ',而不是'IQueryable '。因此,使用表達式會導致以下錯誤:方法'System.Linq.Enumerable.Where (System.Collections.Generic.IEnumerable ,System.Func )'的類型參數不能從使用中推斷出來。嘗試明確指定類型參數。 – Ecyrb 2009-09-14 23:14:32

+0

@Ecyrb:嘗試'EntitySet .ToQueryable()' – leppie 2011-10-18 08:43:25

相關問題