简体   繁体   English

针对实体列表的LINQ to SQL查询

[英]LINQ to SQL query against a list of entities

Ingredient class: 成分类:

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

List of Ingredients: 成分列表:

var ingredientsList = new List<Ingredient>();

Database layout of my "Ingredients" table: 我的“成分”表的数据库布局:

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



Am I able to query my ingredientsList against my "Ingredients" table, doing a where-clause which goes something like this (pseudo code alert!): 我可以在我的“Ingredients”表中查询我的ingredientsList ,做一个where-clause,就像这样(伪代码警报!):

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



I of course want this to be done with LINQ, and not using dynamically generated SQL queries. 我当然希望使用LINQ完成此操作,而不是使用动态生成的SQL查询。

LINQ is composable, but to do this without using UNION you'd have to roll your own Expression . LINQ是可组合的,但要在不使用UNION的情况下执行此操作,您必须滚动自己的Expression Basically, we (presumably) want to create TSQL of the form: 基本上,我们(大概)想要创建以下形式的TSQL:

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

where the name/amount pairs are determined at runtime. 其中名称/数量对在运行时确定。 There is easy way of phrasing that in LINQ; 在LINQ中有简单的措辞方式; if it was "AND" each time, we could use .Where(...) repeatedly. 如果每次都是“AND”,我们可以反复使用.Where(...) Union is a candidate, but I've seen repeated people have problems with that. Union是一个候选人,但我看到反复的人都有这个问题。 What we want to do is emulate us writing a LINQ query like: 我们想要做的是模仿我们编写LINQ查询,如:

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

This is done by crafting an Expression , using Expression.OrElse to combine each - so we will need to iterate over our name/amount pairs, making a richer Expression . 这是通过制作一个Expression来完成的,使用Expression.OrElse来组合每个 - 所以我们需要迭代我们的名称/数量对,从而制作更丰富的Expression

Writing Expression code by hand is a bit of a black art, but I have a very similar example up my sleeve (from a presentation I give); 手工编写Expression代码有点像黑色艺术,但我的袖子上有一个非常相似的例子(从我给出的演示文稿); it uses some custom extension methods; 它使用一些自定义扩展方法; usage via: 用法经过:

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

where args is your array of test ingredients. args是你的测试成分。 What this does is: for each localIngredient in args (our local array of test ingredients), it asks us to provide an Expression (for that localIngredient ) that is the test to apply at the database. 它的作用是:对于args每个localIngredient (我们的本地测试成分数组),它要求我们提供一个Expression (对于localIngredient ),这是在数据库中应用的测试。 It then combines these (in turn) with Expression.OrElse : 然后它将这些(反过来)与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);
}

The only extent to which you can use a local collection in a LINQ 2 SQL query is with the Contains() function, which is basically a translation to the SQL in clause. 在LINQ 2 SQL查询中使用本地集合的唯一程度是使用Contains()函数,它基本上是对SQL in子句的转换。 For example... 例如...

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);

This would generate SQL equivalent to " ...where ingredients.Name in (...) " 这将生成相当于“ ...where ingredients.Name in (...) ”的SQL

Unfortunately I don't think that's going to work for you, as you'd have to join each column atomically. 不幸的是,我不认为这对你有用,因为你必须原子地加入每一列。

And just as an aside, using LINQ 2 SQL is a dynamically generated SQL query. 另外,使用LINQ 2 SQL 动态生成的SQL查询。

You could, of course, do the joining on the client side, but that would require bringing back the entire Ingredients table, which could be performance-prohibitive, and is definitely bad practice. 当然,您可以在客户端进行加入,但这需要恢复整个Ingredients表,这可能性能过高, 绝对是不好的做法。

I think you'll either have to use multiple queries, or copy your ingredients list into a temporary table and do the database query in that way. 我想你要么必须使用多个查询,要么将你的成分列表复制到临时表中并以这种方式进行数据库查询。

I mean, you could have a SQL statement of: 我的意思是,你可以有一个SQL语句:

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

but it get ugly pretty quickly. 但它很快变得丑陋。

Personally I suspect that the temporary table solution is going to be the neatest - but I don't know whether LINQ to SQL has much support for them. 我个人怀疑临时表解决方案将是最好的 - 但我不知道LINQ to SQL是否对它们有很多支持。

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();

I was messing around with this solution in LINQPad , if you have it, you can see the dump outputs. 我在LINQPad中搞乱这个解决方案,如果你有它,你可以看到转储输出。 Not sure if it is what you need, but from what I understand it is. 不确定它是否是你需要的,但从我的理解是它。 I used it against my Users table, but you could replaced that for Ingredients and "UserList" for "IngredientList" and "Username" for "Ingredient Name". 我在针对我的Users表使用它,但你可以用Ingredients替换它,为“IngredientList”替换“UserList”,为“Ingredient Name”替换“Username”。 You can add further "OR" filtering expressions inside the if statement. 您可以在if语句中添加更多“OR”过滤表达式。 It is important you set an ID though. 尽管如此,设置ID非常重要。

So final note the " Dump() " method is specific to LINQPad and is not required. 最后请注意,“ 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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM