2009-04-08 43 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();