Как улучшить мой linq для получения значений, даже если не все условия выполнены - PullRequest
0 голосов
/ 03 июля 2019

Я работаю со списком продуктов, это простая таблица, которая отображает все мои продукты.

Поскольку их много, я включил 4 выпадающих списка в качестве фильтров, где я мог бы выбрать значения для фильтрации пои выглядит это примерно так:

enter image description here

Выше dropdows я написал, какие данные они хранят, чтобы их было легче понять.

Так, например, если я выбираю только Product и Payment, чтобы фильтр по моему linq не возвращал никаких значений, это потому, что, вероятно, я сказал, что все значения должны быть соблюдены, чтобы получить некоторые данные ..

Здесьтакое linq:

private Expression<Func<Product, bool>> GetFilter(Filter filterQuery)
{
    return f => (f.ProductId == filterQuery.ProductId) &&
                (f.LocationId == filterQuery.LocationId) &&
                (f.PaymentMethodId == filterQuery.PaymentMethodId) && ..sameForUser;
}

Итак, как мне улучшить этот linq, чтобы он возвращал значения, если выбраны только Product и Payment, или чтобы возвращал значения, если только Product и выбрано Location, иливсе они .. и т.д ..

Спасибо, ребята, ура

Ответы [ 3 ]

1 голос
/ 04 июля 2019

Проверьте сгенерированный SQL для этого метода. Если вы построите всю условную логику внутри выражения, все это будет преобразовано в SQL.

Что-то вроде:

SELECT
    [Extent1].[ProductId] AS [ProductId],
    [Extent1].[LocationId] AS [LocationId],
    [Extent1].[PaymentMethodId] AS [PaymentMethodId]
    FROM [dbo].[Products] AS [Extent1]
    WHERE ((CASE WHEN (@p__linq__0 IS NOT NULL) THEN CASE WHEN (([Extent1].[ProductId] = @p__linq__1) OR (([Extent1].[ProductId] IS NULL) AND (@p__linq__1 IS NULL))) THEN cast(1 as bit) WHEN ( NOT (([Extent1].[ProductId] = @p__linq__1) AND ((CASE WHEN ([Extent1].[ProductId] IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END) = (CASE WHEN (@p__linq__1 IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END)))) THEN cast(0 as bit) END ELSE cast(1 as bit) END) = 1) AND ((CASE WHEN (@p__linq__2 IS NOT NULL) THEN CASE WHEN (([Extent1].[LocationId] = @p__linq__3) OR (([Extent1].[LocationId] IS NULL) AND (@p__linq__3 IS NULL))) THEN cast(1 as bit) WHEN ( NOT (([Extent1].[LocationId] = @p__linq__3) AND ((CASE WHEN ([Extent1].[LocationId] IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END) = (CASE WHEN (@p__linq__3 IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END)))) THEN cast(0 as bit) END ELSE cast(1 as bit) END) = 1) AND ((CASE WHEN (@p__linq__4 IS NOT NULL) THEN CASE WHEN (([Extent1].[PaymentMethodId] = @p__linq__5) OR (([Extent1].[PaymentMethodId] IS NULL) AND (@p__linq__5 IS NULL))) THEN cast(1 as bit) WHEN ( NOT (([Extent1].[PaymentMethodId] = @p__linq__5) AND ((CASE WHEN ([Extent1].[PaymentMethodId] IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END) = (CASE WHEN (@p__linq__5 IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END)))) THEN cast(0 as bit) END ELSE cast(1 as bit) END) = 1)

Лучшей стратегией является добавление в выражение запроса только для активных критериев фильтра. Простая реализация будет выглядеть примерно так:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;

namespace Ef6Test
{
    public class Product
    {
        public int ProductId { get; set; }
        public int LocationId { get; set; }
        public int PaymentMethodId { get; set; }

    }

    static class FilterExtensions
    {
        public static IQueryable<T> Where<T>(this IQueryable<T> q, Filter<T> filter)
        {
            return filter.ApplyTo(q);
        }
    }
    abstract class Filter<T>
    {
        public abstract IQueryable<T> ApplyTo(IQueryable<T> q);
    }
    class ProductFilter : Filter<Product>
    {
        public int? ProductId { get; set; }
        public int? LocationId { get; set; }
        public int? PaymentMethodId { get; set; }

        public override IQueryable<Product> ApplyTo(IQueryable<Product> q)
        {
            if (ProductId.HasValue)
            {
                q = q.Where(p => p.ProductId == this.ProductId);
            }
            if (LocationId.HasValue)
            {
                q = q.Where(p => p.LocationId == this.LocationId);
            }
            if (PaymentMethodId.HasValue)
            {
                q = q.Where(p => p.PaymentMethodId == this.PaymentMethodId);
            }
            return q;
        }
    }

    class Db : DbContext
    {

        public virtual DbSet<Product> Products { get; set; }


        class Program
        {


            static void Main(string[] args)
            {

                Database.SetInitializer(new DropCreateDatabaseAlways<Db>());

                using (var db = new Db())
                {
                    db.Database.Log = m => Console.WriteLine(m);
                    db.Database.Initialize(false);


                    var filter = new ProductFilter();
                    filter.LocationId = 2;

                    var q = db.Products.Where(filter);


                    var sql = q.ToString();
                    Console.WriteLine(sql);
                }


                Console.WriteLine("Hit any key to exit");
                Console.ReadKey();
            }




        }
    }
}

, который генерирует хороший, чистый SQL-запрос, подобный этому:

SELECT
    [Extent1].[ProductId] AS [ProductId],
    [Extent1].[LocationId] AS [LocationId],
    [Extent1].[PaymentMethodId] AS [PaymentMethodId]
    FROM [dbo].[Products] AS [Extent1]
    WHERE [Extent1].[LocationId] = @p__linq__0
1 голос
/ 03 июля 2019

Вы можете включить запись по умолчанию, если не выбран вариант из различных фильтров. Примерно так:

private Expression<Func<Product, bool>> GetFilter(Filter filterQuery)
{
    return f => (filterQuery.ProductId.HasValue ? f.ProductId == filterQuery.ProductId : true) &&
                (filterQuery.LocationId.HasValue ? f.LocationId == filterQuery.LocationId : true) &&
                (filterQuery.PaymentMethodId.HasValue ? f.PaymentMethodId == filterQuery.PaymentMethodId : true) && ..sameForUser;
}

Предполагается, что ваши Id будут нулевыми, если не выбрано никакого значения. Из вашего вопроса непонятно, что используется для представления не выбранной опции (ноль, 0 и т. Д.), Однако концепция останется прежней: включите их, если не выбрана ни одна опция или если есть выбор, сопоставьте на выбранный идентификатор. Обязательное примечание: лично я (только мое мнение) нахожу способ написания этого метода трудным для чтения и, вероятно, реорганизовал бы обратное лямбда-выражение для удобства чтения.

0 голосов
/ 04 июля 2019

Одним из улучшений было бы создание функции, которая делает то, что вы хотите.

Кажется, что у вас есть входная последовательность Products, и вы хотите отфильтровать эту входную последовательность, чтобы сохранить только те Products, которые соответствуют некоторому предикату.

Вы не очень заинтересованыв создании Expression.В конце концов, этот Expression будет использоваться только в операторе Where для получения подмножества Products.

Итак, прежде всего: вам нужно создать функцию, которая вместо этого возвращает IQueryable<Product>Expression<Product>.

Мы напишем его как функцию расширения Product.См. Расширение методов расширения:

static class ProductExtensions
{
    public static IQueryable<Product> Filter(this IQueryable<Product> products,
                                             Filter filter)
    {
        // TODO: implement
    }
}

Использование будет:

using (var dbContext = new MyDbContext())
{
    Filter filter = ReadFilterComboBoxes();
    var result = dbContext.Products.Filter(filter);
}

При желании вы можете использовать это как любую другую функцию LINQ:

var result = dbContext.Products.Where(product => ...)
                               .Filter(productFilter)
                               .Select(product => )
                               .GroupBy(...);

Очевидно, у вас есть класс Filter, который содержит значения свойств, которые будут использоваться в качестве предиката в Where в вашем запросе.Но почему-то вы хотите сказать: «не используйте это свойство в фильтре, потому что оператор не выбрал никакого значения в поле со списком»

Чтобы указать, что оператор не выбрал какое-либо значение,Вы можете использовать NULL.Для этого вам нужно сделать свойства обнуляемыми.См. , как использовать обнуляемые значения

Классы всегда обнуляются.Вы делаете тип значения (int, double, structs, enums) обнуляемым, добавляя знак вопроса к типу:

class Filter
{
    public int? ProductId {get; set;}
    public int? LocationId {get; set;}
    ...
}

Теперь вы можете сказать, что свойство не должно использоваться, потому что оператор не сделалвыберите значение в поле со списком, присвоив ему значение NULL:

Filter selectedFilter = new Filter()
{
     ProductId = null; // operator didn't select the userId combo
     LocationId = LocationCombo.SelectedValue;
     ...
}

В зависимости от типа используемых полей со списком, SelectedValue может уже иметь значение NULL, если ничего не выбрано, в противном случае вы должны проверитьсебя:

ProductId = ProductCombo.IsValueSelected ?? ProductCombo.Value : null;

Теперь, когда вы изменили класс Filter, легко реализовать метод Filter

 public static IQueryable<Product> Filter(this IQueryable<Product> products, Filter filter)
    {
        // TODO: exception if products == null

        if (filter == null)
        {
            // don't filter, return the original collection:
            return products;
        }
        else
        {
            return products.Where (product =>
                 (filter.ProductId == null || filter.ProductId == product.ProductId)
              && (filter.LocationId == null || filter.LocationId == product.LocationId)
              && ...);
        }
}
    }
...