前述:
对数据库操作的封装,相信网络上已经有一大堆,orm框架,或者是.net本身的ef,都很好的支持数据库操作。这篇文章是分享自己所思考的,对数据库操作的简单封装。我对于这篇文章,认为被浏览者所关注重点的是怎么分析设计数据库操作封装,代码是其次。而且,这是我第一篇文章,为了想好怎么实现花了些天,代码是博客发表时现写的。所以我想,使用可能还有bug,而且没有try catch异常的设计。
这个框架我理应做到对数据库无关,无论是哪个数据库都能够使用。不过,重点在于分析,而不是代码。所以,为了更好的阐述,我只做了对sql server的封装,对其他的话,浏览者可以自己设计;框架可支持链式写法,我想,在许多编程语言,大家对链式写法大不会陌生,所以我想,数据库访问也可以做成链式的模式。这个框架不需要写sql语句,对任何的操作,都只需要简单的传所需的参数,封装好对应的操作。
在阅读文章之前最好有些泛型、反射、link的基础,不然阅读可能会有些费劲。
进入重点:
框架的结构比较简单,使用简单工厂模式,因此笔者就不画一张uml图来解释,而用文字对里面方法进行描述。
在设计工厂接口时候,应该考虑接口中应该含有链式写法必须的三个阶段(也称部分):数据库基本操作(打开,关闭,创建等)、数据库的增删改查、数据库返回的数据(这里我做为执行阶段,估计大家会好奇为什么不是上一阶段,大家往下阅读就知道)和不是必须的事务操作。
using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.data;
using system.data.sqlclient;
namespace dal
{
public interface dbhelper
{
/// <summary>
/// 创建数据库连接
/// </summary>
/// <param name="connectionstring">连接字符串</param>
/// <returns></returns>
dbhelper createconnection(string connectionstring);
/// <summary>
/// 打开数据库
/// </summary>
/// <returns></returns>
dbhelper openconnection();
/// <summary>
/// 关闭数据库
/// </summary>
/// <returns></returns>
dbhelper closeconnection();
/// <summary>
/// 释放sqlconnection对象
/// </summary>
void disposedconnection();
/// <summary>
/// 释放sqlcommand对象
/// </summary>
void disposedcommand();
/// <summary>
/// 创建sqlcommand对象
/// </summary>
/// <returns></returns>
dbhelper createcommand();
/// <summary>
/// 设置sqlcommand的类型
/// </summary>
/// <param name="type">commandtype枚举类型</param>
/// <returns></returns>
dbhelper setcommandtype(commandtype type);
/// <summary>
/// 要查询的表(多表以逗号隔开)、存储过程、视图名
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
dbhelper fromname(string name);
/// <summary>
/// 创建事务
/// </summary>
/// <returns></returns>
dbhelper begintransaction();
/// <summary>
/// 事务回滚
/// </summary>
/// <returns></returns>
dbhelper transactionrowback();
/// <summary>
/// 事务提交
/// </summary>
/// <returns></returns>
dbhelper transactioncommit();
/// <summary>
/// 对多张表间的列进行联系
/// </summary>
/// <param name="fields">表间联系的字段</param>
/// <returns></returns>
dbhelper formultable(string fields);
/// <summary>
/// 查询
/// </summary>
/// <param name="fields">查询字段</param>
/// <param name="where">查询条件字典</param>
/// <param name="otherwhere">其他条件</param>
/// <returns></returns>
dbhelper select(string fields = "*", dictionary<string, object> where = null, string otherwhere = "");
/// <summary>
/// 更新
/// </summary>
/// <param name="model">需要更新的对象</param>
/// <param name="where">更新条件</param>
/// <param name="fields">更新字段</param>
/// <param name="otherwhere">其他条件</param>
/// <returns></returns>
dbhelper update(object model, dictionary<string, object> where, string fields = "", string otherwhere = "");
/// <summary>
/// 插入
/// </summary>
/// <param name="model">需要插入的对象</param>
/// <param name="fields">需要插入的字段</param>
/// <returns></returns>
dbhelper insert(object model, string fields = "");
/// <summary>
/// 删除
/// </summary>
/// <param name="where">删除条件</param>
/// <param name="otherwhere">其他条件</param>
/// <returns></returns>
dbhelper delete(dictionary<string, object> where, string otherwhere = "");
/// <summary>
/// 查询返回list
/// </summary>
/// <typeparam name="t">模型</typeparam>
/// <returns></returns>
list<t> tolist<t>()
where t : class ,new();
/// <summary>
/// 查询返回dataset
/// </summary>
/// <param name="datasetname"></param>
/// <returns></returns>
dataset todataset(string datasetname);
/// <summary>
/// 查询返回datatable
/// </summary>
/// <returns></returns>
datatable todatatable();
/// <summary>
/// 执行存储过程
/// </summary>
/// <param name="parameter">存储过程参数</param>
/// <returns></returns>
dbhelper excuteproc(dictionary<string, object> parameter);
/// <summary>
/// 执行返回查询第一行第一列值
/// </summary>
/// <returns></returns>
object result();
/// <summary>
/// 返回执行的影响行数
/// </summary>
/// <returns></returns>
int excuteresult();
/// <summary>
/// 用户自定义sqlcommand
/// </summary>
/// <param name="fun">委托</param>
void userdefineoperation(action<dynamic> fun);
}
}
好了,看完代码,大家对具体实现应该还是一头雾水,那,接下来一步步分析具体实现,是以sql server来分析。
在具体实现的类中sqlhelper,设计中所必须的字段。在一开始设计时候,我在想怎么给各个数据库兼容,因为它们使用的执行对象command是不同的,所以为了能够更好封装的库,将其设计sqlcommand不暴露给外部使用,而是在内部使用。暴露方法能够设置com的属性,以及excutename就存放着执行数据的对象。
//连接字符串
string connectionstring;
//数据库连接对象
private sqlconnection conn;
//执行对象
sqlcommand com;
//表、存储过程、视图名称
string excutename;
//事务
sqltransaction tran;
//sql语句
stringbuilder sqlbuilderstring;
//参数
sqlparameter[] paras;
第一部分:数据库基本操作
createconnection方法:这个方法其实就是new sqlconnection,对其赋值connectionstring,也采用了大家一般使用的单例模式,这样也会在执行的时候比较安全。不过这个单例是指一个helper对应一个sqlconnection,而不是设计为static,因为我觉得有些项目在访问的数据库有可能有多个。而且在创建时候,对其进行打开和关闭一次,为了检查能否真的能使用。
public dbhelper createconnection(string connectionstring)
{
if (!connectioncanuse())
{
this.connectionstring = connectionstring;
conn = new sqlconnection(this.connectionstring);
}
return this;
}
/// <summary>
/// 检查conn是否能用
/// </summary>
/// <returns></returns>
public bool connectioncanuse()
{
if (conn == null)
return false;
try
{
conn.open();
conn.close();
}catch(exception e)
{
return false;
}
return true;
}
打开、关闭、释放connection和创建command就不作解释了,因为里面就一句话。
关于基本操作,还有就是关于sqlcommandtype的设置,因为存储过程和普通的语句等操作字符串明显是不同,因此要写个方法来设置它。
第二部分:增删改查的操作 这里就解释为什么sql语句不是在这个阶段执行。我觉得,如果将具体的执行放在这个阶段,那么就会导致方法重载过多,为什么?因为并不是所有人都能考虑到使用者要返回的类型,比如我想要list,或者dataset等等,而且还会将这个方法的作用过重:在我设计的这些方法中,实操作的是对sql语句的生成,所以说为什么不能在这边执行,那么就不能重用。是吧,这样设计很灵活,将数据库真正执行放在下个阶段。而且这些方法都是链式的写法,所以会对执行能够很灵活的控制,最重要能够重用,不需要写别的重载方法,只需要一个方法。
生成sql语句在这也是简单的封装,如果要做起真的框架,我觉得sql字符串的组合还应该创建一个类,来更智能的组合用户的需求。
自然,里面使用到反射、linq。不过笔者也一步步解释,将怎么设计分享给大家。
大家看到select、insert、update、delete的接口都有where的条件字典。没错,反射就在这里使用。为了考虑到数据库的安全,所以sql自然只是简单的拼接,还应该使用参数。所以,反射就用在where生成参数上。大家也许还看到别的otherwhere,这个怎么不设计成参数,因为where能够实现的,其实就是赋值语句,也就是表内某字段 = 值,所以需要。在otherwhere中,存放的是其他特殊的条件。前面说这里设计的不完美,就因为如此,其实有些条件 like 或者 使用or ,虽然能够写在otherwhere中,但是没办法使用参数来控制。
那么接下来就是fiels参数了,这个在各个方法充当不同的作用。select是查询的字段,update中是更新的字段,在insert中是插入的字段,这样就灵活的控制了。在这些字段为空的时候,默认为全部,反射在这里就使用了,遍历模型对象中的属性,然后将它们一个个填进sql语句中。在这里比较注意的应该是插入,因为大家在写sql语句时候是这样的 insert tablename values(value,value....)这样的格式,这样是因为sql会自己对应值插入,而在程序中的模型类中,我想大家写属性可不是按顺序的吧,所以在反射遍历时候,就有可能将几个本来待在某个列位置的值去换了位置的情况。所以,这里在遍历的时候,应该按插入的完全格式来设计,也就是 insert tablename(field,field...) values(value,value...)。
在这几个方法中,delete最简单。
public dbhelper select(string fields = "*",dictionary<string,object> where = null,string otherwhere = "")
{
sqlbuilderstring = new stringbuilder();
sqlbuilderstring.appendline("select " + fields + " from " + this.excutename);
list<sqlparameter> paras = new list<sqlparameter>();
sqlbuilderstring.appendline(" where 1 = 1 ");
if (where != null && where.count > 0)
{
paras = new list<sqlparameter>(); //遍历where,将里面的条件添加到sqlparamter和sql语句中
where.keys.tolist().foreach(o => {
sqlbuilderstring.appendline(" and "+ o + " = @" + o);
paras.add(new sqlparameter(o, where[o]));
});
} if(!string.isnullorempty(otherwhere))
{
sqlbuilderstring.appendline(otherwhere);
} this.paras = paras.toarray(); return this;
}
public dbhelper update(object model,dictionary<string, object> where,string fields = "", string otherwhere = "")
{
type t = model.gettype();
list<string> keys = where.keys.tolist();
sqlbuilderstring = new stringbuilder(); bool firstnode = true;
sqlbuilderstring.appendline("update "+excutename + " set ");
list<sqlparameter> paras = new list<sqlparameter>(); if(string.isnullorempty(fields))
{
t.getproperties().tolist().foreach(o =>
{ if (!firstnode)
sqlbuilderstring.append(","); else
firstnode = false; if(!keys.contains(o.name))
{
sqlbuilderstring.appendline(o.name + " = @"+o.name);
paras.add(new sqlparameter(o.name,o.getvalue(model,null)));
}
});
}else
{
fields.split(',').tolist().foreach(o =>
{
sqlbuilderstring.appendline(o + " = @" + o);
paras.add(new sqlparameter(o, t.getproperty(o).getvalue(model, null)));
});
} this.paras = paras.toarray(); return this;
}
public dbhelper insert(object model,string fields = "")
{
list<sqlparameter> paras = new list<sqlparameter>();
type t = model.gettype();
sqlbuilderstring = new stringbuilder();
sqlbuilderstring.appendline("insert " + excutename); if(string.isnullorempty(fields))
{ string s = ""; string s1="";
t.getproperties().tolist().foreach(o =>
{
s += o.name + ",";
s1 += " @" + o.name + ",";
paras.add(new sqlparameter(o.name, o.getvalue(model, null)));
});
s.remove(s.lastindexof(','),1);
s1.remove(s.lastindexof(','), 1);
sqlbuilderstring.appendline("(" + s + ")");
sqlbuilderstring.appendline(" values(" + s1 + ")");
}else
{
sqlbuilderstring.appendline("(" + fields + ")"); string s = "";
fields.split(',').tolist().foreach(o =>
{
s += " @" + o + ",";
paras.add(new sqlparameter(o, t.getproperty(o).getvalue(model, null)));
});
sqlbuilderstring.appendline(" values(" + s + ")");
} this.paras = paras.toarray(); return this;
}
public dbhelper delete(dictionary<string,object> where,string otherwhere = "")
{
sqlbuilderstring = new stringbuilder();
list<sqlparameter> paras = new list<sqlparameter>();
sqlbuilderstring.appendline("delete " + excutename);
sqlbuilderstring.appendline(" where 1 = 1 ");
where.keys.tolist().foreach(o =>
{
sqlbuilderstring.appendline(" and " + o + " = @" + o);
paras.add(new sqlparameter(o, where[o]));
}); this.paras = paras.toarray(); return this;
}
最后一个阶段,那就是执行阶段,这里封装了些执行的方法。
这个也是简单,最重要的方法应该是setcommand,这个方法是对sqlcommand进行设置,执行的语句,以及添加参数。
private void setcommand()
{ if(com.commandtype== commandtype.storedprocedure)
{ this.com.commandtext = excutename;
}else
{ this.com.commandtext = sqlbuilderstring.tostring();
} this.paras.tolist().foreach(o =>
{ this.com.parameters.add(o);
});
}
其他就是执行的语句。
public list<t> tolist<t>() where t:class ,new()
{
list<t> list = new list<t>();
setcommand();
sqldatareader reader = com.executereader();
type t = typeof(t);
list<propertyinfo> pros = t.getproperties().tolist(); while(reader.read())
{
t model = new t();
pros.foreach(o =>
{
o.setvalue(model, reader[o.name], null);
});
list.add(model);
}
reader.dispose(); return list;
} public dataset todataset(string datasetname = "")
{
dataset ds = new dataset();
setcommand();
sqldataadapter adapter = new sqldataadapter(com);
adapter.fill(ds, string.isnullorempty(datasetname) ? this.excutename.replace(",", "_") : datasetname);
adapter.dispose(); return ds;
} public datatable todatatable()
{
datatable dt = new datatable();
setcommand();
sqldataadapter adapter = new sqldataadapter(com);
adapter.fill(dt);
adapter.dispose(); return dt;
} public object result()
{
setcommand(); return com.executescalar();
} public int excuteresult()
{
setcommand(); return com.executenonquery();
} public dbhelper excuteproc(dictionary<string,object> parameter)
{
list<sqlparameter> paras = new list<sqlparameter>();
parameter.keys.tolist().foreach(o =>
{
paras.add(new sqlparameter(o, parameter[o]));
}); return this;
}
当然,还不能少了让用户自定义的方法,所以最后还留了个方法,参数是委托。委托里面的参数还是动态类型,这就懂了吧,想用户怎么用,你就怎么定义。
public void userdefineoperation(action<dynamic> fun)
{
fun(this.com);
}
好了,设计也就到这里,下面就贴上sqlhelper完整的代码。
using system;using system.collections.generic;using system.linq;using system.text;using system.data;using system.data.sqlclient;using system.reflection;namespace dal
{ public class sqlhelper:dbhelper
{ //连接字符串
string connectionstring;
//数据库连接对象
private sqlconnection conn; //执行对象 sqlcommand com; //表、存储过程、视图名称
string excutename; //事务 sqltransaction tran; //sql语句 stringbuilder sqlbuilderstring; //参数 sqlparameter[] paras; private sqlhelper()
{
} /// <summary>
/// 创建sqlhelper静态方法 /// </summary>
/// <returns></returns>
public static dbhelper getinstance()
{ return new sqlhelper();
} /// <summary>
///
/// </summary>
/// <param name="connectionstring"></param>
/// <returns></returns>
public dbhelper createconnection(string connectionstring)
{ if (!connectioncanuse())
{ this.connectionstring = connectionstring;
conn = new sqlconnection(this.connectionstring);
} return this;
} /// <summary>
/// 检查conn是否能用 /// </summary>
/// <returns></returns>
public bool connectioncanuse()
{ if (conn == null) return false; try
{
conn.open();
conn.close();
}catch(exception e)
{ return false;
}
return true;
} /// <summary>
///
/// </summary>
/// <returns></returns>
public dbhelper openconnection()
{ if(conn.state != connectionstate.open) this.conn.open(); return this;
} /// <summary>
///
/// </summary>
/// <returns></returns>
public dbhelper closeconnection()
{ if(conn.state != connectionstate.closed) this.conn.close(); return this;
} /// <summary>
///
/// </summary>
public void disposedconnection()
{ if (!connectionbeused()) this.conn.dispose();
} /// <summary>
/// 检查数据库是否在被打开使用 /// </summary>
/// <returns></returns>
public bool connectionbeused()
{ if(conn.state == connectionstate.open) return true; return false;
} /// <summary>
///
/// </summary>
/// <returns></returns>
public dbhelper createcommand()
{ if (this.com == null)
{ this.com = new sqlcommand();
com.connection = this.conn;
}
return this;
} /// <summary>
///
/// </summary>
public void disposedcommand()
{ this.com.dispose();
} /// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public dbhelper setcommandtype(commandtype type)
{ this.com.commandtype = type; return this;
} /// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public dbhelper fromname(string name)
{ this.excutename = name; return this;
} /// <summary>
///
/// </summary>
/// <returns></returns>
public dbhelper begintransaction()
{ this.tran = conn.begintransaction();
com.transaction = this.tran; return this;
} /// <summary>
///
/// </summary>
/// <returns></returns>
public dbhelper transactionrowback()
{ if(tran!=null)
{
tran.rollback();
} return this;
} /// <summary>
///
/// </summary>
/// <returns></returns>
public dbhelper transactioncommit()
{ if(tran!=null)
{
tran.commit();
tran = null;
} return this;
} /// <summary>
///
/// </summary>
/// <param name="fields"></param>
/// <param name="where"></param>
/// <param name="otherwhere"></param>
/// <returns></returns>
public dbhelper select(string fields = "*",dictionary<string,object> where = null,string otherwhere = "")
{
sqlbuilderstring = new stringbuilder();
sqlbuilderstring.appendline("select " + fields + " from " + this.excutename);
list<sqlparameter> paras = new list<sqlparameter>();
sqlbuilderstring.appendline(" where 1 = 1 ");
if (where != null && where.count > 0)
{
paras = new list<sqlparameter>(); //遍历where,将里面的条件添加到sqlparamter和sql语句中
where.keys.tolist().foreach(o => {
sqlbuilderstring.appendline(" and "+ o + " = @" + o);
paras.add(new sqlparameter(o, where[o]));
});
} if(!string.isnullorempty(otherwhere))
{
sqlbuilderstring.appendline(otherwhere);
} this.paras = paras.toarray(); return this;
}
public dbhelper formultable(string fields)
{
list<string> tables = excutename.split(',').tolist();
fields.split(',').tolist().foreach(o =>
{ for(int i = 0;i<tables.count-2;i++)
{
sqlbuilderstring.appendline(" and " + tables[i] + "." + o + " = " + tables[i + 1] + "." + o);
}
});
return this;
} public dbhelper update(object model,dictionary<string, object> where,string fields = "", string otherwhere = "")
{
type t = model.gettype();
list<string> keys = where.keys.tolist();
sqlbuilderstring = new stringbuilder(); bool firstnode = true;
sqlbuilderstring.appendline("update "+excutename + " set ");
list<sqlparameter> paras = new list<sqlparameter>(); if(string.isnullorempty(fields))
{
t.getproperties().tolist().foreach(o =>
{ if (!firstnode)
sqlbuilderstring.append(","); else
firstnode = false; if(!keys.contains(o.name))
{
sqlbuilderstring.appendline(o.name + " = @"+o.name);
paras.add(new sqlparameter(o.name,o.getvalue(model,null)));
}
});
}else
{
fields.split(',').tolist().foreach(o =>
{
sqlbuilderstring.appendline(o + " = @" + o);
paras.add(new sqlparameter(o, t.getproperty(o).getvalue(model, null)));
});
} this.paras = paras.toarray(); return this;
} public dbhelper insert(object model,string fields = "")
{
list<sqlparameter> paras = new list<sqlparameter>();
type t = model.gettype();
sqlbuilderstring = new stringbuilder();
sqlbuilderstring.appendline("insert " + excutename); if(string.isnullorempty(fields))
{ string s = ""; string s1="";
t.getproperties().tolist().foreach(o =>
{
s += o.name + ",";
s1 += " @" + o.name + ",";
paras.add(new sqlparameter(o.name, o.getvalue(model, null)));
});
s.remove(s.lastindexof(','),1);
s1.remove(s.lastindexof(','), 1);
sqlbuilderstring.appendline("(" + s + ")");
sqlbuilderstring.appendline(" values(" + s1 + ")");
}else
{
sqlbuilderstring.appendline("(" + fields + ")"); string s = "";
fields.split(',').tolist().foreach(o =>
{
s += " @" + o + ",";
paras.add(new sqlparameter(o, t.getproperty(o).getvalue(model, null)));
});
sqlbuilderstring.appendline(" values(" + s + ")");
} this.paras = paras.toarray(); return this;
} public dbhelper delete(dictionary<string,object> where,string otherwhere = "")
{
sqlbuilderstring = new stringbuilder();
list<sqlparameter> paras = new list<sqlparameter>();
sqlbuilderstring.appendline("delete " + excutename);
sqlbuilderstring.appendline(" where 1 = 1 ");
where.keys.tolist().foreach(o =>
{
sqlbuilderstring.appendline(" and " + o + " = @" + o);
paras.add(new sqlparameter(o, where[o]));
}); this.paras = paras.toarray(); return this;
} private void setcommand()
{ if(com.commandtype== commandtype.storedprocedure)
{ this.com.commandtext = excutename;
}else
{ this.com.commandtext = sqlbuilderstring.tostring();
} this.paras.tolist().foreach(o =>
{ this.com.parameters.add(o);
});
} public list<t> tolist<t>() where t:class ,new()
{
list<t> list = new list<t>();
setcommand();
sqldatareader reader = com.executereader();
type t = typeof(t);
list<propertyinfo> pros = t.getproperties().tolist(); while(reader.read())
{
t model = new t();
pros.foreach(o =>
{
o.setvalue(model, reader[o.name], null);
});
list.add(model);
}
reader.dispose(); return list;
} public dataset todataset(string datasetname = "")
{
dataset ds = new dataset();
setcommand();
sqldataadapter adapter = new sqldataadapter(com);
adapter.fill(ds, string.isnullorempty(datasetname) ? this.excutename.replace(",", "_") : datasetname);
adapter.dispose(); return ds;
} public datatable todatatable()
{
datatable dt = new datatable();
setcommand();
sqldataadapter adapter = new sqldataadapter(com);
adapter.fill(dt);
adapter.dispose(); return dt;
} public object result()
{
setcommand(); return com.executescalar();
} public int excuteresult()
{
setcommand(); return com.executenonquery();
} public dbhelper excuteproc(dictionary<string,object> parameter)
{
list<sqlparameter> paras = new list<sqlparameter>();
parameter.keys.tolist().foreach(o =>
{
paras.add(new sqlparameter(o, parameter[o]));
}); return this;
} public void userdefineoperation(action<dynamic> fun)
{
fun(this.com);
}
}
}
最后还有两个事务的方法,前面忘记说了,其实就是sqltransaction,在里面的sqlcommand加上这个,就可以实现。或许有人会问,如果有同一时间段有好几个sqlcommand怎么办?不会的,sqlcommand我也设置成单例,就不会发生控制不了的事情了。
结束语:第一次的博客,我虽然做过不少“幼稚作品”,毕竟我是大三学生,如果随意的写文章,我担心只是会成为被嘲笑的对象,幼稚的“作品”也不好意思放在网上给大家看。所以,在想了几天,写了我觉得蛮有用的封装,虽然可能对许多项目不起作用,但是读者可以自己在更深的思考。
这个框架,我觉得应该还能更好的封装,比如从sql语句组合,调用的时候发生异常处理,怎么更好的实现链式组合,多数据库的处理控制,加上锁我觉得也是可以,毕竟做web的时候可不是像winform,每个端都有自己的connection。还有一个我觉得不错的,就是在模型上做处理,加上特性,让框架能够识别主键,外键,在程序中建立sql中的联系等。那么就给读者思考了。
以上就是详解c# .net更智能的数据库操作的封装的详细内容。