2009-04-08 17 views
2

成份類:LINQ to SQL查詢針對實體的名單

class Ingredient 
{ 
    public String Name { get; set; } 
    public Double Amount { get; set; } 
} 

配料表:我的 「成分」 表的

var ingredientsList = new List<Ingredient>(); 

數據庫佈局:

[Ingredients] (
    [IngredientsID] [int] IDENTITY(1,1) NOT NULL, 
    [RecipeID] [int] NOT NULL, 
    [IngredientsName] [nvarchar](512) NOT NULL, 
    [IngredientsAmount] [float] NOT NULL 
) 



我可以q uery我ingredientsList對我的「成分」表,做一個WHERE子句這是這樣的(僞代碼警惕!):

SELECT * FROM Ingredients WHERE 
IngredientsName = ["Name" property on entities in my ingredientsList] AND 
IngredientsAmount <= ["Amount" property on entities in my ingredientsList] 



當然,我想這與LINQ來完成,不使用動態生成的SQL查詢。

+0

我原本以爲Linq可以處理加入兩種類型的實體,直到我試過它 – cjk 2009-04-08 13:08:14

+0

你想得到什麼結果?我真的不明白,「查詢我的ingredientsList對我的」成分「表」部分。 – antonioh 2009-04-08 13:20:49

回答

7

LINQ是可組合的,但要做到這一點,而不使用UNION,你必須推出自己的Expression。基本上,我們(可能)想要創建以下格式的TSQL:

SELECT * 
FROM [table] 
WHERE (Name = @name1 AND Amount <= @amount1) 
OR  (Name = @name2 AND Amount <= @amount2) 
OR  (Name = @name3 AND Amount <= @amount3) 
... 

其中名稱/數量對在運行時確定。在LINQ中有簡單的措辭方式;如果每次都是「AND」,我們可以重複使用.Where(...)Union是一個候選人,但我已經看到重複的人遇到問題。我們想要做的是模仿我們寫一個LINQ查詢,如:

var qry = from i in db.Ingredients 
      where ( (i.Name == name1 && i.Amount <= amount1) 
       || (i.Name == name2 && i.Amount <= amount2) 
       ...) 
      select i; 

這是通過制定一個Expression完成,使用Expression.OrElse每個組合 - 所以我們需要遍歷我們的名字/量對,使得更豐富Expression

寫作Expression手寫的代碼是一種黑色藝術,但我有一個非常類似的例子(從我給出的演示文稿);它使用一些自定義的擴展方法;使用方法如下:

IQueryable query = db.Ingredients.WhereTrueForAny(
     localIngredient => dbIngredient => 
        dbIngredient.Name == localIngredient.Name 
       && dbIngredient.Amount <= localIngredient.Amount 
      , args); 

其中args是您測試成分的數組。它的作用是:對於args(我們當地的測試成分陣列)localIngredient中的每個localIngredient,它要求我們提供一個(對於localIngredient),該測試適用於數據庫。然後將其與Expression.OrElse結合這些(反過來):


public static IQueryable<TSource> WhereTrueForAny<TSource, TValue>(
    this IQueryable<TSource> source, 
    Func<TValue, Expression<Func<TSource, bool>>> selector, 
    params TValue[] values) 
{ 
    return source.Where(BuildTrueForAny(selector, values)); 
} 
public static Expression<Func<TSource, bool>> BuildTrueForAny<TSource, TValue>(
    Func<TValue, Expression<Func<TSource, bool>>> selector, 
    params TValue[] values) 
{ 
    if (selector == null) throw new ArgumentNullException("selector"); 
    if (values == null) throw new ArgumentNullException("values"); 
    // if there are no filters, return nothing 
    if (values.Length == 0) return x => false; 
    // if there is 1 filter, use it directly 
    if (values.Length == 1) return selector(values[0]); 

    var param = Expression.Parameter(typeof(TSource), "x"); 
    // start with the first filter 
    Expression body = Expression.Invoke(selector(values[0]), param); 
    for (int i = 1; i < values.Length; i++) 
    { // for 2nd, 3rd, etc - use OrElse for that filter 
     body = Expression.OrElse(body, 
      Expression.Invoke(selector(values[i]), param)); 
    } 
    return Expression.Lambda<Func<TSource, bool>>(body, param); 
} 
1

