通过3个下拉过滤器过滤客户记录的更好或优化方式

时间:2016-02-11 07:12:29

标签: c# asp.net performance linq optimization

我有一个页面名称: CustomerList.aspx ,我在其中显示客户列表。

这也是我的表格和类文件:

public partial class Customer
    {
        public int CustomerID { get; set; }
        public string FullName { get; set; }
        public string EmailId { get; set; }
        public int CustomerLocation { get; set; }
        public bool IsActive { get; set; }
        public bool Removed { get; set; }
        public DateTime SubscribeDate { get; set; }
        public Location _Location;
    }

    public partial class Location
    {
        public int LocationId { get; set; }
        public string Name { get; set; }
    }
  

Inactive = true:表示客户在系统中处于活动状态   Inactive = false:表示客户在系统中处于非活动状态。

     

Removed = true:表示客户已从系统中删除

     

Removed = false:表示客户未从系统中删除。

我将为用户提供3个过滤器来过滤客户记录。

1)位置下拉

<select>
<option Text="All" Value="0" selected="true">
<option Text="London" Value="1">
<option Text="America" Value="2">
</select>

2)状态下拉列表,其值为:全部,有效,无效:

<select>
<option Text="All" Value="0" selected="true">
<option Text="Active" Value="1">
<option Text="Inactive" Value="2">
</select>

3)统计数据下拉

<select>
<option Text="All" Value="all" selected="true">
<option Text="Active Customers" Value="all">
<option Text="Recent subscribe customers" Value="subscribe">
<option Text="Recent unsubscribe customers" Value="unsubscribe">
</select>

当我的页面加载时,我想在我的网格中显示客户列表。

这是我的代码:

 public void DisplayCustomersList()
        {
           DataTable list=GetCustomers(Convert.ToInt16(ddlLocation.SelectedValue),Convert.ToInt16(ddlStatus.SelectedValue),ddlstats.SelectedValue);
           Grid1.DataSource = list;
           Grid1.DataBind();
        }


  public DataTable GetCustomers(int LocationId, int ActiveId, string stats)
        {
            using (var context = new MyContext())
            {
                var data = from c in context.Customers
                           where c.Removed == false
                           select new
                           {
                               FullName = c.FullName,
                               c.CustomerID,
                               c._Location.Name,
                               c.IsActive,
                               c.SubscribeDate,
                               c.Removed
                           };
                if (LocationId != 0 && ActiveId != 0)
                {
                    if (ActiveId == 1)
                    {
                        return
                            MyContext.CopyToDataTable(
                                data.Where(x => x.LocationId == LocationId && x.IsActive == true && x.Removed == false));
                    }
                    else if(ActiveId==2)
                    {
                        return
                           MyContext.CopyToDataTable(
                               data.Where(x => x.LocationId == LocationId && x.IsActive == false && x.Removed == false));
                    }
                    return
                        MyContext.CopyToDataTable(
                            data.Where(x => x.LocationId == LocationId && x.Removed==false));
                }

                if (LocationId != 0 && stats != "")
                {
                    if (stats == "all")
                    {
                        return
                            MyContext.CopyToDataTable(
                                data.Where(x => x.LocationId == LocationId && x.IsActive == true && x.Removed == false));
                    }
                    else if (stats == "subscribe")
                    {
                        return
                           MyContext.CopyToDataTable(
                               data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.LocationId==LocationId));
                    }
                }

                if (ActiveId != 0 && stats != "")
                {
                    if(ActiveId==1)
                    {
                        if(stats=="all")
                        {
                            return
                            MyContext.CopyToDataTable(
                                data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == true) || (x.Removed == false) || (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false)));
                        }
                        else if (stats == "subscribe")
                        {
                            return
                               MyContext.CopyToDataTable(
                                   data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == true));
                        }
                        else if (stats == "unsubscribe")
                        {
                            return
                              MyContext.CopyToDataTable(
                                  data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
                        }

                    }
                    else if(ActiveId==2)
                    {
                        if (stats == "all")
                        {
                            MyContext.CopyToDataTable(
                                data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false) && (x.Removed == false)));
                        }
                        else if (stats == "subscribe")
                        {
                            return
                               MyContext.CopyToDataTable(
                                   data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
                        }
                        else if (stats == "unsubscribe")
                        {
                            return
                              MyContext.CopyToDataTable(
                                  data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
                        }
                    }
                }

                if (stats != "")
                {
                    if (stats == "all")
                    {
                        return
                            MyContext.CopyToDataTable(
                                data.Where(x => x.IsActive == true && x.Removed == false));
                    }
                    else if (stats == "subscribe")
                    {
                        return
                           MyContext.CopyToDataTable(
                               data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive==true));
                    }
                    else
                    {
                        return
                       MyContext.CopyToDataTable(
                           data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.IsActive == false && x.Removed == false));
                    }

                }
            }
        }

在所有3 dropdown selected index change event我只是这样调用此函数:DisplayCustomersList()

所以我只想问你这是一种执行过滤器的正确方法,或者这种代码可以更好地进行优化。

