Based on the business object (list) Screening

Business Object based screening

introduction

It may be too familiar with SQL statements, although it may have been over-Asp to Asp.Net from the era, but the concept has not changed much Asp. The result is that we will have most of the logic of the application to the database to complete, forget the .Net Framework provides us flexible and powerful data manipulation capabilities. For example, when we need to filter the data, we think of "Where" clause, instead of List <T> .FindAll (); when we need to sort the data, we think of "Order By "clause, instead of List <T> .Sort (); when we need to page through the data we think of a stored procedure, instead of List <T> .GetRange ().

Of course, let the database to complete these tasks in many cases would be very efficient, especially when large amounts of data. However, in the case of small amount of data, all data will be removed once and then cached on the server for later sorting, filtering, paging request only for the cache, then the efficiency will improve a lot.

No single method is absolutely good or absolutely bad, there is a scope. This article will introduce is the same, when the amount of data is very large, we may not want the database to use "Where" clause to filter, and then screened again returns only the data entries in the current page to be displayed.

This article is just the same question another solution ideas, when to use the case may be.

Are you still assembling SQL statements? The traditional way of screening data

Filter the data, but should be the most common operations, and we Orders Orders table NorthWind database as an example. If we need it to filter by year, month, day in any combination, you probably will how to do it? I think it should be like this:

  1. Create three drop-down box on the page, used to select the year, month, and day.
  2. The first time a user visits the page, displays all the data. At this time there is an access to the database and return all the data, SQL statements, such as "Select * From Orders".
  3. Users select the year, month, any one of the Japan-China, producing PostBack.
  4. According to the user's selection assembled a SQL statement, such as "Where Year (OrderDate) = @Date and Month (OrderDate) = @Month".
  5. Sending SQL statements to the database, the database returns the query results, display to the user interface.
  6. And so forth.

It can be seen in the above mode, in order to show different data according to user's choice, every time the user's operation should be carried out once access to the database, we look at the specific implementation is like.

Assembled typical implementation of SQL statements

First, create a page (SqlApproach.aspx), placed on the page three DropDownList control, a GridView, and a ObjectDataSource control, as shown below:

Order to create business objects based on the Orders table in the App_Code folder (located Order.cs).

public class Order
{
    private int orderId;            // 订单Id
    private string customerId;          // 用户Id
    private DateTime orderDate;         // 下单日期
    private string country;             // 国家

    public int OrderId {
       get { return orderId; }
       set { orderId = value; }
    }

    public string CustomerId
    {
       get { return customerId; }
       set { customerId = value; }
    }

    public DateTime OrderDate
    {
       get { return orderDate; }
       set { orderDate = value; }
    }

    public string Country
    {
       get { return country; }
       set { country = value; }
    }
}

For the set (list, or call the line set) data, we use List <Order> to store. Next, create a second file in the directory App_Code OrderManager.cs to return the result set, populate the list from a database, typically contains code similar to this:

public class OrderManager
{
    // 根据SQL语句获取列表对象
    public static List<Order> GetList(string query)
    {
       List<Order> list = null;
       SqlDataReader reader = ExecuteReader(query);

       if (reader.HasRows)   {
           list = new List<Order>();
           while (reader.Read()){
              list.Add(GetItem(reader));
           }
       }

       reader.Close();

       return list;
    }


    // 获取一个SqlDataReader对象
    private static SqlDataReader ExecuteReader(string query)
    {
       string connString = ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString;
       SqlConnection conn = new SqlConnection(connString);
       SqlCommand cmd = new SqlCommand(query, conn);
       conn.Open();
       SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);

       return reader;
    }

    // 从一行获取一个Order对象
    private static Order GetItem(IDataRecord record)
    {
       Order item = new Order();
       item.OrderId = Convert.ToInt32(record["orderId"]);
       item.CustomerId = record["CustomerId"].ToString();
       item.OrderDate = Convert.ToDateTime(record["OrderDate"]);
       item.Country = record["ShipCountry"].ToString();
       return item;
    }
}

The above code is well understood: GetList () method accepts a query, then returns List <Order> list. Inside GetList () method, call the ExecuteReader () method ExecuteReader () method to create a query and returns a SqlDataReader object passed in accordance with the query, which is used to read the data returned by the database. While in the statement, calling the GetItem () method, which creates an Order object based on each data line. Finally, add the Order object to the List <Order> list, and then return to the list.

Visible, we need to call a method on the page is GetList (query) method, we look at the main code page file SqlFilter.aspx of:

