Home > Back-end >  Best way to create dynamic filter in Entity Framework and SQL Server stored procedure
Best way to create dynamic filter in Entity Framework and SQL Server stored procedure

Time:09-17

We have a procedure that is dynamically filtered and based on the input parameters, it generates the filter dynamically, and in the output, if there is a record for it, it is displayed based on the result set.

CREATE PROCEDURE dbo.GetSalesWithDetails
    (@HistoryID INT = NULL,
     @ShDetailID INT = NULL,
     @DateOfSaleFrom DateTime = NULL,
     @DateOfSaleTo DateTime = NULL,
     @CustomerID INT = NULL)
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @Sql Nvarchar(Max),
            @Paramlist Nvarchar(Max),
            @AllColumn Nvarchar(max)

    SET @Sql = N'
            SELECT  s.HistoryID, 
                    shd.ShDetailID, 
                    s.DateOfSale, 
                    s.SaleCityID, 
                    s.SalesPersonID, 
                    s.CustomerID, 
                    sf.StfName,
                    sf.StfSname, 
                    gd.GoodsName, 
                    cu.CsName, 
                    cu.CsSname, 
                    c.CityName,
                    shd.GoodsQuantity, 
                    shd.UnitPrice, 
                    shd.Discount, 
                    shd.Tax, 
                    shd.OtherCosts, 
                    shd.TotalPriceBefore, 
                    shd.TotalPriceAfterCosts
            FROM        dbo.tblCity AS c INNER JOIN
                        dbo.tblSalesHistory AS s ON c.CityID = s.SaleCityID INNER JOIN
                        dbo.tblCustomers AS cu ON s.CustomerID = cu.CsID INNER JOIN
                        dbo.tblSalesHistoryDetails AS shd ON s.HistiryID = shd.SaleID INNER JOIN
                        dbo.tblStaffs AS sf ON s.SalesPersonID = sf.StfID CROSS JOIN
                        dbo.tblGoods AS gd 
            where 1 = 1 '

IF NOT @HistoryID IS NULL SET @Sql = N' AND HistoryID = @HistoryID '
IF NOT @HistoryID IS NULL SET @Sql = N' AND ShDetailID = @ShDetailID '
IF NOT @CustomerID IS NULL SET @Sql = N' AND CustomerID = @CustomerID '
IF NOT @DateOfSaleFrom IS NULL SET @Sql = N' AND DateOfSale >= @DateOfSaleFrom '
IF NOT @DateOfSaleTo IS NULL SET @Sql = N' AND DateOfSale <= @DateOfSaleTo '

Set @Paramlist = '@HistoryID INT = NULL,
                  @ShDetailID INT = NULL,   
                  @DateOfSaleFrom DateTime = NULL,
                  @DateOfSaleTo DateTime = NULL,
                  @CustomerID INT = NULL'

Exec sp_executesql @Sql ,@Paramlist,
        @HistoryID,
        @ShDetailID,
        @DateOfSaleFrom,
        @DateOfSaleTo,
        @CustomerID
WITH RESULT SETS
((
    SaleHistoryID INT Not Null, 
    ShDetailID INT Not Null, 
    DateOfSale DateTime Not Null, 
    SaleCityID INT Not Null, 
    SalesPersonID INT Not Null, 
    CustomerID INT Not Null, 
    StfName Nvarchar(150) Not Null, 
    StfSname Nvarchar(150) Not Null, 
    GoodsName Nvarchar(150) Not Null, 
    CsName Nvarchar(150) Not Null, 
    CsSname Nvarchar(150) Not Null, 
    CityName Nvarchar(150) Not Null, 
    GoodsQuantity Decimal(18,2) not null, 
    UnitPrice Decimal(18,2) not null, 
    Discount Decimal(18,2) not null, 
    Tax Decimal(18,2) not null, 
    OtherCosts Decimal(18,2) not null, 
    TotalPriceBefore Decimal(18,2) not null, 
    TotalPriceAfterCosts Decimal(18,2) not null
))
END

I have created the same structure used in the procedure in the Entity Framework, but I have encountered problems in creating filters with the Where condition. For example, it wants the conditions like the procedure to be used and the C# method to be able to automatically generate the filter.

public object GetSalesDetailsHistory(int? HistoryID = null, int? ShDetailID = null,
                                             DateTime? DateOfSaleFrom = null, DateTime? DateOfSaleTo = null,
                                             int? CustomerID = null)
{
    EfM.Model1 db = new Model1();

    var Query = (from row1 in db.tblSalesHistories
                 join row2 in db.tblSalesHistoryDetails on row1.HistiryID equals row2.ShDetailID
                 join row3 in db.tblCities on row1.SaleCityID equals row3.CityID
                 join row4 in db.tblCustomers on row1.CustomerID equals row4.CsID
                 join row5 in db.tblStaffs on row1.SalesPersonID equals row5.StfID
                 join row6 in db.tblGoods on row2.GoodsID equals row6.GoodsID
                 select new
                        {
                             row1.HistiryID,
                             row2.ShDetailID,
                             row1.DateOfSale,
                             row1.SaleCityID,
                             row1.SalesPersonID,
                             row1.CustomerID,
                             row5.StfName,
                             row5.StfSname,
                             row6.GoodsName,
                             row4.CsName,
                             row4.CsSname,
                             row3.CityName,
                             row2.GoodsQuantity,
                             row2.UnitPrice,
                             row2.Discount,
                             row2.Tax,
                             row2.OtherCosts,
                             row2.TotalPriceBefore,
                             row2.TotalPriceAfterCosts
                         }
                         ).ToList();
    return Query;
}