任何人都可以为我提供更好的解决方案,或者如果可能的话更好地优化这些代码???

4 个答案:

答案 0 :(得分:3)

可以在没有PredicateBuilder的情况下优化此问题,但需要仔细和“系统”分析。

首先......考虑你的谓词制定决定因素

考虑一下你的情况,将所有14个一起放在一起,你实际上可以看到你只有三个决定因素,即:LocationIdActiveIdstats

No  LocationId  ActiveId    stats       result
1   not 0       1           don't care  data.Where(x => x.LocationId == LocationId && x.IsActive == true && x.Removed == false)
2   not 0       2           don't care  data.Where(x => x.LocationId == LocationId && x.IsActive == false && x.Removed == false));
3   not 0       not 0-2     don't care  data.Where(x => x.LocationId == LocationId && x.Removed == false));
4   not 0       don't care  all         data.Where(x => x.LocationId == LocationId && x.IsActive == true && x.Removed == false)
5   not 0       don't care  subscribe   data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.LocationId == LocationId));
6   don't care  1           all         data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == true) || (x.Removed == false) || (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false)));
7   don't care  1           subscribe   data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == true));
8   don't care  1           unsubscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
9   don't care  2           all         data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false) && (x.Removed == false)));
10  don't care  2           subscribe   data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
11  don't care  2           unsubscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));
12  don't care  don't care  all         data.Where(x => x.IsActive == true && x.Removed == false));
13  don't care  don't care  subscribe   data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == true));
14  don't care  don't care  unsubscribe data.Where(x => x.SubscribeDate >= DateTime.Now.AddDays(-7) && x.Removed == false && x.IsActive == false));

接下来,考虑结果的模式

我观察到你的结果是非常确定的,只有很少的例外。除了结果no 69之外,您的查询谓词实际上可以分为四个基本组件(69省略)。他们是:

comp1: x.LocationId == LocationId
comp2: x.IsRemoved == false
comp3: x.IsActive == true
comp4: x.SubscribeDate >= DateTime.Now.AddDays(-7)

查询逻辑很简单:

comp1 && comp2 && comp3 && comp4

将它们与12个案例(69除外)放在一起,你会得到:

Simplification:
DC = don't care
A = applied
NA = not applied

                                        QueryComponents
No  LocationId  ActiveId    stats       comp1   comp2   comp3   comp4
1   not 0       1           DC          A       A       Yes     NA
2   not 0       2           DC          A       A       No      NA
3   not 0       not 0-2     DC          A       A       NA      NA
4   not 0       DC          all         A       A       Yes     NA
5   not 0       DC          subscribe   A       A       NA      A
7   DC          1           subscribe   NA      A       Yes     A
8   DC          1           unsubscribe NA      A       No      A
10  DC          2           subscribe   NA      A       No      A
11  DC          2           unsubscribe NA      A       No      A
12  DC          DC          all         NA      A       Yes     A
13  DC          DC          subscribe   NA      A       Yes     A
14  DC          DC          unsubscribe NA      A       No      A

在所有分解后,我们可以看到Mapping

现在,可以看到查询组件可以与决定因素一起映射回来:

comp1: Applied only when LocationId is not 0
comp2: Always applied //this is very good!
comp3: Yes = 1, 4, 7, 12, 13; NA = 3, 5; No = 2, 8, 10, 11, 14
comp4: Not Applied when LocationId is 0 except on case 5

您可以开始将概念翻译成代码......

因此,我们可以帮助标志(其中有4个)来确定是否应该包含查询组件,如下所示:

bool LocationIdNotApplied = LocationId == 0; //for comp1
bool IsActiveNotApplied = LocationId != 0 && (ActiveId < 0 || ActiveId > 2 || stats = "subscribe"); //for comp3 to be applied or not
bool IsActiveFalse = (LocationId != 0 && ActiveId == 2) || stats == "unsubscribe" || (ActiveId == 2 && stats == "subscribe"); //for comp3 to be false
bool DateApplied = LocationId == 0 || (LocationId != 0 && stats == "subscribe");

然后,除data.Where6之外的所有案例9都可以这样简化:

data.Where(x => (x.LocationId == LocationId || LocationIdNotApplied) //comp1
  && x.IsRemoved == false //comp2
  && ((x.IsActive == !IsActiveFalse) || IsActiveNotApplied) //comp3
  && (x.SubscribeDate >= DateTime.Now.AddDays(-7) || !DateApplied)) //comp4

对于12例成为1例,这是一个显着的简化,你只需要添加另外两个案例,总共3个案例,而不是原来的14个案例!

将它们放在一起代码

