LINQ to SQL запрос к списку объектов - PullRequest
2 голосов
/ 08 апреля 2009

Класс ингредиента:

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
)



Могу ли я запросить мою 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-запросов.

Ответы [ 5 ]

7 голосов
/ 08 апреля 2009

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; если бы это было «И» каждый раз, мы могли бы использовать .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 - ваш набор тестовых ингредиентов. Это делается следующим образом: для каждого localIngredient в args (наш локальный массив тестовых ингредиентов) он запрашивает у нас Expression (для этого 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);
}
3 голосов
/ 08 апреля 2009

Единственная степень, в которой вы можете использовать локальную коллекцию в запросе 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, которая могла бы быть препятствующей производительности и это определенно плохая практика. 1018 *

1 голос
/ 08 апреля 2009

Я думаю, вам нужно будет использовать несколько запросов или скопировать список ингредиентов во временную таблицу и выполнить запрос к базе данных таким образом.

Я имею в виду, вы могли бы иметь оператор 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 голосов
/ 09 апреля 2009

Я возился с этим решением в LINQPad , если он у вас есть, вы можете увидеть выходные данные дампа. Не уверен, что это то, что вам нужно, но насколько я понимаю, это так. Я использовал его против моей таблицы Users, но вы могли заменить ее для Ingredients и «UserList» для «IngredientList» и «Username» для «Ingredient Name». Вы можете добавить дополнительные фильтрующие выражения «ИЛИ» внутри оператора if. Важно установить идентификатор.

Итак, последнее замечание: метод "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();
0 голосов
/ 09 апреля 2009
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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...