<asp:ObjectDataSource ID="objdsOrderList" runat="server" SelectMethod="GetList"
     TypeName="OrderManager" OnSelecting="objdsOrderList_Selecting">
     <SelectParameters>
       <asp:Parameter Name="query" Type="string" />
     </SelectParameters>
</asp:ObjectDataSource>

Use as GetList ObjectDataSource SelectCommand, ObjectDataSource ID that will be used for the DataSourceID GridView.

Now we continue to look SqlFilter.aspx post-code is usually what (DropDownList when we set the Text to "All" when its Value "0"):

public partial class SqlApproach : System.Web.UI.Page
{
    public int Year{
       get { return Convert.ToInt32(ddlYear.SelectedValue); }
    }

    public int Month{
       get { return Convert.ToInt32(ddlMonth.SelectedValue); }
    }

    public int Day{
       get { return Convert.ToInt32(ddlDay.SelectedValue); }
    }
   
    // 获取查询语句
    public string QuerySql
    {
       get
       {
           int year = Year;
           int month = Month;
           int day = Day;

           List<string> sqlList = new List<string>();
           string subSql = string.Empty;
          
           IF (! year = 0)
              sqlList.Add (the String.Format ( "Year (the OrderDate) = {0}", year));

           IF (! = month The 0)
              sqlList.Add (the String.Format ( "Month (the OrderDate) {0} = ", month The));

           IF (! = Day 0)
              sqlList.Add (the String.Format (" Day (the OrderDate) = {0} ", Day));

           IF (sqlList.Count> 0) // If you select any of a drop-down box, then assembled Sql statement
           {
              String [] = sqlList.ToArray List ();
              subSql = "the Where (" + String.Join ( "and", List) + ")";
           }
           // SQL statement returns the assembled
           return "Select CustomerId, ShipCountry, OrderDate           , OrderId From Orders" + subSql;          
       }
    }

    // page load event
    protected void the Page_Load (Object SENDER, EventArgs E)
    {
        IF (the IsPostBack!)
        {
            AppendListItem (ddlMonth, 12); // a total of 12 months
            AppendListItem (ddlDay, 31); // default 31 days       
        }
    }
   
    // month changes
    protected void ddlMonth_SelectedIndexChanged (SENDER Object, EventArgs E) {
       gvOrderList.DataBind ();
    }

    // year changes
    protected void ddlYear_SelectedIndexChanged (SENDER Object, EventArgs E) {
       gvOrderList.DataBind ();
    }

    // change days
    protected void ddlDay_SelectedIndexChanged (object sender, EventArgs e ) {
       gvOrderList.DataBind ();
    }

    // add the list items
    protected void AppendListItem (the ListControl List, End int) {
       for (int I =. 1; I <= End; I ++) {
           list.Items.Add (new new the ListItem (I. the ToString ()));
       }
    }

    // will call each list PostBack gvOrderList.DataBind (), then triggers here
// turn calls the OrderManager.GetList (query) method, return data from the database.
    void objdsOrderList_Selecting protected (Object SENDER, ObjectDataSourceSelectingEventArgs E) {
       e.InputParameters [ "Query"] = querySQL;
    }
}

This code uses the Year, Month, Day three properties were acquired year, month value, day of DropDownList. Contained in the main logical QuerySql property, the SQL statements which perform assembly according to the state of the three lists. Finally, in the ObjectDataSource's Selecting event, the delivery method QuerySql in, get a list of objects, and then displayed on the page.

NOTE : To make the code simple, I do not have to deal with similar 1997-2-30 such a special day. Even with this date as a query only returns an empty list, the program does not make mistakes, because this is just a sample program, I think we can also accept.

Business Object based screening

Learn the traditional screening based on assembling SQL statements, and now we look at an object-based screening is kind of how, and how to improve performance.

  1. Create three drop-down box on the page, used to select the year, month, and day.
  2. The first time a user visits the page, displays all the data. At this time there is an access to the database and return all the data, SQL statements, such as "Select * From Orders".
  3. The returned data (already converted to List <Order> business objects), all of the cache.
  4. The user's selection of the cache List <Order> screening, return filtered results, displayed on the page.
  5. So again, each subsequent user request only carried out for the cache.

Caching policy

Because here we use the cache, it is necessary to discuss the cache. We know that there are three cache, one is OutputCache, one is based on data caching data source control, one is based on the object cache System.Web.Caching.Cache class. Of the three cache, OutputCache and data caches and cache expiration policy can be applied SqlCacheDependency, SqlCacheDependency simpler say is that when data changes in the database cache dependency to this database (table) is automatically expire, SqlCacheDependency divided into Sql Server2000-based polling (polling) pull mechanism, and Sql two different strategies based on the notification (notification) push mechanism Server2005. SqlCacheDependency expiration policy can not be applied when applying System.Web.Caching.Cache, can only change to a file-based applications or other items to change the Cache expiration policy.

