如何在“WHERE”条件下使用 List 编写 LINQ to Entities 查询

问题描述 投票:0回答:4

我想知道如何以列表作为条件编写最有效的 LINQ(编辑:到实体)查询。事情是这样的。

假设我们有以下数据结构:

 public class Recipe
 {
   public int Id;
   public string Name;
   public List<Ingredient> IngredientList;
 }

 public class Ingredient
 {
   public int Id;
   public string Name;
 }

现在,我想进行一个查询,搜索包含所有给定成分的所有食谱。

 public List<Recipe> GetRecipesWhichHaveGivenIngredients(List<Ingredients> ingredients)
 {
   List<Recipe> recipes;

   using (DataContext context = new DataContext())
   {
    //efficient LINQ query goes here
    recipes = context.Recipes.Where(recipe => /*medaCode recipe.IngredientList.Contains(ingredients) */).ToList();
   }
   return recipes;
 }

基本上这是如何确定给定集合是否是另一个集合的子集的问题。

我尝试过以下查询(主要思想是使用 Intersect 操作):

List<Recipe> recipes = dataContext.Recipes.Include("Ingrediens").Where(rec => rec.IngredientList.Select(ingr => ingr.Id).Intersect(ingredients.Select(sy =>  sy.Id)).Count() == ingredients.Count).ToList();

但是我收到以下错误:

无法创造恒定的价值 输入“闭合类型”。只是原始的 类型('例如 Int32、String 和 Guid') 在这种情况下受到支持。

c# linq
4个回答
2
投票

不要对您想要查找的成分使用

List<Ingredient>
;使用
HashSet<Ingredient>
IsProperSubsetOf
方法,该方法接受集合作为其参数:

.Where(x => ingredients.IsProperSubsetOf(x.IngredientList))

除了是 O(n+m) 操作之外,它还有一个额外的好处:当你查看它时,代码会告诉你它在做什么。

编辑

如果以上内容不清楚:

public List<Recipe> GetRecipesWhichHaveGivenIngredients(HashSet<Ingredient> ingredients)
{
   using (DataContext context = new DataContext())
   {
       return context.Recipes
           .Where(x => ingredients.IsProperSubsetOf(x.IngredientList)  
           .ToList();
   }
 }

2
投票

好吧,如果

IngredientList
真的是
List<T>
,你将能够做到:

recipes = context.Recipes.Where(recipe => recipe.IngredientList.Exists(i => i.Id == ingredient.Id)).ToList();

但这意味着需要填充所有列表。由于这看起来像一个 LINQ to SQL 查询,我猜测

IngredientList
只是一个连接表...?在这种情况下,您将没有完整的列表,但您仍然可以执行类似的操作:

recipes = context.Recipes.Where(recipe => recipe.IngredientList.Count(i => i.Id == ingredient.Id) > 0).ToList();

...它仍然应该只查询一次sql服务器。

编辑

正如评论中刚刚指出的那样,这并不能完全回答问题。至于 contains-all 搜索,我认为如果不循环输入就无法完成。好处是,这可以在不枚举 IEnumerable

recipes
的情况下完成,因此下面的代码将 still 只需使用单个查询即可命中 sql 服务器一次:

var recipes = context.Recipes.AsEnumerable<Recipe>();

ingredients.ForEach(i =>
    var recipes = recipes.Where(r =>
        r.IngredientList.Count(ii => ii.Id == i.Id) > 0
    );
);

return recipes.ToList();

在点击

ToList()
之前,查询不会被执行。


0
投票

不知道这在 Linq2SQL 中是否有效,但在 Linq2Object 中,这有效:

public static class Util
{
    public static List<Recipe> GetRecipesWhichHaveGivenIngredients(this List<Recipe> recipies, List<Ingredient> ingredients)
    {
        int icount=ingredients.Count;

        var res = recipies.Where(r => r.IngredientList.Where(i => ingredients.Contains(i)).Count() == icount).ToList();
        return res;
    }
}

0
投票

已经给出的答案要么不适用于 Linq2SQL,要么没有回答问题,或者根本不起作用。

这应该适用于 Linq2SQL 和 Linq2Objects:

using System;
using System.Collections.Generic;
using System.Linq;
public List<Recipe> GetRecipesWhichHaveGivenIngredients(List<Ingredients> ingredients)
{
    List<Recipe> recipes;

    // Since Linq2SQL cannot convert Ingredients, but only value types,
    // use only the ingredients' identifiers
    List<int> ingredientIds = ingredients.Select(i => i.Id).ToList();

    using (DataContext context = new DataContext())
    {
        recipes = context.Recipes.Where(recipe => 
            ingredientIds.All(ingredientId => 
                recipe.IngredientList.Any(ingredient => 
                    ingredient.Id == ingredientId
                )
            )
        ).ToList();
    }
    return recipes;
}

这将返回包含方法参数中所有成分的食谱,如果该列表为空,则会返回所有食谱:方法参数作为对要检索的食谱的限制。

© www.soinside.com 2019 - 2024. All rights reserved.