不用争论哪种模型好,而是在每种模型下面怎么样做到最好.
对于企业来说,高效的赢利才是根本,在开发过程中应该追求中庸之道才能达到技术与赢利效率的平衡. 软件过于平铺简单会导致后期维护开发成本大大提升,尽管前期看起来很快;软件设计过于复杂导致整个过程有很大的成本浪费. 具体点应该坚持两个原则:
1. 绝对避免过程式开发:停留在脚本语句,asp之类解释性语言的编程模式上不可取.
2. 尽量避免引入各种框架: 编程模型跟着官方走,别找一大堆外在框架堆积在系统中.
对应的采取的方法是:
1. 充分利用现代的,成熟的面各对象的语言,充分利用三个特性:封装,继承,多态. 就能完美的解决80%的问题,剩下20%的问题由设计模式解决.
2. 充分理解各种框架的原理,然后根据应用规模自己模拟其核心实现一个精简版本,就能完全解决各种性能,扩展性,可靠性,效率等软件质量方面的问题.
如果不按照上面做也会导致两个非常糟糕的局面.
1. 随着项目的庞大和时间和流长,代码累积乱成麻:一是不敢轻易对核心业务的乱代码进行升级修改,二是在修改的时候要花费大量时间去理解原来过程式代码,三是非常多的垃圾无用代码没人能清理,大量重复代码没有敢重构.
2.随着项目的庞大和时间和流长,各种框架乱成麻:一是初始感觉良好的框架多半会出现严重的性能问题.二是可控性上会失控:控制不了细粒度实现,控制不了框架的完整性,会被来来去去的各种种色色的人搞得乱七八糟.三是新人进来会花费大量成本在框架学习和理解上,更要命的是学习前人破坏得面目全非的一个烂框架.
最终的解决方案就是写一个中庸的,真正面向对象的,现代的,半自动化的,半熟的程序结构.写好之后,任何项目,任何公司直接复制,通过简单的复制替换方法就能把底层物件全部搭建好.
下面以.NET为例.
三层基础结构: Entity,DataAccess,Components
Data.dll
数据帮助类:
using System;
using System.Data;
using System.Data.Common;
using System.Configuration;
using WQFree.Configuration;
using System.Data.OracleClient;
namespace WQFree.Data
{
public class DataHelper
{
protected ConnectionStringSettings _connectionStrings;
protected DbProviderFactory factory;
protected bool autoClose = true;
protected IDbConnection _connection = null;
public bool AutoClose
{
get { return autoClose; }
set { autoClose = value; }
}
public IDbConnection Connection
{
get
{
if (null == _connection)
{
IDbConnection conn = factory.CreateConnection();
conn.ConnectionString = _connectionStrings.ConnectionString;
conn.Open();
_connection = conn;
}
else if (_connection.State != ConnectionState.Open)
_connection.Open();
return _connection;
}
set
{
_connection = value;
}
}
public ConnectionStringSettings ConnectionStrings
{
get { return _connectionStrings; }
set { _connectionStrings = value; }
}
public DataHelper(string providerName, IDbConnection connection)
{
this._connection = connection;
this._connectionStrings = new ConnectionStringSettings(providerName, connection.ConnectionString, providerName);
Init();
}
public DataHelper(string providerName, IDbConnection connection, bool autoClose)
{
this.autoClose = autoClose;
this._connection = connection;
this._connectionStrings = new ConnectionStringSettings(providerName, connection.ConnectionString, providerName);
Init();
}
public DataHelper(ConnectionStringSettings connectionStrings)
{
this._connectionStrings = connectionStrings;
Init();
}
protected void Init()
{
if (null == factory)
{
factory = DbProviderFactories.GetFactory(_connectionStrings.ProviderName);
}
}
public IDataReader ExcuteDataReader(string cmdText, CommandType cmdType, params IDataParameter[] parameters)
{
IDbConnection conn = Connection;
DbCommand cmd = conn.CreateCommand() as DbCommand;
cmd.CommandText = cmdText;
cmd.CommandType = cmdType;
cmd.Parameters.AddRange(parameters);
if (!autoClose)
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
return cmd.ExecuteReader();
}
public object ExecuteScalar(string cmdText, CommandType cmdType, params IDataParameter[] parameters)
{
IDbConnection conn = Connection;
DbCommand cmd = conn.CreateCommand() as DbCommand;
cmd.CommandText = cmdText;
cmd.CommandType = cmdType;
cmd.Parameters.AddRange(parameters);
object o = cmd.ExecuteScalar();
if (autoClose) conn.Close();
return o;
}
public DataSet ExecuteDataSet(string cmdText, CommandType cmdType, string tableName, params IDataParameter[] parameters)
{
IDbConnection conn = Connection;
DbCommand cmd = conn.CreateCommand() as DbCommand;
cmd.Parameters.AddRange(parameters);
cmd.CommandText = cmdText;
cmd.CommandType = cmdType;
DbDataAdapter da = factory.CreateDataAdapter();
da.SelectCommand = cmd;
DataSet ds = new DataSet(tableName);
da.Fill(ds);
if (autoClose) conn.Close();
return ds;
}
public int Fill(DataSet ds, string cmdText, CommandType cmdType, params IDataParameter[] parameters)
{
IDbConnection conn = Connection;
DbCommand cmd = conn.CreateCommand() as DbCommand;
cmd.Parameters.AddRange(parameters);
cmd.CommandText = cmdText;
cmd.CommandType = cmdType;
DbDataAdapter da = factory.CreateDataAdapter();
da.SelectCommand = cmd;
da.AcceptChangesDuringFill = false;
int n = da.Fill(ds);
if (autoClose) conn.Close();
return n;
}
public int ExecuteNonQuery(string cmdText, CommandType cmdType, params IDataParameter[] parameters)
{
IDbConnection conn = Connection;
DbCommand cmd = conn.CreateCommand() as DbCommand;
cmd.CommandText = cmdText;
cmd.CommandType = cmdType;
cmd.Parameters.AddRange(parameters);
int n = cmd.ExecuteNonQuery();
if (autoClose) conn.Close();
return n;
}
public virtual IDbDataParameter CreateParameter(string name, object value)
{
DbParameter p = factory.CreateParameter();
p.ParameterName = name;
p.Value = value;
return p;
}
public virtual IDbDataParameter CreateParameter(string name, DbType type, object value)
{
DbParameter p = factory.CreateParameter();
p.ParameterName = name;
p.DbType = type;
p.Value = value;
return p;
}
public virtual IDbDataParameter CreateParameter(string name, ParameterDirection direction, DbType type)
{
DbParameter p = null;
p=factory.CreateParameter();
p.DbType = type;
p.Direction = direction;
p.ParameterName = name;
p.DbType = type;
return p;
}
public virtual IDbDataParameter CreateParameter(string name, ParameterDirection direction, DbType type,int size)
{
DbParameter p = null;
p = factory.CreateParameter();
p.DbType = type;
p.Direction = direction;
p.ParameterName = name;
p.DbType = type;
p.Size = size;
return p;
}
public virtual IDbDataParameter CreateParameter(string name, DbType type, int size, object value)
{
DbParameter p = factory.CreateParameter();
p.ParameterName = name;
p.Size = size;
p.DbType = type;
p.Value = value;
return p;
}
public virtual IDbDataParameter CreateParameter(string name, DbType type, ParameterDirection direction, object value)
{
DbParameter p = factory.CreateParameter();
p.ParameterName = name;
p.DbType = type;
p.Direction = direction;
p.Value = value;
return p;
}
public virtual IDbDataParameter CreateParameter(string name, DbType type, ParameterDirection direction, int size, object value)
{
DbParameter p = factory.CreateParameter();
p.ParameterName = name;
p.DbType = type;
p.Direction = direction;
p.Size = size;
p.Value = value;
return p;
}
}
}
数据访问对象基类:
using System;
using System.Data;
using System.Reflection;
using System.Configuration;
using System.Reflection.Emit;
using System.Collections.Generic;
namespace WQFree.Data
{
public class DAO<T> : IDisposable
{
protected DataHelper hp = null;
public IDbConnection Connection
{
get
{
return hp.Connection;
}
set
{
hp.Connection = value;
}
}
public bool AutoClose
{
get { return hp.AutoClose; }
set { hp.AutoClose = value; }
}
public DAO() { hp = HelpFactory.Instance(); }
public DAO(ConnectionStringSettings connectionStrings)
{
hp = HelpFactory.Instance(connectionStrings);
}
public DAO(string providerName, IDbConnection connection)
{
hp = HelpFactory.Instance(providerName, connection);
}
public DAO(string providerName, IDbConnection connection, bool mustClose)
{
hp = HelpFactory.Instance(providerName, connection, mustClose);
}
public List<T> BuildList(DataTable dt)
{
return EntityBuilder<T>.BuildList(dt);
}
public T Build(DataTable dt)
{
return EntityBuilder<T>.Build(dt);
}
public T Build(DataRow row)
{
return EntityBuilder<T>.Build(row);
}
public List<T> BuildList(IDataReader dr)
{
return EntityBuilder<T>.BuildList(dr);
}
public T Build(IDataReader dr)
{
return EntityBuilder<T>.Build(dr);
}
public void Close()
{
IDbConnection conn = Connection;
if (conn != null && conn.State == ConnectionState.Open)
conn.Close();
}
#region IDisposable 成员
public void Dispose()
{
Close();
}
#endregion
}
}
高性能实体映射类:
using System;
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
namespace WQFree.Data
{
public class EntityBuilder<T>
{
private static readonly MethodInfo getValueMethod = typeof(DataRow).GetMethod("get_Item", new Type[] { typeof(int) });
private static readonly MethodInfo isNullMethod = typeof(DataRow).GetMethod("IsNull", new Type[] { typeof(int) });
private static readonly MethodInfo isDBNullMethod = typeof(IDataRecord).GetMethod("IsDBNull", new Type[] { typeof(int) });
private static readonly MethodInfo getRecordValueMethod = typeof(IDataRecord).GetMethod("get_Item", new Type[] { typeof(int) });
private delegate T LoadDataRow(DataRow dataRow);
private LoadDataRow dataRowHandler;
private delegate T LoadRecord(IDataRecord dataRecord);
private LoadRecord recordHander;
private static Dictionary<string, EntityBuilder<T>> builders = new Dictionary<string, EntityBuilder<T>>();
public T BuildEntity(DataRow dataRow)
{
return dataRowHandler(dataRow);
}
public T BuildEntity(IDataRecord dataRecord)
{
return recordHander(dataRecord);
}
private static EntityBuilder<T> CreateBuilder(DataRow dataRow)
{
Type type = typeof(T);
string key="Row:"+type.Name;
if (builders.ContainsKey(key))
return builders[key];
EntityBuilder<T> dynamicBuilder = new EntityBuilder<T>();
DynamicMethod method = new DynamicMethod("DataRowCreateT", type, new Type[] { typeof(DataRow) }, typeof(T), true);
ILGenerator generator = method.GetILGenerator();
LocalBuilder result = generator.DeclareLocal(type);
generator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
generator.Emit(OpCodes.Stloc, result);
int len = dataRow.ItemArray.Length;
for (int i = 0; i < len; i++)
{
System.Reflection.PropertyInfo propertyInfo = type.GetProperty(dataRow.Table.Columns[i].ColumnName);
Label endIfLabel = generator.DefineLabel();
if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
{
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Callvirt, isNullMethod);
generator.Emit(OpCodes.Brtrue, endIfLabel);
generator.Emit(OpCodes.Ldloc, result);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Callvirt, getValueMethod);
generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
generator.MarkLabel(endIfLabel);
}
}
generator.Emit(OpCodes.Ldloc, result);
generator.Emit(OpCodes.Ret);
dynamicBuilder.dataRowHandler = (LoadDataRow)method.CreateDelegate(typeof(LoadDataRow));
builders.Add(key, dynamicBuilder);
return dynamicBuilder;
}
private static EntityBuilder<T> CreateBuilder(IDataRecord dataRecord)
{
Type type = typeof(T);
string key = "Record:" + type.Name;
if (builders.ContainsKey(key))
return builders[key];
EntityBuilder<T> dynamicBuilder = new EntityBuilder<T>();
DynamicMethod method = new DynamicMethod("DataRecordCreateT", type, new Type[] { typeof(IDataRecord) }, typeof(T), true);
ILGenerator generator = method.GetILGenerator();
LocalBuilder result = generator.DeclareLocal(type);
generator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
generator.Emit(OpCodes.Stloc, result);
int len = dataRecord.FieldCount - 1;
for (int i = 0; i < len; i++)
{
PropertyInfo propertyInfo = type.GetProperty(dataRecord.GetName(i));
Label endIfLabel = generator.DefineLabel();
if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
{
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Callvirt, isDBNullMethod);
generator.Emit(OpCodes.Brtrue, endIfLabel);
generator.Emit(OpCodes.Ldloc, result);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Callvirt, getRecordValueMethod);
generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
// generator.Emit(OpCodes.Unbox_Any, dataRecord.GetFieldType(i));
generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
generator.MarkLabel(endIfLabel);
}
}
generator.Emit(OpCodes.Ldloc, result);
generator.Emit(OpCodes.Ret);
dynamicBuilder.recordHander = (LoadRecord)method.CreateDelegate(typeof(LoadRecord));
builders.Add(key, dynamicBuilder);
return dynamicBuilder;
}
public static List<T> BuildList(DataTable dt)
{
List<T> list = new List<T>();
if (dt == null || dt.Rows.Count==0) return list;
EntityBuilder<T> builder = EntityBuilder<T>.CreateBuilder(dt.Rows[0]);
foreach (DataRow info in dt.Rows)
list.Add(builder.BuildEntity(info));
dt.Dispose(); dt = null;
return list;
}
public static T Build(DataRow row)
{
EntityBuilder<T> builder = CreateBuilder(row);
return builder.BuildEntity(row);
}
public static T Build(DataTable dt)
{
EntityBuilder<T> builder = CreateBuilder(dt.Rows[0]);
return builder.BuildEntity(dt.Rows[0]);
}
public static List<T> BuildList(IDataReader dr)
{
EntityBuilder<T> builder = CreateBuilder(dr);
List<T> list = new List<T>();
if (dr == null) return list;
while (dr.Read())
list.Add(builder.BuildEntity(dr));
return list;
}
public static T Build(IDataReader dr)
{
EntityBuilder<T> builder = CreateBuilder(dr);
if (dr.Read())
return builder.BuildEntity(dr);
return default(T);
}
}
}
DataAccess.dll
具体实现类:
public class UserDao : DAO<User>
{
const string saveSP = "PKG_WQFree.UserSaveSP";
public int Save(User entity)
{
IDbDataParameter[] pars = new IDbDataParameter[]
{
hp.CreateParameter("InEntityId",entity.EntityId),
hp.CreateParameter("InUserName",entity.UserName)
};
return hp.ExecuteNonQuery(saveSP, CommandType.StoredProcedure, pars);
}
public int Save(List<User> entities)
{
int ret = 0;
this.AutoClose = false;
using (IDbConnection conn = Connection)
{
foreach (User art in entities)
{
ret += Save(art);
}
}
return ret;
}
public int Save(DataTable datas)
{
List<User> entities = this.BuildList(datas);
return Save(entities);
}
}
Components.dll
组件层:
public class UserMgr
{
UserDao dao = new UserDao();
public int Save(User entity)
{
try
{
return dao.Save(entity);
}
catch (Exception e)
{
Log.Error("保存User失败:" + e.Message);
}
return -1;
}
public List<User> BuildList(DataTable dt)
{
try
{
return dao.BuildList(dt);
}
catch (Exception e)
{
Log.Error("获取User列表失败:" + e.Message);
}
return new List<User>();
}
public int Save(DataTable datas)
{
try
{
return dao.Save(datas);
}
catch (Exception e)
{
Log.Error("保存User失败:" + e.Message);
}
return -1;
}
}
上面几层次的基本实现,严格按照结构,格式和风格来写,不要做任何改变,不要加入任何业务逻辑:可用复制替换的方法快速开发完毕,就是一个高效可完全控制核心代码的ORM.
上面的实现中留给读者两个小作业题自行解决:
1. 兼容Oracle 和Sql数据库,可以通过DataHelperFactory来实现,怎么解决OracleType和DbType之间的映射问题?
2. 在"实体映射类"中怎么样解决Oracle和SQL对应实体属性名称大小写问题?
请期待下一节:完美开发模型之贫血模型参考(II):Service/Facade/UI层
请期待下一篇:完美开发模型之领域模型参考
如有疑问请发: [email protected]