实体框架一个基本的功能就是要与数据库交互,与数据库交互当然可以利用现有的一些基本技术,比如dotnet的ADO.Net等。这里有几个要点:
1)要封装底层的数据库交互通道,这个可以利用现有的数据库访问技术,比如ADO.Net。
2)构造与数据库交互的SQL语句,比如新增,删除,修改,查询等.做得比较好的,需要支持关联查询等。当然能做到AEF中的ESQL那样,还是有点难度。
下面是一个实例,这里我只构造插入语句,并实现现数据库插入实体的功能示意(底层数据库访问封装这里不讲,这个有很多例子)。
为了简单起见,我们假设实体类的类名就是数据库里对应表的表名,实体的属性对应数据库表里的字段,下面就是代码:
~~~
//实体类。
public class AModel
{
public string Field1{get;set;}
public string Field2 { get; set; }
public int age { get; set; }
public string Name { get; set; }
}
/// <summary>
/// SQL语句执行,可以采用普通类,也可以采用抽象接口,便于不同数据库的访问.
/// </summary>
public static class DatabaseHelper
{
public static void ExecuteSQL(string strSQL, SqlParameter[] Params)
{
;//......................
}
}
/// <summary>
/// 实体数据库基本操作,这里只是模拟实体插入数据库的情形,其它的操作其实类似,更新的话可以采用关键字做条件,冲突检查字段也可包含在条件里面.
/// </summary>
public static class DbOperation
{
public static void InsertModel<T>(T Model)
{
//这里的sql语句及参数构造结果可以采取缓存方式,没必要每次都去构造,以减少性能损失。
Dictionary<string,string> thePP = new Dictionary<string,string>();
Dictionary<string, SqlDbType> thePT = new Dictionary<string, SqlDbType>();
string theSQL = SQLBuilderHelper.BuilderInsertSQL<T>(thePP,thePT);
List<SqlParameter> theParams = new List<SqlParameter>();
foreach (var item in thePP)
{
SqlParameter theP = new SqlParameter();
theP.SqlDbType = thePT[item.Key];
theP.ParameterName = item.Value;
theP.Value = GetPropertyValue(Model, item.Key);
theParams.Add(theP);
}
DatabaseHelper.ExecuteSQL(theSQL.ToString(), theParams.ToArray());
}
//根据属性名获取属性值,可以单独放在一个辅助类里。
private static object GetPropertyValue<T>(T Model, string PropertyName)
{
Type theType = Model.GetType();
PropertyInfo pi = theType.GetProperty(PropertyName);
return pi.GetValue(Model, null);
}
}
/// <summary>
/// SQL及参数构建器,这里构造sql语句的时候,**在这里,默认类名就是数据库表名,属性名就是数据库字段名。但在实际构造这种框架的时候,可以采用元属性或者配置文件来实现这个功能**。比如linqtosql,DbContext就采用元属性,AEF,Hibernate就是采用xml格式的配置文件,但这些东西都是为了更好更准确的构造sql语句.构造这种
///sql语 句 需要知道的包括表名,字段名,字段类型,语言类型与字段类型间的对照等。
/// </summary>
public class SQLBuilderHelper
{
public static string BuilderInsertSQL<T>(Dictionary<string, string> PropertyAndParams,Dictionary<string,SqlDbType> thePAndDBType)
{
Type theType = typeof(T);
PropertyInfo[] theProperties = theType.GetProperties();
StringBuilder theSQL1 = new StringBuilder();
StringBuilder theSQL2 = new StringBuilder();
PropertyAndParams.Clear();
//其实这里也可以利用元属性来觉得哪些需要属性需要插入,哪些属性只能读取之类的判断,以及更新模式(对数字类型是赋值更新还是增量更新)等。
foreach (var p in theProperties)
{
if (theSQL1.ToString() == "")
{
theSQL1.Append(p.Name.ToUpper());
theSQL2.Append("@" + p.Name.ToUpper());
}
else
{
theSQL1.Append(","+p.Name.ToUpper());
theSQL2.Append(",@" + p.Name.ToUpper());
}
PropertyAndParams.Add(p.Name, "@" + p.Name.ToUpper());
thePAndDBType.Add(p.Name, GetDbType(p.PropertyType));
}
return "INSERT INTO " + theType.Name.ToUpper() + "(" + theSQL1.ToString() + ") VALUES(" + theSQL2.ToString() + ")";
}
//C#类型与数据库类型的对照可以通过Map文件完成,也可以通过元属性完成,个人觉得元属性的利用比较好,虽然说元属性不是很灵活,
//但减少了构造的文件依赖, 在大型的项目中其实是有好处的,至少是便于管理的。
private static SqlDbType GetDbType(Type type)
{
if (type == typeof(int))
{
return SqlDbType.Int;
}
return SqlDbType.NVarChar;
}
}
~~~
有的时候我们将实体对象称之为概念模型,而对象在数据库里的存储结构称之为物理模型,在这两个模型之间存在着一种映射关系(概念名称与物理名称的映射,对象属性与字段名之间的映射,以及对象之间关系的映射),这其实就是所谓的ORM(对象关系模型)。上面的例子这种映射是利用命名规范来完成,实际上做的时候可以采用配置文件或者元属性来完成,对于大项目时,配置文件的管理本身也会成为一个问题,因此我觉得用元属性数据来进行会比较好。
上面的实现只是一个简单的示例,实际做的时候需考虑得东西会比较多,比如需要封装数据库差异(底层访问差异,sql语句方言),实体与数据库之间的对照(map),SQL语句及参数缓存问题(没必要每次都构造)等,但不管怎么说,基本的原理还是一样的,关键技术主要是反射机制的应用。
下一篇,将介绍实体管理的功能,比如缓存,加载,新增,删除等....