步步为营:Asp.net 通用数据容器的缺陷
2011-12-19 17:59 薛凯凯圆滚滚 阅读(244) 评论(0) 编辑 收藏 举报在做试客项目中,我们项目使用的数据框架是ADO.Net, 我常常在想是不是有很好的办法能通用业务到数据请求。即使不能做到所有的业务,但对于同一种业务能达到通用也好。
于是我就想用泛型尝试解决这个问题,用分页来讲解。
在控制器我这样写
List<Crm_Achievements_HD> lstAchievements = BLLAchievements.GetAchievementsByWhereHD(item);
BLL我这样写
#region 业绩存储过程分页
/// <summary>
/// 业绩存储过程分页
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public static List<Crm_Sell_Achievements> GetAchievementsByWhere(PagerEntity item)
{
List<Crm_Sell_Achievements> list = DatabaseProvider.GetInstance().GetCommonPager<Crm_Sell_Achievements>(item);
return list;
}
#endregion
DAL我这样写
#region 通用存储过程分页
/// <summary>
/// 通用存储过程分页
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item"></param>
/// <returns></returns>
public List<T> GetCommonPager<T>(PagerEntity item) where T : class, new()
{
//创建返回的集合
List<T> objlist = new List<T>();
string strsql = string.Empty;
//创建属性的集合
List<PropertyInfo> prlist = new List<PropertyInfo>();
//获得反射的入口
Type t = typeof(T);
//获取所有公共属性
PropertyInfo[] ps = t.GetProperties();
strsql = "P_CommonPager_RowNomber";
DbParameter[] param = {
SQLHelper.MakeParam("@tblName",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,item.TblName),
SQLHelper.MakeParam("@strGetFields",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,item.StrGetFields),
SQLHelper.MakeParam("@strOrder",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,item.StrOrder),
SQLHelper.MakeParam("@strWhere",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,item.StrWhere),
SQLHelper.MakeParam("@pageIndex",(DbType)SqlDbType.Int,4, ParameterDirection.Input,item.PageIndex),
SQLHelper.MakeParam("@pageSize",(DbType)SqlDbType.Int,4, ParameterDirection.Input,item.PageSize),
SQLHelper.MakeParam("@recordCount",(DbType)SqlDbType.Int,4, ParameterDirection.Output,item.RecordCount),
SQLHelper.MakeParam("@doCount",(DbType)SqlDbType.Int,4, ParameterDirection.Input,item.DoCount),
};
param[6].Direction = ParameterDirection.Output;
using (IDataReader rs = SQLHelper.ExecuteReader(CommandType.StoredProcedure, strsql, param))
{
while (rs.Read())
{
//新实例泛型
T obj = new T();
//遍历属性数组
foreach (PropertyInfo p in ps)
{
//将对应的属性填充给对象
p.SetValue(obj, rs[p.Name] ?? 0, null);
}
//添加对象到返回集合
objlist.Add(obj);
}
}
item.RecordCount = TypeConverter.ObjectToInt(param[6].Value);
return objlist;
}
#endregion
因为DataReader需要关闭后才能取返回值,所以我将反射属性写到了数据层,实现这功能。
但是这样产生了一个问题BLL不能通用,这是我非常懊恼的问题。
之后我又修整了代码,可能是我自己纠结的问题因为就只是分页过程有返回值,其他倒不需要
于是我又写了一遍
在控制器这样写
List<View_TryListCompany> LstTryList = Users.DataTableToList<View_TryListCompany>
(Users.GetCommonPager(tblName, strGetFields, pkName, strOrder, strWhere,
pageIndex, pageSize, ref recordCount, doCount, EnumCollection.Enum_SpCommon.SPROWNUMBER));
BLL中
#region 通用存储过程分页
/// <summary>
/// 通用存储过程分页
/// </summary>
/// <param name="tblName">表名或者视图名如:'sk_Users'</param>
/// <param name="strGetFields">需要返回的列如:'uid,username'</param>
/// <param name="pkName">主键</param>
/// <param name="strOrder">排序的字段名如:'order by id desc'</param>
/// <param name="strWhere">查询条件(注意:不要加where)如:'username like ''%222name%''' </param>
/// <param name="pageIndex">页码如:2</param>
/// <param name="pageSize">每页记录数如:20</param>
/// <param name="recordCount">out 记录总数</param>
/// <param name="doCount">非0则统计,为0则不统计(统计会影响效率)</param>
/// <param name="SpCommon">枚举:选择通用过程</param>
/// <returns>DataTable</returns>
public static DataTable GetCommonPager(string tblName, string strGetFields, string pkName, string strOrder,
string strWhere, int pageIndex, int pageSize, ref int recordCount,
int doCount, EnumCollection.Enum_SpCommon SpCommon)
{
DataSet ds = DatabaseProvider.GetInstance().
GetCommonPager(tblName, strGetFields, pkName, strOrder, strWhere, pageIndex, pageSize, ref recordCount, doCount, SpCommon);
DataTable table = ds.Tables[0];
return table;
}
#endregion
DAL中
#region 通用存储过程分页
/// <summary>
/// Author:梁雄开
/// CreatDate:2010.04.23
/// Description:通用存储过程分页
/// </summary>
/// <param name="tblName">表名如:'sk_Users'</param>
/// <param name="strGetFields">需要返回的列如:'uid,username'</param>
/// <param name="pkName">主键</param>
/// <param name="strOrder">排序的字段名如:'order by id desc'</param>
/// <param name="strWhere">查询条件(注意:不要加where)如:'username like ''%222name%''' </param>
/// <param name="pageIndex">页码如:2</param>
/// <param name="pageSize">每页记录数如:20</param>
/// <param name="recordCount">out 记录总数</param>
/// <param name="doCount">非0则统计,为0则不统计(统计会影响效率)</param>
/// <param name="SpCommon">枚举:选择通用过程</param>
/// <returns></returns>
public DataSet GetCommonPager(string tblName, string strGetFields, string pkName,
string strOrder, string strWhere, int pageIndex, int pageSize, ref int recordCount,
int doCount, EnumCollection.Enum_SpCommon SpCommon)
{
DataSet ds = new DataSet();
string strsql = string.Empty;
if (SpCommon == EnumCollection.Enum_SpCommon.SPNOTINT)
{
strsql = "P_CommonPager_NotIn";
DbParameter[] param = {
SqlHelper.MakeParam("@tblName",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,tblName),
SqlHelper.MakeParam("@strGetFields",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,strGetFields),
SqlHelper.MakeParam("@pkName",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,pkName),
SqlHelper.MakeParam("@strOrder",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,strOrder),
SqlHelper.MakeParam("@strWhere",(DbType)SqlDbType.VarChar,8000, ParameterDirection.Input,strWhere),
SqlHelper.MakeParam("@pageIndex",(DbType)SqlDbType.Int,4, ParameterDirection.Input,pageIndex),
SqlHelper.MakeParam("@pageSize",(DbType)SqlDbType.Int,4, ParameterDirection.Input,pageSize),
SqlHelper.MakeParam("@recordCount",(DbType)SqlDbType.Int,4, ParameterDirection.Output,recordCount),
SqlHelper.MakeParam("@doCount",(DbType)SqlDbType.Int,4, ParameterDirection.Input,doCount),
};
param[7].Direction = ParameterDirection.Output;
ds = SqlHelper.ExecuteDataset(CommandType.StoredProcedure, strsql, param);
recordCount = TypeConverter.ObjectToInt(param[7].Value);
}
if (SpCommon == EnumCollection.Enum_SpCommon.SPROWNUMBER)
{
strsql = "P_CommonPager_RowNomber";
DbParameter[] param = {
SqlHelper.MakeParam("@tblName",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,tblName),
SqlHelper.MakeParam("@strGetFields",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,strGetFields),
SqlHelper.MakeParam("@strOrder",(DbType)SqlDbType.VarChar,255, ParameterDirection.Input,strOrder),
SqlHelper.MakeParam("@strWhere",(DbType)SqlDbType.VarChar,8000, ParameterDirection.Input,strWhere),
SqlHelper.MakeParam("@pageIndex",(DbType)SqlDbType.Int,4, ParameterDirection.Input,pageIndex),
SqlHelper.MakeParam("@pageSize",(DbType)SqlDbType.Int,4, ParameterDirection.Input,pageSize),
SqlHelper.MakeParam("@recordCount",(DbType)SqlDbType.Int,4, ParameterDirection.Output,recordCount),
SqlHelper.MakeParam("@doCount",(DbType)SqlDbType.Int,4, ParameterDirection.Input,doCount),
};
param[6].Direction = ParameterDirection.Output;
ds = SqlHelper.ExecuteDataset(CommandType.StoredProcedure, strsql, param);
recordCount = TypeConverter.ObjectToInt(param[6].Value);
}
return ds;
}
#endregion
这次换成DataSet获取数据,出来后在转换反射出想要的列表。
#region DataTable转List
/// <summary>
/// DataTable转List
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <param name="dt">数据表格</param>
/// <returns></returns>
public static List<T> DataTableToList<T>(DataTable dt) where T : class,new()
{
//创建一个属性的列表
List<PropertyInfo> prlist = new List<PropertyInfo>();
//获取T的类型实例 反射的入口
Type t = typeof(T);
//获得T 的所有的Public 属性 并找出T属性和DataTable的列名称相同的属性(PropertyInfo) 并加入到属性列表
Array.ForEach<PropertyInfo>(t.GetProperties(), p => { if (dt.Columns.IndexOf(p.Name) != -1) prlist.Add(p); });
//创建返回的集合
List<T> oblist = new List<T>();
foreach (DataRow row in dt.Rows)
{
//创建T的实例
T obj = new T();
//找到对应的数据 并赋值
prlist.ForEach(p => { if (row[p.Name] != DBNull.Value) p.SetValue(obj, row[p.Name], null); });
//放入到返回的集合中.
oblist.Add(obj);
}
return oblist;
}
#endregion
完成自己想要的List,但是使用DataSet性能不好。转换也花费了一层消耗,还是不够完美。
让我们再次整理次吧。
先让我们讨论看看问题:
强耦合
DataReader和DataTable不允许你透明地检索数据,而不影响用户界面的代码。这意味这你的应用程序和数据库结构是强耦合的,对数据库结构的任何改变都需要对你的程序有所改动。这应该是在数据访问层解决的问题,而不是在用户界面层。
很多时候,数据库都是为一个应用程序提供服务,这样数据组织起来很容易被使用。但是,有些程序是建立在已有的数据库之上的,这时候就不能对数据库做任何改动,因为还有其他程序在使用这个数据库。这种情况下,你的代码可能跟数据库耦合性更强,甚至超出你的想象。例如,订单可能存储在一个表中,送货地址存储在另一个表中。数据访问层的代码可能减少这种影响,但是问题依然存在。
当列的名称发生改变又会发生什么呢,其实在开发的过程中这种事情经常发生。结果是,你必须改变用户界面层的代码来适应这一变化。
你会这样写
object shippingAddress = orders.Rows[0]["ShippingAddress"];
你获取列需要自己写列名,也就是说当修改数据结构,你就必须维护这里。
string shippingAddress = (string)orders.Rows[0]["ShippingAddress"];
string shippingAddress = orders.Rows[0]["ShippingAddress"].ToString();
除了这些,你还需要对类型进行显示的转换,进行类型转换在性能和内存使用上都有一定的损失,因为从值类型转换到引用类型要进行装箱操作,反之进行拆箱操作。
DataReader比DataTable有优势,它提供了不需要显式转换访问字段的类型方法,方法的参数接受列在一行中得索引值。它还提供了一个提供列的名称返回索引的方法。但是输入的字符串错误,也会抛出异常。
string address = rd.GetString(rd.GetOrdinal("ShippingAddress"));
string address = rd.GetString(rd.GetOrdinal("ShipingAdres")); //exception
性能问题
DataSet可能是.NET类库中最复杂的结构了。它包括一个或多个DataTable实例,每个DataTable实例又包括一系列DataRow对象,每个DataRow对象又由DataColumn对象构成的。DataTable可以由一个或多个列组成组成一个主键,还可以在某些列上声明外键,列还支持版本控制等等。尽管这些特性很多时候都是没用的,也常常被开发人员忽略,但是DataSet仍然在内部创建包含这些对象的空的集合。这对一个独立的应用程序可能是微不足道的性能损失,但是在一个多用户的环境中,有成千上万条请求,这将是不可接受的。
相比之下,DataReader可以适用在不同的场景中。DataTable从数据库中读出所有的数据放到内存中,但是很多时候你并不需要这么多的数据在内存中,你只需要一条条的从数据库中读出记录即可。另外一种情况,你经常查询数据而不用更新它,这种情况下,DataReader是最佳选择,因为它使用只读的方式检索数据。尽管DataReader提升了性能,但是它依然有类型转换带来的性能损失。