NOTE : Sql Server2000 polling (Polling) Asp.Net mechanism means that the process time to time to carry out a process of this database access, because the time interval is fixed, so called poll (access time in milliseconds, You can set the Web.Config Lane). Find data on a visit and not the same, then the cache expires immediately rely on this data when a particular poll. Notification (Notification) Sql Server2005 mechanism is said Asp.Net just do their own thing, not database for questioning process, and when the data in the database are changed, Sql Server 2005 process Asp.Net proactive notification process, the data tell it happened change, then let Asp.Net cache expires. Thus, the efficiency of the notification mechanism using Sql Server2005 much higher.
This article is not about how to turn SqlDependency, you can read reference books.

When I mentioned the cache you may feel for screening based business objects I use the cache, and for assembling SQL way I did, this does not seem fair to compare them, then I now listed tables for their respective applications when the performance cache to make a comparison (SqlDependency polling mechanism using SqlServer 2000):

Cache name SQL-based screening assembly Screening based business objects
OutputCache
VaryByParam="*"
Duration="600"
When fewer options when the drop-down box meaningful comparison, in the buffer period, not access to the database. But when more options, more pages will be cached, still have access to the database when you first visit multiple pages cached result, the effect is not good enough. Database data changes, the cache does not expire. It does not make sense, because the business object has been read from the cache. Database data changes, the cache does not expire.
OutputCache
VaryByParam="*"
Duration="999999"
SqlDependency="Northwind:Orders"
And the same, but when the data changes will make the cache expires. It does not make sense, when the data changes in the database, make the page cache expires, page caching asked to re-load the data, but the data is still reload the objects from the cache. As a result even if the database is changed, the page displays the results has not changed.
ObjectDataSource
EnableCaching="true"
CacheDuration="600"
Within the cache valid time, the drop-down list of functional failure. Because in Cache validity, GridView's DataBind () method does not re-read the data source data (source data does not trigger Selecting event), in other words, the data source does not call the GetList (query) method, so the list of functional failure. Sql assembly method and effect as a list of failures.
ObjectDataSource
EnableCaching="true"
CacheDuration=" infinite"
SqlDependency="Northwind:Orders"
List of failures, the same effect as above, the only difference is the change in the database cache expires (first visit after the first failure, a list of valid). List of failures, like Sql assembly method. The difference is SqlDependency also fail because when the data changes in the database, the data cache expires, the data source to re-read data, but the data still comes from the cache.
Catch
Insert("fullList", List<Order>)
基本不可实施(对每次返回结果进行缓存,效果基本等同于全部返回,且非常麻烦) 本文对象即是应用此方法缓存。

很明显,本文使用的方法的问题就是:当数据库数据变动时,缓存不能够即时过期。解决方法有两种:一个是使用Cache.Insert()的重载方法,设置缓存的自动过期时间(时间设的短了缓存优势不明显,时间设的长了数据变化不能即时反应);还有一个是在对数据库进行增删改时使用Cache.Remove()手动移除缓存(比较麻烦容易遗漏)。

本文不是讲述如何使用缓存的,上面是让大家了解使用缓存都会发生哪些可能,只要知道使用Cache.Insert(key,value)方法可以添加缓存就可以了。最后再说一下当我们使用Cache.Insert(key,value)插入缓存时,虽然没有设置过期时间,但是当服务器内存空间不足的时候,依然会将缓存移除。

对业务对象进行筛选

基于业务对象筛选其实就是基于List<Order>进行筛选(当然你的业务对象也可能不是List<Order>),思路似乎很简单,我们先通过一个重载的GetList()方法获取全部列表,在这个GetList()方法中应用缓存。然后遍历业务对象,选出它符合条件的项目,然后将符合条件的项目加入到新列表中,最后返回新列表。

// 获取全部列表
public static List<Order> GetList() {
    List<Order> list = HttpContext.Current.Cache["fullList"] as List<Order>;

    if (list == null) {
       list = GetList("Select OrderId, CustomerId, ShipCountry, OrderDate From Orders");
       // 添加缓存,永不过期(可以在删除、更新操作时手动让缓存过期)
       HttpContext.Current.Cache.Insert("fullList", list);
    }

    return list;
}