我認爲你要麼必須使用多個查詢,要麼將你的成分列表複製到臨時表中,並以這種方式進行數據庫查詢。

我的意思是,你能的SQL語句:

SELECT * FROM Ingredients WHERE 
(IngredientsName = 'Flour' AND IngredientsAmount < 10) OR 
(IngredientsName = 'Water' AND IngredientsAmount <= 5) OR 
(IngredientsName = 'Eggs' AND IngredientsAmount <= 20) 

但它變得醜陋很快。

就我個人而言,我懷疑臨時表解決方案將是最好的 - 但我不知道LINQ to SQL是否對他們有很大的支持。

+0

選擇表中的所有條目聽起來像是一個非常糟糕的選擇,性能明智。也;我旨在爲此使用LINQ to SQL。不是動態生成的SQL查詢:) – roosteronacid 2009-04-08 13:15:54

+0

@rooster,就像我在我的回答中所說的,LINQ 2 SQL是一個動態生成的SQL查詢。 – 2009-04-08 13:24:51

3

您可以在LINQ 2 SQL查詢中使用本地集合的唯一範圍是Contains()函數,該函數基本上是對SQL in子句的轉換。例如...

var ingredientsList = new List<Ingredient>(); 

... add your ingredients 

var myQuery = (from ingredient in context.Ingredients where ingredientsList.Select(i => i.Name).Contains(ingredient.Name) select ingredient); 

這將生成SQL相當於「...where ingredients.Name in (...)

不幸的是,我不認爲這會爲你工作,因爲你不得不原子加入每一列。

而就在旁邊,使用LINQ 2 SQL 一個動態生成的SQL查詢。

你可以,當然,做客戶端的連接,但這需要帶回整個Ingredients表,這可能是表現令人望而卻步,而且是絕對實踐。

0
List<string> ingredientNames = ingredientsList 
    .Select(i => i.Name).ToList(); 
Dictionary<string, Double> ingredientValues = ingredientsList 
    .ToDictionary(i => i.Name, i => i.Amount); 
//database hit 
List<Ingredient> queryResults = db.Ingredients 
    .Where(i => ingredientNames.Contains(i.Name)) 
    .ToList(); 
//continue filtering locally - TODO: handle case-sensitivity 
List<Ingredient> filteredResults = queryResults 
    .Where(i => i.Amount <= ingredientValues[i.Name]) 
    .ToList(); 
0

我在LINQPad這個解決方案瞎搞,如果你擁有它,你可以看到轉儲輸出。不知道這是你需要什麼,但從我的理解是。我用它對我的Users表格進行了處理,但您可以替換成分,將「成分名稱」和「用戶名」替換爲「成分名稱」的「用戶列表」。您可以在if語句內添加更多「OR」過濾表達式。儘管設置ID很重要。

所以最後注意「Dump()」方法是特定於LINQPad的,並不是必需的。

var userList = new List<User>(); 
userList.Add(new User() { ID = 1, Username = "goneale" }); 
userList.Add(new User() { ID = 2, Username = "Test" }); 

List<int> IDs = new List<int>(); 
//      vv ingredients from db context 
IQueryable<User> users = Users; 
foreach(var user in userList) 
{ 
    if (users.Any(x => x.Username == user.Username)) 
     IDs.Add(user.ID); 
} 
IDs.Dump(); 
userList.Dump(); 
users.Dump(); 
users = users.Where(x => IDs.Contains(x.ID)); 
users.Dump();