一、开篇
首先、园子里面之前的很多同仁已经讨论过了ORM相关的框架及其优点和缺点。虽然我本篇讨论的有点晚,但是其毕竟优点大于缺点,本文只是简单的介绍我讨
论ORM的目的,及为什么要讨论这个已经被大家讨论的成熟的不能再成熟的东西。
我们先来看看ORM的优缺点:
本篇将详细分析为什么我们要使用ORM,及ORM前篇的分析建模。
二、本章简介
本篇主要是分析ORM框架的优缺点及项目中的取舍,找到突破点,并且通过图形化的方式分析ORM应该具备的基本功能及如何构建ORM中的核心模块。这里面简单
列举出ORM中的核心模块:
下面将分别讲解和分析实现方案。
三、本章内容
1、摘要。
2、本章简介。
3、本章内容。
4、ORM的应用性分析。
5、ORM设计分析。
6、本章总结。
7、系列进度。
8、下篇预告。
四、ORM的应用性分析
首先、在软件开发中我们都知道OO面向对象的思想对我们现有的软件开发意义,我们可以把软件开发的过程理解为将现实社会的抽象过程。面向对象的思想把现
实世界抽象为万物皆为对象,通过对象之间的交互完成所有的活动。OO出现之前的软件开发都是面向过程的开发思想。面向过程关系的是过程而不是对象。在某个动作
过程中的步骤,通过一系列的函数来解决问题。
面向对象则把一切事物看作对象,而过程就是对象之间的交互或是对象内部的活动。
我们知道目前流行的数据库都是关系型数据库,二维的数据库结构。我们如何将某个对象与这个实体对应起来呢?这就成了我们更关心的问题,这时候ORM思想
的出现解决了这样的问题。
上图反映了实体对象与数据库表的关系,一个实体对象对应数据库表中的一个行记录。而通过DDL操作中的查询方法,来将数据库表中的行纪录映射到多个实体对
象中。而通过ORM提供的DDL操作方法,将实体对象的数据持久化到对应的数据库表中。
另外一个需要注意的问题就是当实体中的属性添加或减少时或是数据库表中的结构发生变化时,如何做到实体中的属性与数据库表中的列一一对应这是个每个ORM
头疼的问题,因为ORM无法实现自动的同步这样的变化。当然目前的大名鼎鼎的Nhibernate在这方面也是处理的比较灵活,这是必须肯定的。当然在这个系列中我们也
会详细的讲解实现的思路与方案,如何处理实体与数据库表结构发生变化时的同步问题,当然这和采用的ORM的实现方式有关。
ORM思想给我提供了如下的方便:
当然ORM框架也不是万能的,有优点的必然存在这一定的缺点,我们来看看ORM的不足:
通过上面的分析我们简单的了解了ORM的优缺点,那么如何在项目中应用它呢,我们在使用某个技术时肯定是扬长避短,所以ORM也是一样的道理,如果我们在项目中有大量的DDL操作语句,并且对业务逻辑之间的多实体间的关联关系不是特别的紧密时,那么用ORM技术就会比较好。
如果在项目中多表的关联查询比较多,并且表之间的逻辑关系比较复杂时就不推荐用ORM来处理。不但会提高项目的复杂度,维护起来代价也比较大。例如像统
计分析系统。用ORM来实现就比较麻烦。
五、ORM设计分析
首先我们来看看数据库访问的通用组件模型:
上图大概画出了比较常用的几类数据库,通过ORM中的数据库访问组件来实现数据库的访问。当然我们这里通过定义数据库访问统一接口的形式,让所有的数据
库访问类都默认继承实现这个接口。
实例代码如下:
public interface IDBAccessor
{
/// <summary>
/// 执行Update,Delete,Insert语句方法
/// </summary>
/// <returns>返回影响的行数</returns>
int Excute();
/// <summary>
/// 执行查询方法
/// </summary>
void Query();
}
接口中只是定义了简单的DDL语言中的四个基本的操作。
下面看每个不同数据库的实现方法。
SQLServer数据库
public class SQLServer : IDBAccessor
{
#region IDBAccessor 成员
private string commandStr = string.Empty;
private static string connectionString = "";
private System.Data.IDbConnection sqlConnection = new System.Data.SqlClient.SqlConnection(connectionString);
public int Excute()
{
if (sqlConnection.State != System.Data.ConnectionState.Open)
sqlConnection.Open();
try
{
using (System.Data.IDbCommand command = sqlConnection.CreateCommand())
{
command.CommandText = commandStr;
return command.ExecuteNonQuery();
}
}
catch(System.Exception)
{
return -1;
}
finally
{
}
}
public void Query()
{
}
#endregion
}
Oracle数据库
public class Oracle : IDBAccessor
{
#region IDBAccessor 成员
private string commandStr = string.Empty;
private static string connectionString = "";
private System.Data.IDbConnection oraConnection = new System.Data.OracleClient.OracleConnection(connectionString);
public int Excute()
{
if (oraConnection.State != System.Data.ConnectionState.Open)
oraConnection.Open();
try
{
using (System.Data.IDbCommand command = oraConnection.CreateCommand())
{
command.CommandText = commandStr;
return command.ExecuteNonQuery();
}
}
catch (System.Exception)
{
return -1;
}
finally
{
}
}
public void Query()
{
throw new NotImplementedException();
}
#endregion
}
其他的几个类型的数据库我们就不一一举例说明了,当然我这里面的接口中并没有考虑把数据库连接也定义成接口,让所有的都从这个接口进行继承,因为这个不是
本章讨论的重点,本章只是简单的分析与设计如何实现通用把数据层访问。
下面我们来说说对象关系映射的实现。
我们比较常见的方式目前就这样的2种方式,第一种方式想必大家都比较了解的,无论是JAVA中的Hibernate还是.NET中的Nhibernate都是这样的方式,以XML文
件的方式把数据库中的表列属性与实体的属性一一对应。第二种方式则是在类文件中硬编码书写数据库列与实体之间的映射关系。
下面我们来分析下这二种方式的利弊:
以上大概描述了各自的有点,下面再阐述下各自的缺点。
当然以上的2种形式各有优缺点,我们已经讲述了XML配置文件中现有的开源框架中采用这种形式的框架有Nhibernate。而采用类文件映射的框架其实有很多,但
是他们的思想相对来说都是一样的。不管是XML文件形式,还是类文件形式,他们的主要观点都是实现如何把实体的属性与数据库表字段的对应,这个才是核心的内容。
下面我们给出一种简单的思路去完成这样的映射,当然我们这里是以类文件形式给出示例。
其实博客园的很多人都写过类文件映射的实例。我这里当然也只是抛砖引玉,不足之处在所难免,还请大家多多提出意见。
我给出的方式是通过特性(Attribute)+反射(Rflection)的思想来实现ORM映射。
具体相应代码如下:
/// <summary>
/// Model中的字段属性特性
/// </summary>
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class PropertyAttribute : Attribute
{
private string dbColumnName;
private bool isPrimary;
private DbType dbType;
private object defaultValue;
private bool isIdentify;
private int length;
public string DbColumnName
{
get
{
return this.dbColumnName;
}
set
{
this.dbColumnName = value;
}
}
public bool IsPrimary
{
get
{
return this.isPrimary;
}
set
{
this.isPrimary = value;
}
}
public bool IsIdentify
{
get
{
return this.isIdentify;
}
set
{
this.isIdentify = value;
}
}
public DbType DbType
{
get
{
return this.dbType;
}
set
{
this.dbType = value;
}
}
public object DefaultValue
{
get
{
return this.defaultValue;
}
set
{
this.defaultValue = value;
}
}
public int DbLength
{
get
{
return this.length;
}
set
{
this.length = value;
}
}
public PropertyAttribute(string dbName, bool isPrimery, DbType type,object dValue)
{
this.dbColumnName = dbName;
this.isPrimary = isPrimery;
this.dbType = type;
this.defaultValue = this.GetDefaultValue();
}
private object GetDefaultValue()
{
return new object();
}
public PropertyAttribute(string dbName)
{
this.dbColumnName = dbName;
this.isPrimary = false;
this.dbType = DbType.String;
this.defaultValue = this.GetDefaultValue();
}
public PropertyAttribute(string dbName,bool isPrimery)
{
this.dbColumnName = dbName;
this.isPrimary = isPrimery;
this.dbType = DbType.String;
this.defaultValue = this.GetDefaultValue();
}
public PropertyAttribute(string dbName, bool isPrimery, DbType type)
{
this.dbColumnName = dbName;
this.isPrimary = isPrimery;
this.dbType = type;
this.defaultValue = null;
}
}
上面给出的是字段属性上定义的特性,我们来看看表的特性:
/// <summary>
/// 基于表的自定义特性类
/// </summary>
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public class TableAttribute : Attribute
{
private string dbTableName;
public TableAttribute(string dbName)
{
this.dbTableName = dbName;
}
public string TableName
{
get
{
return this.dbTableName;
}
set
{
this.dbTableName = value;
}
}
}
在实体层的具体使用如下:
/// <summary>
/// 管理员账户ID
/// </summary>
[PropertyAttribute("",false,System.Data.DbType.Int32,0)]
public int AdminId
{
set
{
_adminid = value;
}
get
{
return _adminid;
}
}
基于表上的特性如下使用:
[TableAttribute("es_memberaccount")]
public class Account
{
public Account()
{
}
}
下面看看如何在生成SQL语句层中的处理方法:
/// <summary>
/// 返回Model对应的数据库表名
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="model"></param>
/// <returns></returns>
public string DbTableName<T>(T model)
{
string dbName = string.Empty;
DPM.Common.TableAttribute attr = null;
object[] attributes = model.GetType().GetCustomAttributes(typeof(DPM.Common.TableAttribute), true);
if (attributes.Length > 0)
{
attr = (DPM.Common.TableAttribute)attributes[0];
}
if (attr != null)
dbName = attr.TableName;
return dbName;
}
/// <summary>
/// 返回数据库表中的所有数据列
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="model"></param>
/// <returns></returns>
public string InitDbColumns<T>(T model)
{
StringBuilder commandBuilder = new StringBuilder();
DPM.Common.PropertyAttribute attr = null;
foreach (PropertyInfo property in model.GetType().GetProperties())
{
object[] attributes = property.GetCustomAttributes(typeof(DPM.Common.PropertyAttribute), true);
if (attributes.Length > 0)
{
attr = (DPM.Common.PropertyAttribute)attributes[0];
}
commandBuilder.Append(attr.DbColumnName+”,”);
}
return commandBuilder.ToString().Substring(0,commandBuilder.ToString().Length-1);
}
六、本章总结
本章简单讲述了ORM实现的基本思路分析及ORM框架使用的优缺点及在项目中如何合理的分析与应用。下面我们来简单总结下本章讲解的内容。
本章主要讲述了ORM的优点:减少工作流,复用性高,开发速度快,更关注业务方面的开发,将DDL操作中除了联合查询实现起来比较复杂外,其他的基本上都
能正常的处理。缺点:一对多或者多对多的关联关系无法很好的满足需求外,还有就是性能上会有一定的影响。在项目中应根据项目的业务需求来决定是否在项目中使
用ORM框架来解决问题。
七、系列进度
2、Step by Step-构建自己的ORM系列-数据访问层
3、Step by Step-构建自己的ORM系列-配置管理层
4、Step by Step-构建自己的ORM系列-对象映射层[上]
5、Step by Step-构建自己的ORM系列-对象映射层[中]
6、Step by Step-构建自己的ORM系列-对象映射层[下]
7、Step by Step-构建自己的ORM系列-测试ORM框架
8、Step by Step-构建自己的ORM系列-瓶颈、优化
9、Step by Step-构建自己的ORM系列-实例
八、下篇预告
下篇我们将讲解如何实现通用的数据访问层,将会详细的介绍如何设计出通用的数据访问层,并且采用设计模中的2个原则:低耦合、高内聚等一些设计规范和原
则。欢迎大家拍砖和提出好的意见和建议。