The next thing I am facing is that in the dynamic query method in the procedure, when the procedure is complex and heavy, it is very difficult and time-consuming to fix the procedure's bugs and develop or edit it. Is there any other way to filter the list without using dynamic query?

CodePudding user response:

You can do this by checking if a filter exists and if it does, apply it.

CREATE PROCEDURE dbo.GetSalesWithDetails
(
@HistoryID INT = NULL,
@ShDetailID INT = NULL,
@DateOfSaleFrom DateTime = NULL,
@DateOfSaleTo DateTime = NULL,
@CustomerID INT = NULL
)
AS

SELECT  s.HistoryID, 
        shd.ShDetailID, 
        s.DateOfSale, 
        s.SaleCityID, 
        s.SalesPersonID, 
        s.CustomerID, 
        sf.StfName,
        sf.StfSname, 
        gd.GoodsName, 
        cu.CsName, 
        cu.CsSname, 
        c.CityName,
        shd.GoodsQuantity, 
        shd.UnitPrice, 
        shd.Discount, 
        shd.Tax, 
        shd.OtherCosts, 
        shd.TotalPriceBefore, 
        shd.TotalPriceAfterCosts
FROM        dbo.tblCity AS c INNER JOIN
            dbo.tblSalesHistory AS s ON c.CityID = s.SaleCityID INNER JOIN
            dbo.tblCustomers AS cu ON s.CustomerID = cu.CsID INNER JOIN
            dbo.tblSalesHistoryDetails AS shd ON s.HistiryID = shd.SaleID INNER JOIN
            dbo.tblStaffs AS sf ON s.SalesPersonID = sf.StfID CROSS JOIN
            dbo.tblGoods AS gd 
where 
    (@HistoryID IS NULL OR HistoryID = @HistoryID)AND
    (@HistoryID IS NULL OR ShDetailID = @ShDetailID)AND
    (@CustomerID IS NULL OR CustomerID = @CustomerID)AND
    (@DateOfSaleFrom IS NULL OR DateOfSale >= @DateOfSaleFrom)AND
    (@DateOfSaleTo IS NULL OR DateOfSale <= @DateOfSaleTo)

CodePudding user response:

Change your procedure as follows:

CREATE PROCEDURE dbo.GetSalesWithDetails
(
@HistoryID INT = NULL,
@ShDetailID INT = NULL,
@DateOfSaleFrom DateTime = NULL,
@DateOfSaleTo DateTime = NULL,
@CustomerID INT = NULL
)
AS
BEGIN
SET NOCOUNT ON;

SELECT  s.HistoryID, 
                    shd.ShDetailID, 
                    s.DateOfSale, 
                    s.SaleCityID, 
                    s.SalesPersonID, 
                    s.CustomerID, 
                    sf.StfName,
                    sf.StfSname, 
                    gd.GoodsName, 
                    cu.CsName, 
                    cu.CsSname, 
                    c.CityName,
                    shd.GoodsQuantity, 
                    shd.UnitPrice, 
                    shd.Discount, 
                    shd.Tax, 
                    shd.OtherCosts, 
                    shd.TotalPriceBefore, 
                    shd.TotalPriceAfterCosts
            FROM        dbo.tblCity AS c INNER JOIN
                        dbo.tblSalesHistory AS s ON c.CityID = s.SaleCityID INNER JOIN
                        dbo.tblCustomers AS cu ON s.CustomerID = cu.CsID INNER JOIN
                        dbo.tblSalesHistoryDetails AS shd ON s.HistiryID = shd.SaleID INNER JOIN
                        dbo.tblStaffs AS sf ON s.SalesPersonID = sf.StfID CROSS JOIN
                        dbo.tblGoods AS gd 
where 1 = 1
AND (@HistoryID is null OR HistoryID = @HistoryID)
AND (@HistoryID IS NULL OR ShDetailID = @ShDetailID)
AND (@CustomerID IS NULL OR CustomerID = @CustomerID)
AND (@DateOfSaleFrom IS NULL OR DateOfSale >= @DateOfSaleFrom)
AND (@DateOfSaleTo IS NULL OR DateOfSale <= @DateOfSaleTo )

END

and change your EF method as below:

public object GetSalesDetailsHistory(int? HistoryID = null, int? ShDetailID = null,
                                             DateTime? DateOfSaleFrom = null, DateTime? DateOfSaleTo = null,
                                             int? CustomerID = null)
        {
            EfM.Model1 db = new Model1();
            var Query = (from row1 in db.tblSalesHistories
                         join
                         row2 in db.tblSalesHistoryDetails on row1.HistiryID equals row2.ShDetailID
                         join
                         row3 in db.tblCities on row1.SaleCityID equals row3.CityID
                         join
                         row4 in db.tblCustomers on row1.CustomerID equals row4.CsID
                         join
                         row5 in db.tblStaffs on row1.SalesPersonID equals row5.StfID
                         join
                         row6 in db.tblGoods on row2.GoodsID equals row6.GoodsID
                         where 
                         (
                         (HistoryID == null || row1.HistiryID == HistoryID)
                         &&
                         (ShDetailID == null || row2.ShDetailID == ShDetailID)
                         &&
                         (DateOfSaleFrom == null || row1.DateOfSale >= DateOfSaleFrom)
                         &&
                         (DateOfSaleTo == null || row1.DateOfSale <= DateOfSaleTo)
                         &&
                         (CustomerID == null || row1.CustomerID == CustomerID)
                         )

                         select new
                         {
                             row1.HistiryID,
                             row2.ShDetailID,
                             row1.DateOfSale,
                             row1.SaleCityID,
                             row1.SalesPersonID,
                             row1.CustomerID,
                             row5.StfName,
                             row5.StfSname,
                             row6.GoodsName,
                             row4.CsName,
                             row4.CsSname,
                             row3.CityName,
                             row2.GoodsQuantity,
                             row2.UnitPrice,
                             row2.Discount,
                             row2.Tax,
                             row2.OtherCosts,
                             row2.TotalPriceBefore,
                             row2.TotalPriceAfterCosts
                         }
                         ).ToList();
            return Query;
        }

And it is better to create a class as follows instead of return value of object type and change your method as follows:

public class ClassSaleResults
{
    public int HistiryID { set; get; }
    public int ShDetailID { set; get; }
    public DateTime DateOfSale { set; get; }
    public int SaleCityID { set; get; }
    public int SalesPersonID { set; get; }
    public int CustomerID { set; get; }
    public string StfName { set; get; }
    public string StfSname { set; get; }
    public string GoodsName { set; get; }
    public string CsName { set; get; }
    public string CsSname { set; get; }
    public string CityName { set; get; }
    public decimal GoodsQuantity { set; get; }
    public decimal UnitPrice { set; get; }
    public decimal Discount { set; get; }
    public decimal Tax { set; get; }
    public decimal OtherCosts { set; get; }
    public decimal TotalPriceBefore { set; get; }
    public decimal TotalPriceAfterCosts { set; get; }
}

 public List<ClassSaleResults> GetSalesDetailsHistory(int? HistoryID = null, int? ShDetailID = null,
                                             DateTime? DateOfSaleFrom = null, DateTime? DateOfSaleTo = null,
                                             int? CustomerID = null)
        {
            EfM.Model1 db = new Model1();
            List<ClassSaleResults> Query = (from row1 in db.tblSalesHistories
                         join
                         row2 in db.tblSalesHistoryDetails on row1.HistiryID equals row2.ShDetailID
                         join
                         row3 in db.tblCities on row1.SaleCityID equals row3.CityID
                         join
                         row4 in db.tblCustomers on row1.CustomerID equals row4.CsID
                         join
                         row5 in db.tblStaffs on row1.SalesPersonID equals row5.StfID
                         join
                         row6 in db.tblGoods on row2.GoodsID equals row6.GoodsID
                         where 
                         (
                         (HistoryID == null || row1.HistiryID == HistoryID)
                         &&
                         (ShDetailID == null || row2.ShDetailID == ShDetailID)
                         &&
                         (DateOfSaleFrom == null || row1.DateOfSale >= DateOfSaleFrom)
                         &&
                         (DateOfSaleTo == null || row1.DateOfSale <= DateOfSaleTo)
                         &&
                         (CustomerID == null || row1.CustomerID == CustomerID)
                         )

                         select new ClassSaleResults()
                         {
                             HistiryID = row1.HistiryID,
                             ShDetailID = row2.ShDetailID,
                             DateOfSale = row1.DateOfSale,
                             SaleCityID = row1.SaleCityID,
                             SalesPersonID = row1.SalesPersonID,
                             CustomerID = row1.CustomerID,
                             StfName = row5.StfName,
                             StfSname = row5.StfSname,
                             GoodsName = row6.GoodsName,
                             CsName = row4.CsName,
                             CsSname = row4.CsSname,
                             CityName = row3.CityName,
                             GoodsQuantity = row2.GoodsQuantity,
                             UnitPrice = row2.UnitPrice,
                             Discount = row2.Discount,
                             Tax = row2.Tax,
                             OtherCosts = row2.OtherCosts,
                             TotalPriceBefore = row2.TotalPriceBefore,
                             TotalPriceAfterCosts = row2.TotalPriceAfterCosts
                         }
                         ).ToList<ClassSaleResults>();
            return Query;
        }

With this method, you can access the structure and more details of the list and class.

  • Related