// 根据一个全部项目的列表,以及年、月、日对列表进行筛选
public static List<Order> GetList(List<Order> fullList, int year, int month, int day)
{
    List<Order> list = null;
    bool canAdd;      // 标记变量,说明当前项目是否符合添加的条件

    if (fullList != null)
    {
       list = new List<Order>();

       foreach (Order item in fullList)
       {
           canAdd = true;

           if (year != 0 && year != item.Date.Year)
              canAdd = false;

           if (month != 0 && month != item.Date.Month)
              canAdd = false;

           if (day != 0 && day != item.Date.Day)
              canAdd = false;

           if (canAdd == true)      // 如果全部条件满足,那么加入列表
              list.Add(item);
       }
    }

    return list;
}

上面无参数的GetList()方法在没有缓存的情况下调用GetList(query)方法,返回全部列表,然后加入缓存;有缓存的情况下直接使用缓存中的数据。在GetList(fullList, year, month, day)方法中,根据 年、月、日 对传递进去的列表(全部列表)进行了筛选。

使用List<T>.FindAll(Predicate<T> match)进行筛选

上面的方法虽然可以完成任务,但是不够好,为什么呢?

  1. 我们将筛选的条件(年、月、日)紧耦合到了GetList()方法中,如果日后想添加对其他列,比如国家的筛选,那么我们的方法签名就需要改变(添加国家),而所有调用GetList()方法的地方都需要修改。
  2. 代码没有重用,针对年、月、日来进行筛选是一项很常见的任务,我们应该把这部分封装起来,以后对其他的业务对象进行筛选时,使这些代码可以重用。

实际上,这些问题.Net Framework已经为我们想到了,并在List<T>上提供了一个FindAll(Predicate<T> match)方法来进行筛选的工作,而Predicate<T>类型的参数,封装了筛选的规则。Predicate<T>是一个泛型委托,这意味着match参数是一个返回bool类型的方法,在FindAll()内部,会调用我们传递进去的这个方法。

public delegate bool Predicate<T>(T obj);

NOTE:我看到过这样的一句话,是问Librariy和Framework的区别是什么?回答是:我们调用Librariy的方法,但是Framework调用我们的方法(当然我们也会调用Framework)。可见Framework是一个扩展性和弹性很高的东西,在很多地方我们可以将自己的代码融入到Framework中去。

现在我们来看下如何定义满足 Predicate<T>委托的方法。如果我们将方法写在OrderManager类的内部,那么似乎可以这样写:

// 进行数据筛选的主要逻辑
public bool MatchRule(Order item)
{
    if (year != 0 && year != item.Date.Year)
       return false;

    if (month != 0 && month != item.Date.Month)
       return false;

    if (day != 0 && day != item.Date.Day)
       return false;

    return true;
}

可实际上,你发现没有地方来传递year, month, day参数,因为Predicate<T>(T obj)要求只接受一个参数,在这里是Order类型的item。所以,实际上我们要对这个方法进行一个简单的封装,让我们可以传递year, month, day参数进去。在进行封装之前,我们应该考虑:对于年、月、日的筛选是很常见的操作,我们要让代码重用。

我们先定义一个接口,这个接口仅要求返回一个DateTime类型的属性Date,对于所有实现了这个接口的类,都应该可以使用我们的筛选方法(一个没有日期的对象显然不能按年、月、日筛选)。

public interface IDate
{
    DateTime Date { get; }
}

此时我们的Order类也应该进行修改,让它来实现这个接口,我们只需要它返回orderDate字段就可以了:

public class Order :IDate
{  
    // ... 略
    public DateTime Date
    {
       get { return orderDate; }
    }
}

接下来定义可以用于筛选的类,创建一个DateFilter.cs文件:

// 用于按照年、月、日筛选列表的泛型类,基类
public class DateFilter<T> where T : IDate
{
    private int year;
    private int month;
    private int day;

    public DateFilter(int year, int month, int day)
    {
       this.year = year;
       this.month = month;
       this.day = day;
    }
    // 方便使用的一组构造函数
    public DateFilter(DateTime date) : this(date.Year, date.Month, date.Day) { }
    public DateFilter(int year, int month) : this(year, month, 0) { }
    public DateFilter(int year) : this(year, 0, 0) { }
    public DateFilter() : this(0, 0, 0) { }
   
    // 进行数据筛选的主要逻辑
    public virtual bool MatchRule(T item)
    {
       if (year != 0 && year != item.Date.Year)
           return false;

       if (month != 0 && month != item.Date.Month)
           return false;

       if (day != 0 && day != item.Date.Day)
           return false;

       return true;
    }
}

