基于对象的数据筛选与排序(一)
来源:互联网 发布:python项目交易平台 编辑:程序博客网 时间:2024/06/08 04:56
可能大家对于数据库的操作太过于熟悉了,以至于忘记.Net提供的强大而灵活的数据操作。例如,当我们想对数据进行筛选时,首先想到的是“Where”而不是List< T>.FindAll();当我们想对数据进行排序时,首先想到的是“Sort”而不是List< T>.Sort();当我们想对数据进行分页时,首先想到的是存储过程,而不是List< T>.GetRange()。。。
当然在这里并不是要指明数据库的直接操作不够好,在数据量比较大的时候,数据库的直接操作效率确实很高,然而在对较少数据进行操作时,一次性取出数据然后缓存在服务器上,这对于以后的排序、筛选、分页等操作直接对缓存进行,则会使效率提高很多。
方法不是绝对的,也并没有绝对的更优更劣,这里只是提供了不同的思路,具体的方法选用还是得根据实际情况来进行,所以在这里笔者详细介绍下.Net本身强大的对象数据操作。
首先我们在Web界面上放置这些控件,如下图:
在这里的数据我使用了之前一个农田数据采集的数据库,具体的字段有:id编号,光照度,温度,导电度,湿度,所属农田编号和记录时间。
作为样本,我们现在想实现的效果是,根据时间展示4个记录数据就好。
基于SQL的筛选
首先我们最为熟悉和第一反应肯定是用数据库进行筛选操作,我们建立数据表对应的业务对象AgriData
public class AgriData:IData { public int Tem { get; set; } public int Ele { get; set; } public int Sun { get; set; } public int Water { get; set; } public int AgriDataBelong { get; set; } public DateTime Date { get; set; } }
对于采集的数据这里使用List< AgriData>进行存储。接下来我们创建一个SqlAgriDataManager类进行数据的存储工作,并返回List< AgriData>。SqlAgriDataManager的实现思路通常如下:
public class SqlAgriDataManager { //填充List<AgriData>并返回 public static List<AgriData> GetList(string query) { List<AgriData> list = null; SqlDataReader reader = ExcuteReader(query); if (reader.HasRows) { list = new List<AgriData>(); while (reader.Read()) list.Add(GetItem(reader)); } reader.Close(); return list; } //数据库读取数据返回SqlDataReader private static SqlDataReader ExcuteReader(string query) { string strCon = ConfigurationManager.ConnectionStrings["db_AgricultureConnectionString"].ConnectionString; SqlConnection con = new SqlConnection(strCon); SqlCommand com = new SqlCommand(query,con); con.Open(); SqlDataReader reader = com.ExecuteReader(CommandBehavior.CloseConnection); return reader; } //将读取的数据进行封装 private static AgriData GetItem(SqlDataReader record) { AgriData agr = new AgriData(); agr.Tem = Convert.ToInt32(record["AgriDataTem"]); agr.Sun = Convert.ToInt32(record["AgriDataSun"]); agr.Ele = Convert.ToInt32(record["AgriDataEle"]); agr.Water = Convert.ToInt32(record["AgriDataWater"]); return agr; } }
这段代码理解也比较容易,首先连接数据库,执行相应的筛选语句返回一个存储了数据的SqlDataReader对象,接着将每个数据中列值封装到AgriData对象中,逐个填充最终返回List< AgriData>对象。
接下要做的便是提供ObjectDataSource的数据源,也就是我们刚才获取的List< AgriData >集合,很显然我们要在页面上调用的便是GetList方法,具体的页面文件index.aspx代码如下:
<asp:ObjectDataSource ID="ObjectAgrList" runat="server" SelectMethod="GetList" TypeName="Manager.SqlAgriDataManager" OnSelecting="ObjectAgrList_Selecting"> <SelectParameters> <asp:Parameter Name="query" Type="String" /> </SelectParameters> </asp:ObjectDataSource>
ObjctDataSource使用GetList()方法作为SelectCommand(注意要使用静态方法),ObjectDataSource的ID将会用于GridView的DataSourceID。
好的接下来我们进行后台操作,即查询条件——时间的拼装(这里数据库中的时间并没有用通常的DateTime类型,而是vchar),我们来看一下具体实现:
public partial class index : System.Web.UI.Page { //获取下拉列表Year的值 public int Year { get { return Convert.ToInt32(ddlistYear.SelectedValue); } //获取下拉列表Month的值 public int Month { get { return Convert.ToInt32(ddlistMounth.SelectedValue); } } //获取下拉列表Day的值 public int Day { get { return Convert.ToInt32(ddlistDay.SelectedValue); } } //拼装Sql语句 public string QuerySql { get { int year = Year; int mounth = Month; int day = Day; string str = string.Empty; if (year != 0) str += year.ToString() + "/"; if (mounth != 0) str += mounth.ToString() + "/"; if (day != 0) str += day.ToString() + " "; return "select AgriDataTem,AgriDataEle,AgriDataSun,AgriDataWater from AgriData where " + "AgriDataTime like '" + str + "%'"; } } //页面加载事件 protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { AppedListItem(ddlistMounth, 12); AppedListItem(ddlistDay, 30); } } protected void AppedListItem(DropDownList list,int end) { for (int i = 1; i <= end; i++) { list.Items.Add(new ListItem(i.ToString())); } } protected void ddlistYear_SelectedIndexChanged(object sender, EventArgs e) { gvAgriculture.DataBind(); } protected void ddlistMounth_SelectedIndexChanged(object sender, EventArgs e) { gvAgriculture.DataBind(); } protected void ddlistDay_SelectedIndexChanged(object sender, EventArgs e) { gvAgriculture.DataBind(); } //每个列表的回发都很会触发gvAgriculture.DataBind(),然后出发这里 protected void ObjectAgrList_Selecting(object sender, ObjectDataSourceSelectingEventArgs e) { e.InputParameters["query"] = this.QuerySql; } }
这段代码中Year、Month、Day分别对应3个DropDownList控件的SelectedValue,同时用AppendListItem方法对月和日控件赋初值(年列表直接赋予了2017,当然为了简便,这里没有对不同月的天数进行处理,直接为30天),在每个下拉列表SelectedIndex发生变化时,对GridView控件进行数据绑定,在回发过程中触发新的查询操作即ObjectDataSource的Selecting事件,我们用一个按钮来辅助做回发操作。
基本的基于数据库的操作过程便是如此,执行之后得到到的效果如下图:
基于对象的筛选
上面我们演示了传统的SQL的数据筛选操作,那么在此基础上是怎样进行基于对象的筛选的,又是怎样提升性能的(没有优化的操作就不会有被推广的意义)呢?
同样,我们沿用刚才所创建的控件进行操作,在本例中基于对象的筛选就是对List< AgriData>的筛选。实现的思路也并不难,首先创建一个重载的GetList(下篇代码直接新建了一个类来实现该方法,未在原SqlAgriDataManager类中实现重载)方法,然后取出所有的AgriData并添加到缓存中,然后创建一个新的List< AgriData>,将缓存中的所有数据遍历,将符合要求的项添加到该List< AgriData>中,最后再返回该集合,从而实现了相关的筛选操作。代码如下(为了利于区别,在这里新建了一个类):
public class ObjAgriDataManager { public static List<AgriData> GetList() { List<AgriData> list = HttpContext.Current.Cache["AgriList"] as List<AgriData>; if (list == null) { list = SqlAgriDataManager.GetList("select AgriDataTem,AgriDataEle,AgriDataSun,AgriDataWater from AgriData"); HttpContext.Current.Cache.Insert("AgriList", list); } return list; } public static List<AgriData> GetList(List<AgriData> agriList,int year,int month,int day) { List<AgriData> list = null; bool canAdd; //将从缓存中提取的List<AgriData>根据年月日筛选出来 if (agriList != null) { list = new List<AgriData>(); foreach (AgriData n in agriList) { canAdd = true; //为0时即时间段都符合要求 if (year != 0 && year != n.Date.Year) canAdd = false; if (month != 0 && month != n.Date.Month) canAdd = false; if (day != 0 && day != n.Date.Day) canAdd = false; if (canAdd) list.Add(n); } } return list; }
OK,我们来仔细看一下这个代码,无参的GetList方法在无缓存情况下通过SqlAgriDataManager中的GetList方法执行sql语句将得到的数据一次性缓存到缓存中,在有缓存数据情况下直接使用缓存中数据。第二个GetList方法中通过输入的年、月、日对agriList进行筛选,从而返回筛选后的集合对象。
很显然,上面的方法扩展性是很差的,现在是根据年、月、日查询,那有需要根据所属农田Id查询时,又需要对该方法进行修改,或者再写一个重载方法,这显然不符合面向对象设计模式,因为代码没有得到重用。
实际上,.Net框架已经为这些问题做好了解决方案,在List< T>上提供了一个FindAll(Predicate< T> math)方法进行筛选工作,Predicate< T>是一个泛型委托:
public delegate bool Predicate<T>(T obj)
因此math参数是一个返回bool类型并且只有一个传递参数的方法,在FindAll()内部再将这个方法传递进去。
现在我们要做的工作就是完成Predicate< T>封装的筛选规则,和定义Predicate< T>委托的方法。
public static List<AgriData> GetList(List<AgriData> agriList,int year,int month,int day)
显然这里的筛选条件为了更好的扩展性需要进行变更,我们可以定义一个泛型数据筛选类DataFilter< T>来进行筛选条件的设置,于是这个GetList方法变为:
public static List<AgriData> GetList(List<AgriData> agriList, DataFilter<AgriData> filter)
那么具体的这个DataFilter的设计思路是怎样的呢?
考虑到Predicate< T>只能传递一个参数,我们用数据对象作为参数即这里的AgriData业务对象进行参数传递,于是DataFilter< T>这个类和DataFilter< T>中的bool型筛选方法就应该是这样定义的:
public class DataFilter<T> where T : AgriData { public bool MatchRule(T param) { if (year != 0 && year != param.Date.Year) return false; if (month != 0 && month != param.Date.Month) return false; if (day != 0 && day != param.Date.Day) return false; return true; } }
因为year,month,day是比较通常的查询操作,为了便于封装和实现这个查询约束,我们这里定义一个接口,仅含有一个DateTime类型的Date属性,对于所有实现了该接口的类,都可以使用上面的筛选方法(一个不包含年、月、日的类显然不符合这里的筛选条件)。
public interface IData { DateTime Date { get; set;} }
同时对AgriData类进行修改,让他实现这个接口:
public class AgriData:IData
好了,有了这样的约束接口,我们可以将DataFilter的约束条件更改为IData,同时我们完善该筛选类的具体代码:
public class DataFilter<T> where T : IData { private int year, month, day; public DataFilter(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } //方便使用的一组构造函数 public DataFilter(DateTime date) : this(date.Year, date.Month, date.Day) { } public DataFilter(int year, int month) : this(year, month, 0) { } public DataFilter(int year) : this(year, 0, 0) { } public DataFilter() : this(0, 0, 0) { } //基于对时间筛选的基本逻辑 public bool MatchRule(T param) { if (year != 0 && year != param.Date.Year) return false; if (month != 0 && month != param.Date.Month) return false; if (day != 0 && day != param.Date.Day) return false; return true; } }
我们回到之前的问题,数据筛选不单单只要筛选出时间符合条件的问题,如还要筛选符合的所属农田Id咋办,我们工作到了这里,应该很容易联想到DataFilter< T>应该作为一个筛选类的基类,同时应将MathRule方法作为可重写方法,这样更利于子类的相关实现。于是便有了这样的修改:
public virtual bool MatchRule(T param) {}
接下来我们来看一下对所属农田进行的筛选时如何进行的:
public class AgriDataFilter : DataFilter<AgriData> { private int agriDataBelong; //同时对时间和所属农田id的查询赋值 public AgriDataFilter(int year, int month, int day, int id) : base(year, month, day) { this.agriDataBelong = id; } public override bool MatchRule(AgriData param) { bool result = base.MatchRule(param); //0 if (agriDataBelong == 0 || agriDataBelong == param.AgriDataBelong) return true; return result; } }
现在ObjAgriDataManager中的GetList方法也显而易见了:
public static List<AgriData> GetList(List<AgriData> agriList, DataFilter<AgriData> filter) { List<AgriData> list = null; //通过List<T>自带的FindAll进行筛选 if (agriList != null) list = agriList.FindAll(new Predicate<AgriData>(filter.MatchRule)); return list; }
同时,我们对SqlAgriDataManager中的GetItem扩充一下:
private static AgriData GetItem(SqlDataReader record) { AgriData agr = new AgriData(); agr.Tem = Convert.ToInt32(record["AgriDataTem"]); agr.Sun = Convert.ToInt32(record["AgriDataSun"]); agr.Ele = Convert.ToInt32(record["AgriDataEle"]); agr.Water = Convert.ToInt32(record["AgriDataWater"]); agr.AgriDataBelong = Convert.ToInt32(record["AgriDataBelong"]); agr.Date = Convert.ToDateTime(record["AgriDataTime"]); return agr; }
最后要做的就是对index.aspx页面上的ObjectDataSource控件的属性重新配置一下:
<asp:ObjectDataSource ID="ObjectDataSource" runat="server" SelectMethod="GetList" TypeName="Manager.ObjAgriDataManager" OnSelecting="ObjectDataSource_Selecting"> <SelectParameters> <asp:Parameter Name="agriList" Type="Object" /> <asp:Parameter Name="filter" Type="Object" /> </SelectParameters> </asp:ObjectDataSource>
后台得到DateFilter的处理为:
public DataFilter<AgriData> Filter { get { DataFilter<AgriData> filter = new AgriDataFilter(Year, Month, Day, 100); return filter; } }
ObjectDataSource的Selecting的事件处理为(其他DropDownList的相关处理事件无需更改):
protected void ObjectDataSource_Selecting(object sender, ObjectDataSourceSelectingEventArgs e) { e.InputParameters["agriList"] = ObjAgriDataManager.GetList(); e.InputParameters["filter"] = Filter; }
一切就是这样的顺利,最终运行的出的效果为:
所有工作都已经完成了,我们可以测试一下通过这方式对数据库的依赖是否减少(理论上只需执行一次SQL操作)。我们可以打开SQL 2008中的事件探测器(SQL Server Profiler)进行测试。
单击工具栏的“橡皮擦”图标,先对列表清除。然后运行第一次基于数据库筛选的index.aspx文件,可以看到对列表的每次操作,无论翻页还是筛选,都会对数据库进行一次查询操作。然后单击“橡皮擦”清除列表,运行第二次基于对象的筛选的程序,可以看到果然和预期一样,只进行了一次访问,后继的翻页还是筛选对数据库都未构成依赖,全部都是对缓存进行了相关操作。
- 基于对象的数据筛选与排序(一)
- 基于对象的数据筛选与排序(二)
- “筛选”与数据透视表的筛选
- C# DataView数据筛选与排序
- 基于ES的搜索+筛选+排序解决方案
- 基于内存缓冲区的流媒体数据缓存排序(一)
- 数据集的筛选和排序
- 脱机数据的排序、搜索和筛选
- pandas筛选排序数据
- 自动筛选的逻辑(7)-与数据透视表的筛选不同
- orderBy排序与筛选的例子
- Linq(筛选与排序)
- #研发解决方案介绍#基于ES的搜索+筛选+排序解决方案
- 研发解决方案介绍#基于ES的搜索+筛选+排序解决方案
- DataView的使用与筛选数据
- C++基于对象与面向对象(一)(Boolan)
- 与DotNet数据对象结合的自定义数据对象设计 (一) 数据对象与DataRow
- map 数据筛选和排序
- Longest Common Prefix
- Hadoop基本知识点之HDFS
- CCF考试-折点计数(201604-1)
- 一个让我心态爆炸的bug
- js中进行金额计算
- 基于对象的数据筛选与排序(一)
- 14 STL中容器vector(学自Boolean)
- MySql数据引擎-MyISAM与InnoDB区别
- JavaSE基础Map集合
- c++中二维数组与二维向量的长度
- 读书笔记《C++ Primer》第五版——第九章 顺序容器
- 关于开关灯的问题
- matlab画图函数汇总(三)
- js_expresion&operator