public DataTable GetCustomers(int LocationId, int ActiveId, string stats)
{
    using (var context = new MyContext())
    {
        var data = from c in context.Customers
                   where c.Removed == false
                   select new
                   {
                       FullName = c.FullName,
                       c.CustomerID,
                       c._Location.Name,
                       c.IsActive,
                       c.SubscribeDate,
                       c.Removed
                   };

        bool LocationIdNotApplied = LocationId == 0; //for comp1
        bool IsActiveNotApplied = LocationId != 0 && (ActiveId < 0 || ActiveId > 2 || stats = "subscribe"); //for comp3 to be applied or not
        bool IsActiveFalse = (LocationId != 0 && ActiveId == 2) || stats == "unsubscribe" || (ActiveId == 2 && stats == "subscribe"); //for comp3 to be false
        bool DateApplied = LocationId == 0 || (LocationId != 0 && stats == "subscribe");

        if(LocationId == 0 && ActiveId == 1 && stats == "all"){ //case 6
            return MyContext.CopyToDataTable(
                     data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == true) || (x.Removed == false) || (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false)));          
        } else if (LocationId == 0 && ActiveId == 2 && stats == "all"){ //case 9
            return MyContext.CopyToDataTable(
                     data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false) && (x.Removed == false)));
        } else { //other cases
            return MyContext.CopyToDataTable(
                     data.Where(x => (x.LocationId == LocationId || LocationIdNotApplied) //comp1
                       && x.IsRemoved == false //comp2
                       && ((x.IsActive == !IsActiveFalse) || IsActiveNotApplied) //comp3
                       && (x.SubscribeDate >= DateTime.Now.AddDays(-7) || !DateApplied))) //comp4
        }
    }        
}

最后笔记

你的案子6实际上对我来说很奇怪:

data.Where(x => (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == true) || (x.Removed == false) || (x.SubscribeDate >= DateTime.Now.AddDays(-7) || x.IsActive == false)));

请注意x.IsActive == true同时拥有x.IsActive == falsex.SubscribeDate >= DateTime.Now.AddDays(-7)。然后将其与||结合使用。这就像说:

(A || true) || (A || false)

无论如何,总是return true。您可能想再次检查,甚至可以进一步简化/

最后备注和道歉

因此,在没有PredicateBuilder的情况下解决这个问题 - 它需要仔细和“系统地”(或者,我实际上是指逐步)分析所有可能的案例。

我必须为OP道歉,因为我无法完全测试我提出的缺乏完整测试资源的代码(与OP不同)。

但是如果OP发现我错过了处理的情况,OP在原始问题中没有提出,至少,我上面的解决方案中提出的步骤应该是仍然有用让OP对自己的实际案例进行自己的仔细分析。

答案 1 :(得分:2)

您可以在GetCustomer方法中优化搜索条件。请看看Linqkit

此处为基本示例

http://www.albahari.com/nutshell/predicatebuilder.aspx https://www.nuget.org/packages/LinqKit/

使用PredicateBuilder

以下是使用PredicateBuilder解决上述示例的方法:

IQueryable<Product> SearchProducts (params string[] keywords)
{
  var predicate = PredicateBuilder.False<Product>();

  foreach (string keyword in keywords)
  {
    string temp = keyword;
    predicate = predicate.Or (p => p.Description.Contains (temp));
  }
  return dataContext.Products.Where (predicate);
}

答案 2 :(得分:1)

首先,我不再使用DataTable了,而是创建一个像这样的ViewModel:

public class CustomerVm
{
    public string FullName { get; set; }
    public int CustomerID  { get; set; }
    public string LocationName  { get; set; }
    public bool IsActive  { get; set; }
    public DateTime SubscribeDate  { get; set; }
} 

然后创建一个基本查询来抓住客户:

var data = from c in context.Customers
           where c.Removed == false;

根据您的下拉值操纵查询的 where 条件:

if(ActiveId == 1)
{
    data = data.Where(c => c.IsActive);    
}
else if(ActiveId == 1)
{
    data = data.Where(c => c.IsActive);  
}

if (LocationId != 0)
{
    data = data.Where(c => c.LocationId == LocationId);  
}

if (stats == "subscribe")
{
    data = data.Where(c => c.IsActive 
      && c.SubscribeDate >= DateTime.Now.AddDays(-7));  
}
else if (stats == "unbsubscribe")
{
    data = data.Where(c => !c.IsActive 
      && c.SubscribeDate >= DateTime.Now.AddDays(-7));  
}

最后,执行查询并将结果作为ViewModels返回:

return data.Select(c => new CustomerVm {
    FullName = c.FullName,
    CustomerId = ...
});

答案 3 :(得分:1)

我发现了一种更具可扩展性的不同方法。性能方面真正的重大改进是在纯SQL中实际执行此过滤,并仅将匹配数据移动到应用程序逻辑。

我能够通过以下方式使这种查询速度提高100倍(使用Oracle):

  • 添加接受搜索条件的pl / sql过程
  • 构建动态SQL语句并将搜索条件绑定在那里
  • 确保没有“空标准”,即如果未设置位置,我们不构建where(location = in_location或in_location为null)
  • 这给出了一些固定的SQL,其中将缓存文本和执行计划

有关https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1669972300346534908

基础知识的更多信息

数据库是为了在数以万计的记录上进行数据访问,过滤和数据加入非常好而构建的。我发现利用它可以提供超过任何ORM工具的竞争优势,这种工具具有严重的局限性(放弃数据库端处理的整个功能,你放弃所有高级SQL例如分区,你无法访问Oracle文本等技术,因为你无法控制查询放松。)