可以看到,Predicate<T>委托类型的方法MatchRule和前面几乎没有区别,唯一的不同是改成了虚拟方法,以便在子类中覆盖它,以支持对更多列(属性)的筛选。还有值得注意的地方是这个泛型类使用了约束,我们要求类型参数T必须实现IDate接口。

实际上这个类通常用作基类(也可以直接使用,非抽象类),现在来看下如果我们希望可以对Country也进行筛选,应该如何扩展它:

// 可以添加对国家的筛选
public class OrderFilter : DateFilter<Order>
{
    private string country;

    public OrderFilter(int year, int month, int day, string country)
       : base(year, month, day)     // 调用基类构造函数
    {
       this.country = country;
    }

    public override bool MatchRule(Order item)
    {
       // 先获取基类关于日期的对比结果
       bool result = base.MatchRule(item);

       if (result == false)     // 如果日期都不满足,直接返回false
           return false;

       // 继续进行 country 的对比
       if (String.IsNullOrEmpty(country) || string.Compare(item.Country, country, true) == 0)
       {
           return true;     
       } else
       {
           return false;
       }
    }
}

页面实现

我们现在为OrderManager类添加一个新方法,使用我们上面创建的OrderFilter,看看它是什么样的,它仅仅是在fullList上调用了FindAll()方法,传递了我们自定义的DateFilter,然后返回了结果:

// 获取列表对象,使用 filter 作为筛选的条件
public static List<Order> GetList(List<Order> fullList, DateFilter<Order> filter)
{
    List<Order> list = null;
    if (fullList != null)
    {
       list = fullList.FindAll(new Predicate<Order>(filter.MatchRule));
    }
    return list;
}

在ObjFilter.aspx页面上布局与使用拼装SQL几乎没有区别,ObjectDataSource控件的属性有一些变化:

<asp:ObjectDataSource ID="objdsOrderList" runat="server" SelectMethod="GetList"
    TypeName="OrderManager" OnSelecting="objdsOrderList_Selecting">
    <SelectParameters>
       <asp:Parameter Name="fullList" Type="Object" />
       <asp:Parameter Name="filter" Type="Object" />
    </SelectParameters>
</asp:ObjectDataSource>

调用了新的重载了的GetList()方法。然后我们看一下CodeBehind文件上如何进行设置ObjectDataSource的Selecting事件:

// 属性,获取用于筛选的对象
public DateFilter<Order> Filter {
    get {
       DateFilter<Order> filter = new OrderFilter(Year, Month, Day, Country);
       return filter;
    }
}

// 设置参数
protected void objdsOrderList_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
    e.InputParameters["fullList"] = OrderManager.GetList();// 获取全部列表
    e.InputParameters["filter"] = Filter;
}

注意上面Year、Month、Day属性的获取代码以及DropDownList的SelectedIndexChanged事件代码我都省略了以节省篇幅。

事件探查器

OK,现在我们的所有工作都已经完成了,我们来测试一下通过这种方式对数据库依赖的减小。大家可以打开Sql Server2000的事件探查器(Sql Server2005下的Sql Server Profiler)。选择“文件” --> “新建” --> “跟踪” --> 进行登录。之后应该如下图所示:

选择“事件”选项卡,之后如下图所示:

从右侧“选定的事件”中删除“存储过程”、“安全审查”、“会话”,只保留“T-SQL”,我们只对它进行监视。然后可以看到类似下图,我们对数据库的每次访问都可以在这里看到:

点击上面的“橡皮擦”图标,可以对列表进行清除。然后我们先打开SqlFilter.aspx文件,可以看到我们对列表的每次操作,不管是翻页还是筛选,都会对数据库进行一次查询操作。然后我们点击“橡皮擦”清除掉列表,然后打开ObjFilter.aspx文件,可以看到在对数据库进行了第一次访问以后,后继的动作,无论是进行分页还是筛选操作,都不再对数据库构成依赖。

总结

在这篇文章中,我们主要讨论了如何对业务对象进行筛选。我先提出了很多人的一个思维定势:将操作交给数据库。随后列出了这种操作的典型流程,并在本文中将它称为“基于拼装SQL进行筛选”,然后给出了代码示范。

后半部分,我们详细讨论了基于业务对象进行筛选的方法――将对象缓存在服务器上来对请求提供支持。与前半部分一样,我们先了解了流程,学习了缓存策略,然后进行了代码实现。最后我们使用Sql Server提供的事件探查器对两种情况下对数据库请求的状况进行了跟踪。

感谢阅读,希望这篇文章能给你带来帮助!

转载于:https://www.cnblogs.com/JimmyZhang/archive/2008/03/18/1110708.html

Guess you like

Origin blog.csdn.net/weixin_33695082/article/details/93444019