依照Dapper二次封装了二个易用的OCRUISERM工具类:SqlDapperUtil,把平时能用到的种种CRUD都开展了简化封装,让常常程序员只需关怀工作即可,因为格外简单,故间接贴源代码,大家若需使用能够一贯复制到项目中,该SqlDapperUtil已常见用于公司项目中。
目前成熟的O途乐M如拾草芥,那里只介绍Dapper的施用(最起码小编在运用它,已经运用到项目中,小伙伴们反映还能)。
方今趁着不忙,在动脑筋二个搭建2个开源的完好项目,至于原因以及全体项目框架前面小说作者再作证。既然要起八个完好的品种,那么数量存款和储蓄访问就少不了,那篇小说作者根本介绍这些新项目(OSS.Core)中自作者对仓库储存层的差不离思维和落到实处进度(当前项目还处在搭建阶段),首要集中在以下多少个方面:
【美高梅开户网址】据他们说Dapper三回封装了1个易用的O奥迪Q5M工具类,表明式解析。
近期趁着不忙,在盘算3个搭建多个开源的总体项目,至于原因以及一切项目框架后面文章作者再作证。既然要起3个完完全全的门类,那么数量存款和储蓄访问就少不了,那篇小说作者最首要介绍这么些新项目(OSS.Core)中本人对仓储层的简易思维和促成进程(当前项目还地处搭建阶段),重要汇聚在偏下多少个方面:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using System.Data;
using System.Data.Common;
using System.Reflection;
using System.IO;
using System.Collections.Concurrent;
using System.Data.SqlClient;
namespace Zuowj.Common
{
/// <summary>
/// 基于Dapper的数据操作类封装的工具类
/// Author:左文俊
/// Date:2017/12/11
/// </summary>
public class SqlDapperUtil
{
private static string dbConnectionStringConfigPath = null;
private readonly static ConcurrentDictionary<string, bool> dbConnNamesCacheDic = new ConcurrentDictionary<string, bool>();
private string dbConnectionName = null;
private string dbConnectionString = null;
private string dbProviderName = null;
private IDbConnection dbConnection = null;
private bool useDbTransaction = false;
private IDbTransaction dbTransaction = null;
#region 私有方法
private IDbConnection GetDbConnection()
{
bool needCreateNew = false;
if (dbConnection == null || string.IsNullOrWhiteSpace(dbConnection.ConnectionString))
{
needCreateNew = true;
}
else if (!MemoryCacheUtil.Contains(dbConnectionName))
{
needCreateNew = true;
}
if (needCreateNew)
{
dbConnectionString = GetDbConnectionString(dbConnectionName, out dbProviderName);
var dbProviderFactory = DbProviderFactories.GetFactory(dbProviderName);
dbConnection = dbProviderFactory.CreateConnection();
dbConnection.ConnectionString = dbConnectionString;
}
if (dbConnection.State == ConnectionState.Closed)
{
dbConnection.Open();
}
return dbConnection;
}
private string GetDbConnectionString(string dbConnName, out string dbProviderName)
{
//如果指定的连接字符串配置文件路径,则创建缓存依赖,一旦配置文件更改就失效,再重新读取
string[] connInfos = MemoryCacheUtil.GetOrAddCacheItem(dbConnName, () =>
{
var connStrSettings = ConfigUtil.GetConnectionStringForConfigPath(dbConnName, SqlDapperUtil.DbConnectionStringConfigPath);
string dbProdName = connStrSettings.ProviderName;
string dbConnStr = connStrSettings.ConnectionString;
//LogUtil.Info(string.Format("SqlDapperUtil.GetDbConnectionString>读取连接字符串配置节点[{0}]:{1},ProviderName:{2}", dbConnName, dbConnStr, dbProdName), "SqlDapperUtil.GetDbConnectionString");
return new[] { EncryptUtil.Decrypt(dbConnStr), dbProdName };
}, SqlDapperUtil.DbConnectionStringConfigPath);
dbProviderName = connInfos[1];
return connInfos[0];
}
private T UseDbConnection<T>(Func<IDbConnection, T> queryOrExecSqlFunc)
{
IDbConnection dbConn = null;
try
{
Type modelType = typeof(T);
var typeMap = Dapper.SqlMapper.GetTypeMap(modelType);
if (typeMap == null || !(typeMap is ColumnAttributeTypeMapper<T>))
{
Dapper.SqlMapper.SetTypeMap(modelType, new ColumnAttributeTypeMapper<T>());
}
dbConn = GetDbConnection();
if (useDbTransaction && dbTransaction == null)
{
dbTransaction = GetDbTransaction();
}
return queryOrExecSqlFunc(dbConn);
}
catch
{
throw;
}
finally
{
if (dbTransaction == null && dbConn != null)
{
CloseDbConnection(dbConn);
}
}
}
private void CloseDbConnection(IDbConnection dbConn, bool disposed = false)
{
if (dbConn != null)
{
if (disposed && dbTransaction != null)
{
dbTransaction.Rollback();
dbTransaction.Dispose();
dbTransaction = null;
}
if (dbConn.State != ConnectionState.Closed)
{
dbConn.Close();
}
dbConn.Dispose();
dbConn = null;
}
}
/// <summary>
/// 获取一个事务对象(如果需要确保多条执行语句的一致性,必需使用事务)
/// </summary>
/// <param name="il"></param>
/// <returns></returns>
private IDbTransaction GetDbTransaction(IsolationLevel il = IsolationLevel.Unspecified)
{
return GetDbConnection().BeginTransaction(il);
}
private DynamicParameters ToDynamicParameters(Dictionary<string, object> paramDic)
{
return new DynamicParameters(paramDic);
}
#endregion
public static string DbConnectionStringConfigPath
{
get
{
if (string.IsNullOrEmpty(dbConnectionStringConfigPath))//如果没有指定配置文件,则取默认的配置文件路径作为缓存依赖路径
{
dbConnectionStringConfigPath = BaseUtil.GetConfigPath();
}
return dbConnectionStringConfigPath;
}
set
{
if (!string.IsNullOrWhiteSpace(value) && !File.Exists(value))
{
throw new FileNotFoundException("指定的DB连接字符串配置文件不存在:" + value);
}
//如果配置文件改变,则可能导致连接字符串改变,故必需清除所有连接字符串的缓存以便后续重新加载字符串
if (!string.Equals(dbConnectionStringConfigPath, value, StringComparison.OrdinalIgnoreCase))
{
foreach (var item in dbConnNamesCacheDic)
{
MemoryCacheUtil.RemoveCacheItem(item.Key);
}
}
dbConnectionStringConfigPath = value;
}
}
public SqlDapperUtil(string connName)
{
dbConnectionName = connName;
if (!dbConnNamesCacheDic.ContainsKey(connName)) //如果静态缓存中没有,则加入到静态缓存中
{
dbConnNamesCacheDic[connName] = true;
}
}
/// <summary>
/// 使用事务
/// </summary>
public void UseDbTransaction()
{
useDbTransaction = true;
}
/// <summary>
/// 获取一个值,param可以是SQL参数也可以是匿名对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public T GetValue<T>(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
{
return UseDbConnection((dbConn) =>
{
return dbConn.ExecuteScalar<T>(sql, param, dbTransaction, commandTimeout, commandType);
});
}
/// <summary>
/// 获取第一行的所有值,param可以是SQL参数也可以是匿名对象
/// </summary>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public Dictionary<string, dynamic> GetFirstValues(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
{
return UseDbConnection((dbConn) =>
{
Dictionary<string, dynamic> firstValues = new Dictionary<string, dynamic>();
List<string> indexColNameMappings = new List<string>();
int rowIndex = 0;
using (var reader = dbConn.ExecuteReader(sql, param, dbTransaction, commandTimeout, commandType))
{
while (reader.Read())
{
if ((++rowIndex) > 1) break;
if (indexColNameMappings.Count == 0)
{
for (int i = 0; i < reader.FieldCount; i++)
{
indexColNameMappings.Add(reader.GetName(i));
}
}
for (int i = 0; i < reader.FieldCount; i++)
{
firstValues[indexColNameMappings[i]] = reader.GetValue(i);
}
}
reader.Close();
}
return firstValues;
});
}
/// <summary>
/// 获取一个数据模型实体类,param可以是SQL参数也可以是匿名对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public T GetModel<T>(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null) where T : class
{
return UseDbConnection((dbConn) =>
{
return dbConn.QueryFirstOrDefault<T>(sql, param, dbTransaction, commandTimeout, commandType);
});
}
/// <summary>
/// 获取符合条件的所有数据模型实体类列表,param可以是SQL参数也可以是匿名对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public List<T> GetModelList<T>(string sql, object param = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) where T : class
{
return UseDbConnection((dbConn) =>
{
return dbConn.Query<T>(sql, param, dbTransaction, buffered, commandTimeout, commandType).ToList();
});
}
/// <summary>
/// 获取符合条件的所有数据并根据动态构建Model类委托来创建合适的返回结果(适用于临时性结果且无对应的模型实体类的情况)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="buildModelFunc"></param>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="buffered"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public T GetDynamicModel<T>(Func<IEnumerable<dynamic>, T> buildModelFunc, string sql, object param = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
var dynamicResult = UseDbConnection((dbConn) =>
{
return dbConn.Query(sql, param, dbTransaction, buffered, commandTimeout, commandType);
});
return buildModelFunc(dynamicResult);
}
/// <summary>
/// 获取符合条件的所有指定返回结果对象的列表(复合对象【如:1对多,1对1】),param可以是SQL参数也可以是匿名对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="types"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public List<T> GetMultModelList<T>(string sql, Type[] types, Func<object[], T> map, object param = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return UseDbConnection((dbConn) =>
{
return dbConn.Query<T>(sql, types, map, param, dbTransaction, buffered, splitOn, commandTimeout, commandType).ToList();
});
}
/// <summary>
/// 执行SQL命令(CRUD),param可以是SQL参数也可以是要添加的实体类
/// </summary>
/// <param name="sql"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public bool ExecuteCommand(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
{
return UseDbConnection((dbConn) =>
{
int result = dbConn.Execute(sql, param, dbTransaction, commandTimeout, commandType);
return (result > 0);
});
}
/// <summary>
/// 批量转移数据(利用SqlBulkCopy实现快速大批量插入到指定的目的表及SqlDataAdapter的批量删除)
/// </summary>
public bool BatchMoveData(string srcSelectSql, string srcTableName, List<SqlParameter> srcPrimarykeyParams, string destConnName, string destTableName)
{
using (SqlDataAdapter srcSqlDataAdapter = new SqlDataAdapter(srcSelectSql, GetDbConnectionString(dbConnectionName, out dbProviderName)))
{
DataTable srcTable = new DataTable();
SqlCommand deleteCommand = null;
try
{
srcSqlDataAdapter.AcceptChangesDuringFill = true;
srcSqlDataAdapter.AcceptChangesDuringUpdate = false;
srcSqlDataAdapter.Fill(srcTable);
if (srcTable == null || srcTable.Rows.Count <= 0) return true;
string notExistsDestSqlWhere = null;
string deleteSrcSqlWhere = null;
for (int i = 0; i < srcPrimarykeyParams.Count; i++)
{
string keyColName = srcPrimarykeyParams[i].ParameterName.Replace("@", "");
notExistsDestSqlWhere += string.Format(" AND told.{0}=tnew.{0}", keyColName);
deleteSrcSqlWhere += string.Format(" AND {0}=@{0}", keyColName);
}
string dbProviderName2 = null;
using (var destConn = new SqlConnection(GetDbConnectionString(destConnName, out dbProviderName2)))
{
destConn.Open();
string tempDestTableName = "#temp_" + destTableName;
destConn.Execute(string.Format("select top 0 * into {0} from {1}", tempDestTableName, destTableName));
string destInsertCols = null;
using (var destSqlBulkCopy = new SqlBulkCopy(destConn))
{
try
{
destSqlBulkCopy.BulkCopyTimeout = 120;
destSqlBulkCopy.DestinationTableName = tempDestTableName;
foreach (DataColumn col in srcTable.Columns)
{
destSqlBulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
destInsertCols += "," + col.ColumnName;
}
destSqlBulkCopy.BatchSize = 1000;
destSqlBulkCopy.WriteToServer(srcTable);
}
catch (Exception ex)
{
//LogUtil.Error("SqlDapperUtil.BatchMoveData.SqlBulkCopy:" + ex.ToString(), "SqlDapperUtil.BatchMoveData");
}
destInsertCols = destInsertCols.Substring(1);
destConn.Execute(string.Format("insert into {1}({0}) select {0} from {2} tnew where not exists(select 1 from {1} told where {3})",
destInsertCols, destTableName, tempDestTableName, notExistsDestSqlWhere.Trim().Substring(3)), null, null, 100);
}
destConn.Close();
}
deleteCommand = new SqlCommand(string.Format("DELETE FROM {0} WHERE {1}", srcTableName, deleteSrcSqlWhere.Trim().Substring(3)), srcSqlDataAdapter.SelectCommand.Connection);
deleteCommand.Parameters.AddRange(srcPrimarykeyParams.ToArray());
deleteCommand.UpdatedRowSource = UpdateRowSource.None;
deleteCommand.CommandTimeout = 200;
srcSqlDataAdapter.DeleteCommand = deleteCommand;
foreach (DataRow row in srcTable.Rows)
{
row.Delete();
}
srcSqlDataAdapter.UpdateBatchSize = 1000;
srcSqlDataAdapter.Update(srcTable);
srcTable.AcceptChanges();
return true;
}
catch (Exception ex)
{
//LogUtil.Error("SqlDapperUtil.BatchMoveData:" + ex.ToString(), "SqlDapperUtil.BatchMoveData");
return false;
}
finally
{
if (deleteCommand != null)
{
deleteCommand.Parameters.Clear();
}
}
}
}
/// <summary>
/// 批量复制数据(把源DB中根据SQL语句查出的结果批量COPY插入到目的DB的目的表中)
/// </summary>
public TResult BatchCopyData<TResult>(string srcSelectSql, string destConnName, string destTableName, IDictionary<string, string> colMappings, Func<IDbConnection, TResult> afterCoppyFunc)
{
using (SqlDataAdapter srcSqlDataAdapter = new SqlDataAdapter(srcSelectSql, GetDbConnectionString(dbConnectionName, out dbProviderName)))
{
DataTable srcTable = new DataTable();
TResult copyResult = default(TResult);
try
{
srcSqlDataAdapter.AcceptChangesDuringFill = true;
srcSqlDataAdapter.AcceptChangesDuringUpdate = false;
srcSqlDataAdapter.Fill(srcTable);
if (srcTable == null || srcTable.Rows.Count <= 0) return copyResult;
string dbProviderName2 = null;
using (var destConn = new SqlConnection(GetDbConnectionString(destConnName, out dbProviderName2)))
{
destConn.Open();
string tempDestTableName = "#temp_" + destTableName;
destConn.Execute(string.Format("select top 0 * into {0} from {1}", tempDestTableName, destTableName));
bool bcpResult = false;
using (var destSqlBulkCopy = new SqlBulkCopy(destConn))
{
try
{
destSqlBulkCopy.BulkCopyTimeout = 120;
destSqlBulkCopy.DestinationTableName = tempDestTableName;
foreach (var col in colMappings)
{
destSqlBulkCopy.ColumnMappings.Add(col.Key, col.Value);
}
destSqlBulkCopy.BatchSize = 1000;
destSqlBulkCopy.WriteToServer(srcTable);
bcpResult = true;
}
catch (Exception ex)
{
//LogUtil.Error("SqlDapperUtil.BatchMoveData.SqlBulkCopy:" + ex.ToString(), "SqlDapperUtil.BatchMoveData");
}
}
if (bcpResult)
{
copyResult = afterCoppyFunc(destConn);
}
destConn.Close();
}
return copyResult;
}
catch (Exception ex)
{
//LogUtil.Error("SqlDapperUtil.BatchCopyData:" + ex.ToString(), "SqlDapperUtil.BatchCopyData");
return copyResult;
}
}
}
/// <summary>
/// 当使用了事务,则最后需要调用该方法以提交所有操作
/// </summary>
/// <param name="dbTransaction"></param>
public void Commit()
{
try
{
if (dbTransaction.Connection != null && dbTransaction.Connection.State != ConnectionState.Closed)
{
dbTransaction.Commit();
}
}
catch
{
throw;
}
finally
{
if (dbTransaction.Connection != null)
{
CloseDbConnection(dbTransaction.Connection);
}
dbTransaction.Dispose();
dbTransaction = null;
useDbTransaction = false;
if (dbConnection != null)
{
CloseDbConnection(dbConnection);
}
}
}
/// <summary>
/// 当使用了事务,如果报错或需要中断执行,则需要调用该方法执行回滚操作
/// </summary>
/// <param name="dbTransaction"></param>
public void Rollback()
{
try
{
if (dbTransaction.Connection != null && dbTransaction.Connection.State != ConnectionState.Closed)
{
dbTransaction.Rollback();
}
}
catch
{
throw;
}
finally
{
if (dbTransaction.Connection != null)
{
CloseDbConnection(dbTransaction.Connection);
}
dbTransaction.Dispose();
dbTransaction = null;
useDbTransaction = false;
}
}
~SqlDapperUtil()
{
try
{
CloseDbConnection(dbConnection, true);
}
catch
{ }
}
}
}
优点:
数量存款和储蓄层的必要
O景逸SUVM框架选取
OSS.Core存款和储蓄层设计实现
调用示例
多少存款和储蓄层的急需
O奇骏M框架选拔
OSS.Core仓库储存层设计完结
调用示例
ColumnAttributeTypeMapper协助类相关代码如下:(即便不考虑实体类的属性与表字段不同的事态,如下映射类能够不须要加上,同时SqlDapperUtil中移除相关正视ColumnAttributeTypeMapper逻辑即可)
一 、开源、轻量、小巧、上手不难。
上面的落成部分中或许须求您对.NET的
泛型,委托,扩充,表明式等有二个基础掌握。正是因为那些语言特色,方便我们对操作共性的抽取统一。
下面的达成部分中只怕须要您对.NET的
泛型,委托,扩张,表达式等有一个基础精晓。就是因为这么些语言特色,方便我们对操作共性的抽取统一。
using Dapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace KYExpress.Common
{
public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
{
public ColumnAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(
typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class ColumnAttribute : Attribute
{
public string Name { get; set; }
}
public class FallbackTypeMapper : SqlMapper.ITypeMap
{
private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;
public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
{
_mappers = mappers;
}
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
foreach (var mapper in _mappers)
{
try
{
ConstructorInfo result = mapper.FindConstructor(names, types);
if (result != null)
{
return result;
}
}
catch (NotImplementedException)
{
}
}
return null;
}
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetConstructorParameter(constructor, columnName);
if (result != null)
{
return result;
}
}
catch (NotImplementedException)
{
}
}
return null;
}
public SqlMapper.IMemberMap GetMember(string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetMember(columnName);
if (result != null)
{
return result;
}
}
catch (NotImplementedException)
{
}
}
return null;
}
public ConstructorInfo FindExplicitConstructor()
{
return _mappers
.Select(mapper => mapper.FindExplicitConstructor())
.FirstOrDefault(result => result != null);
}
}
}
二 、扶助的数据库还蛮多的,
Mysql,SqlLite,Sqlserver,Oracle等一各种的数据库。
一. 数码存款和储蓄层需要
一. 多少存款和储蓄层必要
动用示例方法如下:
叁 、Dapper原理通过Emit反射IDataReader的行列队列来快捷的收获和产生对象。质量一般很牛逼的指南
既然是一个完好无缺的档次,数据访问是其最基本的有个别,同时,数据访问也是整个项目最简单并发瓶颈的位置。在自家的分割中,其负责的剧中人物是负担整个数据的输入输出,不仅仅是指向单数据库(有时甚至多库),有时还索要形成拔尖缓存的落到实处,给逻辑层提供最基础的数码帮衬。
既然是一个完完全全的类型,数据访问是其最主旨的一些,同时,数据访问也是整个项目最简单出现瓶颈的位置。在自笔者的细分中,其承担的剧中人物是肩负整个数据的输入输出,不仅仅是针对性单数据库(有时如故多库),有时还要求实现一流缓存的达成,给逻辑层提供最基础的数码支撑。
1.先来效仿各类查询数据(由于是直接写模拟SQL输出,故并未规则,也利于咱们COPY后直接能够测试结果)
缺点:
业务永远是在扭转的,那么项目也要负有火速多变的能力,所以作者愿意数据层能够保险相对的简约,在结构上尽量减少复杂的耦合查询,在性质上尽量收缩不须求的消耗,例如反射的汪洋应用。同时针对种种事情对象完毕数据库层面为主的CRUD统一封装完成。若是有亟待的时候还是能够在至少的更动下插手缓存的换代。(对于什么贯彻不一致模块差异缓存存储策略,像Redis,Memcached会在背后小说介绍)
业务永远是在扭转的,那么项目也要具备快速多变的能力,所以自身梦想数据层可以保障相对的简练,在结构上尽量收缩复杂的耦合查询,在性质上尽量减弱不供给的开支,例如反射的多量应用。同时针对每一个事情对象完毕数据库层面为主的CRUD统一封装完毕。假设有亟待的时候还是能够在至少的变动下参加缓存的更新。(对于哪些完毕不一致模块分化缓存存款和储蓄策略,像Redis,Memcached会在前边作品介绍)
//实例化SqlDapperUtil对象,构造函数是config文件中的connectionStrings的Name名
var dapper = new SqlDapperUtil("LmsConnectionString");
//查询1个值
DateTime nowTime = dapper.GetValue<DateTime>("select getdate() as nowtime");
//查询1行值,并转换成字典(这对于临时查询多个字段而无需定义实体类有用)
Dictionary<string, dynamic> rowValues = dapper.GetFirstValues("select 0 as col0,1 as col1,2 as col2");
//查询1行并返回实体类
Person person = dapper.GetModel<Person>("select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr");
//查询1行表字段与实体类属性不一致映射
Person person2 = dapper.GetModel<Person>("select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddress");
//查询多行返回实体集合
var persons = dapper.GetModelList<Person>(@"select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr union all
select '李四' as Name,25 as Age,'2018-10-1' as BirthDay,'中国广东深圳' as HomeAddress union all
select '王五' as Name,35 as Age,'1982-10-1' as BirthDay,'中国广东广州' as HomeAddress
");
//查询多行返回1对1关联实体结果集
var personWithCarResult = dapper.GetMultModelList<Person>(@"select t1.*,t2.* from
(select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr union all
select '李四' as Name,25 as Age,'2018-10-1' as BirthDay,'中国广东深圳' as HomeAddress union all
select '王五' as Name,35 as Age,'1982-10-1' as BirthDay,'中国广东广州' as HomeAddress)as t1 inner join
(
select '张三' as DriverName,'大众' as Brand,'2018-8-8' as ManufactureDate union all
select '李四' as DriverName,'奔驰' as Brand,'2018-1-8' as ManufactureDate union all
select '王五' as DriverName,'奥迪' as Brand,'2017-8-8' as ManufactureDate
)as t2
on t1.Name=t2.DriverName
", new[] { typeof(Person), typeof(CarInfo) }, (objs) =>
{
Person personItem = objs[0] as Person;
CarInfo carItem = objs[1] as CarInfo;
personItem.Car = carItem;
return personItem;
}, splitOn: "DriverName");
//查询多行返回1对多关联实体结果=personWithManyCars
List<Person> personWithManyCars = new List<Person>();
dapper.GetMultModelList<Person>(@"select t1.*,t2.* from
(select '张三' as Name,22 as Age,'2018-1-1' as BirthDay,'中国广东深圳' as HomeAddr union all
select '李四' as Name,25 as Age,'2018-10-1' as BirthDay,'中国广东深圳' as HomeAddress union all
select '王五' as Name,35 as Age,'1982-10-1' as BirthDay,'中国广东广州' as HomeAddress)as t1 inner join
(
select '张三' as DriverName,'大众' as Brand,'2018-8-8' as ManufactureDate union all
select '张三' as DriverName,'奔驰' as Brand,'2018-1-8' as ManufactureDate union all
select '张三' as DriverName,'奥迪' as Brand,'2017-8-8' as ManufactureDate
)as t2
on t1.Name=t2.DriverName
", new[] { typeof(Person), typeof(CarInfo) }, (objs) =>
{
Person personItem = objs[0] as Person;
CarInfo carItem = objs[1] as CarInfo;
Person personItemMain = personWithManyCars.FirstOrDefault(p => p.Name == personItem.Name);
if (personItemMain == null)
{
personItem.Cars = new List<CarInfo>();
personItemMain = personItem;
personWithManyCars.Add(personItemMain);
}
personItemMain.Cars.Add(carItem);
return personItemMain;
}, splitOn: "DriverName");
作为一款OCRUISERM太过度轻量级了,遵照目的自动生成sql的职能依然空荡荡,供给协调来扩充,
同时,对于三个不怎么有点范围的种类来说,消除数据库访问的最快捷做法正是促成读写分离,所以,小编期望那么些框架可以在一开端在底层就兑现了读写分离的支撑,以制止早先时期再重头对业务代码的汪洋改动。
同时,对于叁个有点有点范围的品类以来,消除数据库访问的最急忙做法正是落成读写分离,所以,小编希望以此框架能够在一开首在底层就实现了读写分离的扶助,以制止中期再重头对作业代码的大批量改动。
2.上边是出现说法如何进展增、删、改以及动态查询的景观:
当然那也是可取, 好声音的良师们平日说某人就是张白纸……
二. O冠道M 框架选拔
二. OCR-VM 框架选用
//使用事务创建多张表,多条SQL语句写在一起
try
{
dapper.UseDbTransaction();
dapper.ExecuteCommand(@"create table T_Person(Name nvarchar(20) primary key,Age int,BirthDay datetime,HomeAddress nvarchar(200));
create table T_CarInfo(DriverName nvarchar(20) primary key,Brand nvarchar(50),ManufactureDate datetime)");
dapper.Commit();
}
catch (Exception ex)
{
dapper.Rollback();
//记日志
}
//使用事务批量插入多张表的多个记录,多条SQL分多次执行(参数支持批量集合对象传入,无需循环)
try
{
dapper.UseDbTransaction();
dapper.ExecuteCommand(@"insert into T_Person
select N'张三' as Name,22 as Age,'2018-1-1' as BirthDay,N'中国广东深圳' as HomeAddress union all
select N'李四' as Name,25 as Age,'2018-10-1' as BirthDay,N'中国广东深圳' as HomeAddress union all
select N'王五' as Name,35 as Age,'1982-10-1' as BirthDay,N'中国广东广州' as HomeAddress");
var carInfos = dapper.GetModelList<CarInfo>(@"
select N'张三' as DriverName,N'大众' as Brand,'2018-8-8' as ManufactureDate union all
select N'李四' as DriverName,N'奔驰' as Brand,'2018-1-8' as ManufactureDate union all
select N'王五' as DriverName,N'奥迪' as Brand,'2017-8-8' as ManufactureDate");
dapper.ExecuteCommand(@"insert into T_CarInfo(DriverName,Brand,ManufactureDate) Values(@DriverName,@Brand,@ManufactureDate)", carInfos);
dapper.Commit();
}
catch (Exception ex)
{
dapper.Rollback();
//记日志
}
//执行删除,有参数,参数可以是实体类、匿名对象、字典(如有需要,可以是集合,以支持批量操作)
bool deleteResult = dapper.ExecuteCommand("delete from T_CarInfo where DriverName=@DriverName", new { DriverName = "李四" });
//构建动态执行SQL语句(以下是更新,查询类似)
StringBuilder updateSqlBuilder = new StringBuilder();
var updateParams = new Dictionary<string, object>();
if (1 == 1)
{
updateSqlBuilder.Append(",Age=@Age");
updateParams["Age"] = 20;
}
if (2 == 2)
{
updateSqlBuilder.Append(",BirthDay=@BirthDay");
updateParams["BirthDay"] = Convert.ToDateTime("2010-1-1");
}
if (3 == 3)
{
updateSqlBuilder.Append(",HomeAddress=@HomeAddress");
updateParams["HomeAddress"] = "中国北京天安门";
}
string updateSql = string.Concat("update T_Person set ", updateSqlBuilder.ToString().TrimStart(','), " where Name=@Name");
updateParams["Name"] = "张三";
bool updateResult = dapper.ExecuteCommand(updateSql, updateParams);
//查询返回动态自定义结果,之所以不直接返回Dynamic就好,是因为可读性差,故尽可能的在执行后就转成指定的类型
Tuple<string, int> hasCarInfo = dapper.GetDynamicModel<Tuple<string, int>>((rs) =>
{
var result = rs.First();
return Tuple.Create<string, int>(result.Name, result.CarCount);
}, @"select a.Name,count(b.DriverName) as CarCount from T_Person a left join T_CarInfo b on a.Name=b.DriverName where a.Name=@Name group by a.Name", new { Name = "张三" });
从而针对Dapper已经有广大成熟的壮大类型了,Dapper.Rainbow、Dapper.Contrib,DapperExtensions。
当然,要是为了不难和性质,直接ADO.NET连接理论上来说是相比火速的做法,不过如此会招致大气的重新操作逻辑代码,同时也会促成代码的紊乱,扩充入保证护复杂度。作为技术人员,不仅要求缓解事情难题进步效能,同时也要增强协调的频率,所以小编会选择三个O帕杰罗M框架来成功部分基础工作。
当然,要是为了简单和质量,直接ADO.NET连接理论上来说是比较高效的做法,不过如此会促成大量的再次操作逻辑代码,同时也会造成代码的眼花缭乱,扩展入保障护复杂度。作为技术人士,不仅供给缓解业务难题进步效能,同时也要拉长本人的频率,所以笔者会选拔1个O安德拉M框架来达成都部队分基础工作。
3.还有八个方法:BatchCopyData、BatchMoveData,那是卓绝封装的,不是依照Dapper而是基于原生的Ado.net及BCP,指标是火速大量跨DB跨表COPY数据或转移数据,使用也不复杂,建议想打听的网络好友能够查看本人过去的篇章
大家那里介绍的是DapperExtensions。
当前在.NET序列下,开源的OPRADOM框架很多,如:Entityframework,NHibernate,iBATIS.NET,Dapper等等,各有特色,基于前面小编说的,保证效能的同时,兼顾简单还是能最大程度收缩品质的消耗,并且提供.net
standard标准库下的援救。那里比较之后笔者选用Dapper那么些半自动化的O智跑M作为仓库储存层的功底框架,选用原因如下:
当前在.NET体系下,开源的O君越M框架很多,如:Entityframework,NHibernate,iBATIS.NET,Dapper等等,各有特色,基于后面笔者说的,保障功效的还要,兼顾不难还是能够最大程度减弱质量的损耗,并且提供.net
standard标准库下的支持。那里比较之后作者选拔Dapper这一个半自动化的O福特ExplorerM作为仓库储存层的根底框架,采纳原因如下:
上述示例方法用到了多少个类,如下:
dapper-dot-net源码:
(更新频率快,项目包蕴了各类除了Dapper-Extensions的扩大类型)
1. 其结构不难,整个封装重要集中Dapper.cs文件中,体量十分的小
1. 其协会简单,整个封装首要集中Dapper.cs文件中,体积十分的小
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public DateTime BirthDay { get; set; }
[Column(Name = "HomeAddress")]
public string HomeAddr { get; set; }
public CarInfo Car { get; set; }
public List<CarInfo> Cars { get; set; }
}
class CarInfo
{
public string Brand { get; set; }
public DateTime ManufactureDate { get; set; }
public string DriverName { get; set; }
}
Dapper-Extensions源码:
2. 装进功效简单强大,对原生SQL的协助上很灵巧
2. 封装成效简单强大,对原生SQL的支撑上很利索
SqlDapperUtil类中凭借了事先自个儿封装的类:如:MemoryCacheUtil(本地内部存款和储蓄器正视缓存实用工具类)、ConfigUtil(配置文件管理工科具类)、EncryptUtil(加密工具类),纵然项目中不想引用这一个类,能够移除或改成任何方法即可。
Dapper-Extensions的优点:
那点大约完胜其余框架,无需任何多余的设置,同时基本上你可调用全数原生ADO.NET的法力,sql语句完全本人掌握控制,却又无需关心command的参数赋值,以及结果实体转换等。
那点大概完胜其余框架,无需任何多余的装置,同时基本上你可调用全体原生ADO.NET的成效,sql语句完全本身掌握控制,却又无需关切command的参数赋值,以及结果实体转换等。
除此以外说多美滋(Dumex)下,为了避防万一和减弱因DB连接未立时放出导致的连接池不足等原因,故私下认可执行全体的CRUD方法都以用完即自由,但有一种景况不会放出便是行使了作业,若接纳工作,则少不了配套使用:UseDbTransaction、Commit、或失利执行Rollback,不然只怕造成未能及时放出对象,当然最后当SqlDapperUtil实例被回收后事务若没有交到或回滚,会强制执行回滚操作并释放工作及连接对象,幸免或许的财富浪费情形。
1、开源
3. 性质上的飞快
3. 属性上的连忙
理所当然早就想总计一下那篇小说,但平昔由于工作太忙没有时间,前些天选取加班切磋.NET
CORE的空子时间成功,请大家帮忙,有好东西本身必然会享用的,就算不肯定高大上,但毫无疑问实用且项目中有实战过的。
② 、针对Dapper封装了常用的CRUD方法,有单独的询问语法。
很多O奇骏M的实体映射通过反射来形成,那点上Dapper再度显示其吸重力,在Commond参数赋值,以及实体转换等重庆大学模块,使用了Reflection.Emit功用,间接完成了MSIL编写翻译层面包车型大巴赋值达成,之所以说间接,是因为其本人代码还须求编译器生成IL代码。在运营时依据项目属性动态创设赋值委托方法。
很多O本田CR-VM的实体映射通过反射来形成,这一点上Dapper再一次突显其魔力,在Commond参数赋值,以及实体转换等首要模块,使用了Reflection.Emit功效,直接达成了MSIL编译层面包车型大巴赋值实现,之所以说直接,是因为其自作者代码还要求编写翻译器生成IL代码。在运营时依照项目属性动态创立赋值委托方法。
叁 、供给映射的实体类自身0配置,无需加天性什么的。是经过独立的映射类来拍卖,能够安装类映射到DB的小名,字段的别称等等。
Dapper-Extensions的缺点:
三. OSS.Core仓库储存层设计完结
三. OSS.Core仓库储存层设计完结
① 、好几年没更新了
通过Dapper能够实现在数据库访问片段一层简易的卷入,可是小编仍旧亟待手动编写不少的sql语句,同时还要举行参数化的处理,包含数据的读写分离等。那么这几个职能的贯彻自笔者将在OSS.Core.RepDapper中成功,为了有利于清楚,先贴出三个简易的包裹后的主意调用传输流程:
通过Dapper能够完结在数据库访问一些一层简易的卷入,然则作者如故供给手动编写不少的sql语句,同时还要开始展览参数化的处理,包括数据的读写分离等。那么那个职能的贯彻自我将在OSS.Core.RepDapper中成就,为了有利于清楚,先贴出3个归纳的包装后的法门调用传输流程:
贰 、不协助oracle(木有oracle的方言,已经消除)
叁 、不能够同时援助多样数据库
在这几个图里呈现二个总结的主意调用流程,围绕那张图的多少个基本部分,我分别介绍下:
在那么些图里体现多个简约的格局调用流程,围绕那张图的多少个宗旨部分,作者分别介绍下:
肆 、部分代码有个别bug
1. 接口设计
1. 接口设计
下面先简单介绍一下Dapper的主干语法。
因为自个儿期待以此是完全的言传身教项目,所从前边希望能够包容不相同数据库,由此对外的积存访问都依照接口调用。当然假若你的连串根本未曾切换数据库的必要,小编更提议去掉这一环节,直接在基类中落到实处单例方式,业务逻辑层直接调用。
因为笔者期望以此是欧洲经济共同体的以身作则项目,所未来面希望能够协作分化数据库,由此对外的蕴藏访问都基于接口调用。当然假如您的类型从来没有切换数据库的须要,小编更提出去掉这一环节,直接在基类中贯彻单例形式,业务逻辑层直接调用。
Dapper就3个.cs文件,能够放置项目代码中央直属机关接编写翻译,也得以一贯引用DLL文件。
图中得以见见接口层独立于达成部分,笔者将切实事务实人体模型型和接口
单独放在了OSS.Core.DomainMos
类库中,一方面是为着实人体模型型在各模块中的共用,另一方面解耦业务逻辑层(Services)和储存层(Reps)之间的依赖关系。
图中得以见到接口层独立于贯彻部分,小编将切实业务实人体模型型和接口
单独放在了OSS.Core.DomainMos
类库中,一方面是为了实体模型在各模块中的共用,另一方面解耦业务逻辑层(Services)和储存层(Reps)之间的依靠关系。
Dapper对DB的操作依赖于Connection,为了援助多库,我们用IDbConnection conn
同时三个项目中数据库访问代码多数都会以CRUD为主,所以这里本身定义了一个基础接口(IBaseRep),其涵盖的艺术首要有(表明式部分在前面介绍):
同时三个项目中数据库访问代码多数都会以CRUD为主,所以那里自个儿定义了三个基础接口(IBaseRep),其含有的主意首要有(表明式部分在末端介绍):
using (IDbConnection conn = GetConnection { const string query = "select * from XO order by id desc"; return conn.Query<XOEntity>(query,null); }
下边是带参数的语法
具体的作业数据接口继承至基础接口就好,当中表明式部分是笔者自身做了多少个打包,前边会简单介绍。
具体的工作数据接口继承至基础接口就好,当中表达式部分是自作者自身做了叁个打包,后面会不难介绍。
int xoID=666; //变量主键
using (IDbConnection conn = GetConnection { const string query = "select * from XO where Id=@MyID"; return conn.Query<XOEntity>(query, new { MyID = xoID}); }
各个法子都重载了业务的操作,一般的数据库操作都支持。但是每一次执行都亟待传递sql,而且每趟都要动用Using,看着不爽啊,
这……
2.
存款和储蓄基类实现(BaseRep)
2.
囤积基类落成(BaseRep)
好啊上面简单介绍下利用Dapper-Extensions的中坚语法(在Dapper-Extensions
的基本功上用了Repository方式,代码效果如下)。
首先,如图所示,大家贯彻了读写分离的七个扩展,其实说到底都会经过Excute方法,那么那里显示下方法的切实落成:
首先,如图所示,大家完毕了读写分离的多个扩充,其实说到底都会经过Excute方法,那么那里展现下方法的实际达成:
//实体类 DemoEntity entity = new DemoEntity(); //根据实体主键删除 this.Delete<DemoEntity>; //根据主键ID删除 this.Delete<DemoEntity>(1); //增加 this.Insert<DemoEntity>; //更新 bool result = this.Update<DemoEntity>; //根据主键返回实体 entity = this.GetById<DemoEntity>(1); //返回 行数 this.Count<DemoEntity>(new { ID = 1 }); //查询所有 IEnumerable<DemoEntity> list = this.GetAll<DemoEntity>(); IList<ISort> sort = new List<ISort>(); sort.Add(new Sort { PropertyName = "ID", Ascending = false }); //条件查询 list = this.GetList<DemoEntity>(new { ID = 1, Name = "123" }, sort); //orm 拼接条件 查询 IList<IPredicate> predList = new List<IPredicate>(); predList.Add(Predicates.Field<DemoEntity>(p => p.Name, Operator.Like, "不知道%")); predList.Add(Predicates.Field<DemoEntity>(p => p.ID, Operator.Eq, 1)); IPredicateGroup predGroup = Predicates.Group(GroupOperator.And, predList.ToArray; list = this.GetList<DemoEntity>(predGroup); //分页查询 long allRowsCount = 0; this.GetPageList<DemoEntity>(1, 10, out allRowsCount, new { ID = 1 }, sort);
在说OOdysseyM在此以前,依旧要说一下HY.DataAccess这几个模块
能够看到在这几个办法提供了1个对准IDbConnection的信托,提供调用层自由使用Dapper方法的还要,统一了数量访问方法入口,便于日志记录,和排查。
能够看到在那些法子提供了二个针对性IDbConnection的嘱托,提供调用层自由使用Dapper方法的同时,统一了数额访问方法入口,便于日志记录,和排查。
以此模块是对数据访问提供的一个Helper的机能,里面包罗了
种种DB的SqlHelper,分页。
其次,在诸多门类中会出现用户和订单在不一致库中的那类情状,因为涉嫌到分库的动静,所以供给子类中能有改动连接串能力,那么那里作者经过构造函数的格局,提供了七个可空参数:
其次,在诸多项目中会出现用户和订单在不相同库中的那类意况,因为涉嫌到分库的事态,所以需求子类中能有修改连接串能力,那么那里自个儿经过构造函数的款型,提供了七个可空参数:
DBHelper 都卫冕自IDBHelper.cs
能够看看,假设子类中定义了和睦的连年串,则以子类自定义为主,不然走默许的总是音讯。
能够看出,固然子类中定义了团结的连天串,则以子类自定义为主,不然走私下认可的连日音信。
using System.Data.Common;using System.Data;namespace HY.DataAccess{ /// <summary> /// 提供对数据库的基本操作,连接字符串需要在数据库配置。 /// </summary> public interface IDBHelper { /// <summary> /// 生成分页SQL语句 /// </summary> /// <param name="pageIndex"></param> /// <param name="pageSize"></param> /// <param name="selectSql"></param> /// <param name="sqlCount"></param> /// <param name="orderBy"></param> /// <returns></returns> string GetPagingSql(int pageIndex, int pageSize, string selectSql, string sqlCount, string orderBy); /// <summary> /// 开始一个事务 /// </summary> /// <returns></returns> DbTransaction BeginTractionand(); /// <summary> /// 开始一个事务 /// </summary> /// <param name="connKey">数据库连接字符key</param> DbTransaction BeginTractionand(string connKey); /// <summary> /// 回滚事务 /// </summary> /// <param name="dbTransaction">要回滚的事务</param> void RollbackTractionand(DbTransaction dbTransaction); /// <summary> /// 结束并确认事务 /// </summary> /// <param name="dbTransaction">要结束的事务</param> void CommitTractionand(DbTransaction dbTransaction); #region DataSet /// <summary> /// 执行sql语句,ExecuteDataSet 返回DataSet /// </summary> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> DataSet ExecuteDataSet(string commandText, CommandType commandType); /// <summary> /// 执行sql语句,ExecuteDataSet 返回DataSet /// </summary> /// <param name="connKey">数据库连接字符key</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> DataSet ExecuteDataSet(string connKey, string commandText, CommandType commandType); /// <summary> /// 执行sql语句,ExecuteDataSet 返回DataSet /// </summary> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> DataSet ExecuteDataSet(string commandText, CommandType commandType, params DbParameter[] parameterValues); /// <summary> /// 执行sql语句,ExecuteDataSet 返回DataSet /// </summary> /// <param name="connKey">数据库连接字符key</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> DataSet ExecuteDataSet(string connKey, string commandText, CommandType commandType, params DbParameter[] parameterValues); #endregion #region ExecuteNonQuery /// <summary> /// 执行sql语句,返回影响的行数 /// </summary> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> int ExecuteNonQuery(string commandText, CommandType commandType); /// <summary> /// 执行sql语句,返回影响的行数 /// </summary> /// <param name="connKey">数据库连接字符key</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> int ExecuteNonQuery(string connKey, string commandText, CommandType commandType); /// <summary> /// 执行sql语句,返回影响的行数 /// </summary> /// <param name="trans">事务对象</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> int ExecuteNonQuery(DbTransaction trans, string commandText, CommandType commandType); /// <summary> /// 执行sql语句,返回影响的行数 /// </summary> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> int ExecuteNonQuery(string commandText, CommandType commandType, params DbParameter[] parameterValues); /// <summary> /// 执行sql语句,返回影响的行数 /// </summary> /// <param name="connKey">数据库连接字符key</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> int ExecuteNonQuery(string connKey, string commandText, CommandType commandType, params DbParameter[] parameterValues); /// <summary> /// 执行sql语句,返回影响的行数 /// </summary> /// <param name="trans">事务对象</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> int ExecuteNonQuery(DbTransaction trans, string commandText, CommandType commandType, params DbParameter[] parameterValues); #endregion #region IDataReader /// <summary> /// 执行sql语句,ExecuteReader 返回IDataReader /// </summary> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> IDataReader ExecuteReader(string commandText, CommandType commandType); /// <summary> /// 执行sql语句,ExecuteReader 返回IDataReader /// </summary> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> IDataReader ExecuteReader(string commandText, CommandType commandType, params DbParameter[] parameterValues); /// <summary> /// 执行sql语句,ExecuteReader 返回IDataReader /// </summary> /// <param name="connKey">数据库连接字符key</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> IDataReader ExecuteReader(string connKey, string commandText, CommandType commandType); /// <summary> /// 执行sql语句,ExecuteReader 返回IDataReader /// </summary> /// <param name="connKey">数据库连接字符key</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> IDataReader ExecuteReader(string connKey, string commandText, CommandType commandType, params DbParameter[] parameterValues); #endregion #region ExecuteScalar /// <summary> /// 执行sql语句,ExecuteScalar 返回第一行第一列的值 /// </summary> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> object ExecuteScalar(string commandText, CommandType commandType); /// <summary> /// 执行sql语句,ExecuteScalar 返回第一行第一列的值 /// </summary> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> object ExecuteScalar(string commandText, CommandType commandType, params DbParameter[] parameterValues); /// <summary> /// 执行sql语句,ExecuteScalar 返回第一行第一列的值 /// </summary> /// <param name="trans">事务</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> object ExecuteScalar(DbTransaction trans, string commandText, CommandType commandType); /// <summary> /// 执行sql语句,ExecuteScalar 返回第一行第一列的值 /// </summary> /// <param name="connKey">数据库连接字符key</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> object ExecuteScalar(string connKey, string commandText, CommandType commandType); /// <summary> /// 执行sql语句,ExecuteScalar 返回第一行第一列的值 /// </summary> /// <param name="connKey">数据库连接字符key</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> object ExecuteScalar(string connKey, string commandText, CommandType commandType, params DbParameter[] parameterValues); /// <summary> /// 执行sql语句,ExecuteScalar 返回第一行第一列的值 /// </summary> /// <param name="trans">事务</param> /// <param name="commandText">sql语句</param> /// <param name="commandType"></param> /// <param name="parameterValues">参数</param> /// <returns></returns> object ExecuteScalar(DbTransaction trans, string commandText, CommandType commandType, params DbParameter[] parameterValues); #endregion }}
View Code
最终,大家也促成了针对性基础接口方法的切实可行落实,举一示范:
最后,大家也促成了针对性基础接口方法的求实落成,举一演示:
IDBSession.cs 对数据访问对象的概念
同时,为了保证子类中能够进入缓存处理,所以选取了虚方法(virtual)的情势,保障子类能够重写。
同时,为了确认保证子类中可见投入缓存处理,所以使用了虚方法(virtual)的方式,保险子类能够重写。
using System;using System.Data;namespace HY.DataAccess{ /// <summary> /// 数据库接口 /// </summary> public interface IDatabase { IDbConnection Connection { get; } DatabaseType DatabaseType { get; } string ConnKey { get; } } /// <summary> /// 数据库类对象 /// </summary> public class Database : IDatabase { public IDbConnection Connection { get; private set; } public DatabaseType DatabaseType { get; private set; } public string ConnKey { get; set; } public Database(IDbConnection connection) { Connection = connection; } public Database(DatabaseType dbType, string connKey) { DatabaseType = dbType; ConnKey = connKey; Connection = SqlConnectionFactory.CreateSqlConnection(dbType, connKey); } } /// <summary> /// 数据连接事务的Session接口 /// </summary> public interface IDBSession : IDisposable { string ConnKey { get; } DatabaseType DatabaseType { get; } IDbConnection Connection { get; } IDbTransaction Transaction { get; } IDbTransaction Begin(IsolationLevel isolation = IsolationLevel.ReadCommitted); void Commit(); void Rollback(); }}
View Code
3. 基于Connection的扩展
3. 基于Connection的扩展
SqlConnectionFactory.cs 那一个类是使用工厂格局开创DB连接的包装,代码如下:
这一个地点首要分为五个部分,a. 表明式的辨析,以及参数化的处理 b.
扩张Connection的Insert,Update…等Dapper没有增加的章程:
这几个地点重点分为多个部分,a. 说明式的解析,以及参数化的处理 b.
扩张Connection的Insert,Update…等Dapper没有扩充的办法:
a.
熟知Expression表明式的恋人应该相比驾驭,表达式自个儿是二个树形接口,依照分化的类型,能够不断的解析其子表达式,直到不具有持续分析的或然。所以这一个就非常粗略正是递归的不停迭代,依照其差别的NodeType能够组建差异的sql成分,因为代码较长,能够瞻仰github下的SqlExpressionVisitor.cs类,个中参数的赋值部分,没有使用反射,而是使用的反射发射,代码详见SqlParameterEmit.cs
a.
纯熟Expression表明式的爱人应该相比领悟,表明式自身是二个树形接口,依据差别的品类,能够不停的解析其子表明式,直到不具有继续分析的只怕。所以这么些就很简短正是递归的到处迭代,根据其分裂的NodeType能够组建区别的sql成分,因为代码较长,能够参见github下的SqlExpressionVisitor.cs类,当中参数的赋值部分,没有应用反射,而是利用的反光发射,代码详见SqlParameterEmit.cs
using System;using System.Collections.Generic;using System.Configuration;using System.Data;namespace HY.DataAccess{ public enum DatabaseType { SqlServer, MySql, Oracle, DB2 } public class SqlConnectionFactory { public static IDbConnection CreateSqlConnection(DatabaseType dbType, string strKey) { IDbConnection connection = null; string strConn = ConfigurationManager.ConnectionStrings[strKey].ConnectionString; switch { case DatabaseType.SqlServer: connection = new System.Data.SqlClient.SqlConnection; break; case DatabaseType.MySql: //connection = new MySql.Data.MySqlClient.MySqlConnection; //break; case DatabaseType.Oracle: //connection = new Oracle.DataAccess.Client.OracleConnection; connection = new System.Data.OracleClient.OracleConnection; break; case DatabaseType.DB2: connection = new System.Data.OleDb.OleDbConnection; break; } return connection; } }}
b.
有了表明式的壮大之后,就能够拿走相应的sql和参数,通过this增添Connection方法即可,美高梅开户网址,代码见ConnoctionExtention.cs
b.
有了表达式的恢宏之后,就足以博得相应的sql和参数,通过this扩大Connection方法即可,代码见ConnoctionExtention.cs
View Code
O翼虎M也不是万能的,比如做大数量的批量插入什么的,依旧供给SqlHelper,加上有个别人就喜爱DataTable大概DataSet。
四. 调用示例
四. 调用示例
由此SqlHelper作为基础,O大切诺基M作为增派,万无一失啊。
1. 大家定义3个不难UserInfoMo实体(包括mobile等性格)
1. 大家定义一个简单UserInfoMo实体(包蕴mobile等性子)
上边说说O景逸SUVM那块的兑现格局。见下截图
2. 概念接口 IUserInfoRep: IBaseRep
2. 概念接口 IUserInfoRep: IBaseRep
3. 概念完结类 UserInfoRep : BaseRep, IUserInfoRep
3. 概念达成类 UserInfoRep : BaseRep, IUserInfoRep
IDataServiceRepository.cs(提供业务层使用,里面包车型大巴法子不支持传递sql,包括sql的口舌最棒依旧放在数据层操作的好)
在不添加任何代码的功底上,我们就能够做到上边包车型客车调用:
在不添加此外代码的基础上,大家就可以做到上面包车型客车调用:
using System.Collections.Generic;using System.Data;using DapperExtensions;using HY.DataAccess;namespace HY.ORM{ public interface IDataServiceRepository { IDBSession DBSession { get; } T GetById<T>(dynamic primaryId) where T : class; IEnumerable<T> GetByIds<T>(IList<dynamic> ids) where T : class; IEnumerable<T> GetAll<T>() where T : class; int Count<T>(object predicate, bool buffered = false) where T : class; //lsit IEnumerable<T> GetList<T>(object predicate = null, IList<ISort> sort = null, bool buffered = false) where T : class; IEnumerable<T> GetPageList<T>(int pageIndex, int pageSize, out long allRowsCount, object predicate = null, IList<ISort> sort = null, bool buffered = true) where T : class; dynamic Insert<T>(T entity, IDbTransaction transaction = null) where T : class; bool InsertBatch<T>(IEnumerable<T> entityList, IDbTransaction transaction = null) where T : class; bool Update<T>(T entity, IDbTransaction transaction = null) where T : class; bool UpdateBatch<T>(IEnumerable<T> entityList, IDbTransaction transaction = null) where T : class; int Delete<T>(dynamic primaryId, IDbTransaction transaction = null) where T : class; int DeleteList<T>(object predicate, IDbTransaction transaction = null) where T : class; bool DeleteBatch<T>(IEnumerable<dynamic> ids, IDbTransaction transaction = null) where T : class; }}
View Code
日前项目还在搭建中,假诺有趣味的同桌也欢迎参与进去,代码详见Github下OSS.Core
此时此刻项目还在搭建中,假设有趣味的同窗也欢迎参预进去,代码详见Github下OSS.Core
IDataRepository.cs(提供数据层使用,继承了上边的IDataServiceRepository,帮忙传入sql)
=============================
=============================
using System;using System.Collections.Generic;using System.Data;using Dapper;using HY.DataAccess;namespace HY.ORM{ public interface IDataRepository : IDataServiceRepository { IDBSession DBSession { get; } IEnumerable<T> Get<T>(string sql, dynamic param = null, bool buffered = true) where T : class; IEnumerable<dynamic> Get(string sql, dynamic param = null, bool buffered = true); IEnumerable<TReturn> Get<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null); IEnumerable<TReturn> Get<TFirst, TSecond,TThird, TReturn>(string sql, Func<TFirst, TSecond,TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null); SqlMapper.GridReader GetMultiple(string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null); IEnumerable<T> GetPage<T>(int pageIndex, int pageSize, out long allRowsCount, string sql, dynamic param = null, string allRowsCountSql=null, dynamic allRowsCountParam = null, bool buffered = true) where T : class; Int32 Execute(string sql, dynamic param = null, IDbTransaction transaction = null); }}
设若您还有其余题材,欢迎关怀群众号(OSSCoder)
假定你还有其余题材,欢迎关怀群众号(OSSCoder)
View Code
RepositoryServiceBase.cs(IDataServiceRepository的落到实处类)
using System.Collections.Generic;using System.Data;using System.Linq;using Dapper;using DapperExtensions;using HY.DataAccess;namespace HY.ORM{ public class RepositoryServiceBase : IDataServiceRepository { public RepositoryServiceBase() { } public RepositoryServiceBase(IDBSession dbSession) { DBSession = dbSession; } public IDBSession DBSession { get; private set; } public void SetDBSession(IDBSession dbSession) { DBSession = dbSession; } /// <summary> /// 根据Id获取实体 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="primaryId"></param> /// <returns></returns> public T GetById<T>(dynamic primaryId) where T : class { return DBSession.Connection.Get<T>(primaryId as object, databaseType: DBSession.DatabaseType); } /// <summary> /// 根据多个Id获取多个实体 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="ids"></param> /// <returns></returns> public IEnumerable<T> GetByIds<T>(IList<dynamic> ids) where T : class { var tblName = string.Format("dbo.{0}", typeof; var idsin = string.Join(",", ids.ToArray<dynamic>; var sql = "SELECT * FROM @table WHERE Id in "; IEnumerable<T> dataList = DBSession.Connection.Query<T>(sql, new { table = tblName, ids = idsin }); return dataList; } /// <summary> /// 获取全部数据集合 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public IEnumerable<T> GetAll<T>() where T : class { return DBSession.Connection.GetList<T>(databaseType: DBSession.DatabaseType); } /// <summary> /// 统计记录总数 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="predicate"></param> /// <param name="buffered"></param> /// <returns></returns> public int Count<T>(object predicate, bool buffered = false) where T : class { return DBSession.Connection.Count<T>(predicate, databaseType: DBSession.DatabaseType); } /// <summary> /// 查询列表数据 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="predicate"></param> /// <param name="sort"></param> /// <param name="buffered"></param> /// <returns></returns> public IEnumerable<T> GetList<T>(object predicate = null, IList<ISort> sort = null, bool buffered = false) where T : class { return DBSession.Connection.GetList<T>(predicate, sort, null, null, buffered, databaseType: DBSession.DatabaseType); } /// <summary> /// 分页 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="pageIndex"></param> /// <param name="pageSize"></param> /// <param name="allRowsCount"></param> /// <param name="predicate"></param> /// <param name="sort"></param> /// <param name="buffered"></param> /// <returns></returns> public IEnumerable<T> GetPageList<T>(int pageIndex, int pageSize, out long allRowsCount, object predicate = null, IList<ISort> sort = null, bool buffered = true) where T : class { if (sort == null) { sort = new List<ISort>(); } IEnumerable<T> entityList = DBSession.Connection.GetPage<T>(predicate, sort, pageIndex, pageSize, null, null, buffered, databaseType: DBSession.DatabaseType); allRowsCount = DBSession.Connection.Count<T>(predicate, databaseType: DBSession.DatabaseType); return entityList; } /// <summary> /// 插入单条记录 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="transaction"></param> /// <returns></returns> public dynamic Insert<T>(T entity, IDbTransaction transaction = null) where T : class { dynamic result = DBSession.Connection.Insert<T>(entity, transaction, databaseType: DBSession.DatabaseType); return result; } /// <summary> /// 更新单条记录 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="transaction"></param> /// <returns></returns> public bool Update<T>(T entity, IDbTransaction transaction = null) where T : class { bool isOk = DBSession.Connection.Update<T>(entity, transaction, databaseType: DBSession.DatabaseType); return isOk; } /// <summary> /// 删除单条记录 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="primaryId"></param> /// <param name="transaction"></param> /// <returns></returns> public int Delete<T>(dynamic primaryId, IDbTransaction transaction = null) where T : class { var entity = GetById<T>(primaryId); var obj = entity as T; int isOk = DBSession.Connection.Delete<T>(obj, databaseType: DBSession.DatabaseType); return isOk; } /// <summary> /// 删除单条记录 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="predicate"></param> /// <param name="transaction"></param> /// <returns></returns> public int DeleteList<T>(object predicate = null, IDbTransaction transaction = null) where T : class { return DBSession.Connection.Delete<T>(predicate, transaction, databaseType: DBSession.DatabaseType); } /// <summary> /// 批量插入功能 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entityList"></param> /// <param name="transaction"></param> public bool InsertBatch<T>(IEnumerable<T> entityList, IDbTransaction transaction = null) where T : class { bool isOk = false; foreach (var item in entityList) { Insert<T>(item, transaction); } isOk = true; return isOk; } /// <summary> /// 批量更新() /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entityList"></param> /// <param name="transaction"></param> /// <returns></returns> public bool UpdateBatch<T>(IEnumerable<T> entityList, IDbTransaction transaction = null) where T : class { bool isOk = false; foreach (var item in entityList) { Update<T>(item, transaction); } isOk = true; return isOk; } /// <summary> /// 批量删除 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="ids"></param> /// <param name="transaction"></param> /// <returns></returns> public bool DeleteBatch<T>(IEnumerable<dynamic> ids, IDbTransaction transaction = null) where T : class { bool isOk = false; foreach (var id in ids) { Delete<T>(id, transaction); } isOk = true; return isOk; } }}
View Code
RepositoryBase.cs(IDataRepository的兑现类)
using System;using System.Collections.Generic;using System.Data;using Dapper;using DapperExtensions;using HY.DataAccess;namespace HY.ORM{ /// <summary> /// Repository基类 /// </summary> public class RepositoryBase : RepositoryServiceBase, IDataRepository { public RepositoryBase() { } public new void SetDBSession(IDBSession dbSession) { base.SetDBSession(dbSession); } public RepositoryBase(IDBSession dbSession) : base(dbSession) { } /// <summary> /// 根据条件筛选出数据集合 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="sql"></param> /// <param name="param"></param> /// <param name="buffered"></param> /// <returns></returns> public IEnumerable<T> Get<T>(string sql, dynamic param = null, bool buffered = true) where T : class { return DBSession.Connection.Query<T>(sql, param as object, DBSession.Transaction, buffered); } /// <summary> /// 根据条件筛选数据集合 /// </summary> /// <param name="sql"></param> /// <param name="param"></param> /// <param name="buffered"></param> /// <returns></returns> public IEnumerable<dynamic> Get(string sql, dynamic param = null, bool buffered = true) { return DBSession.Connection.Query(sql, param as object, DBSession.Transaction, buffered); } /// <summary> /// 分页查询 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="pageIndex"></param> /// <param name="pageSize"></param> /// <param name="allRowsCount"></param> /// <param name="sql"></param> /// <param name="param"></param> /// <param name="allRowsCountSql"></param> /// <param name="allRowsCountParam"></param> /// <param name="buffered"></param> /// <returns></returns> public IEnumerable<T> GetPage<T>(int pageIndex, int pageSize, out long allRowsCount, string sql, dynamic param = null, string allRowsCountSql = null, dynamic allRowsCountParam = null, bool buffered = true) where T : class { IEnumerable<T> entityList = DBSession.Connection.GetPage<T>(pageIndex, pageSize, out allRowsCount, sql, param as object, allRowsCountSql, null, null, buffered, databaseType: DBSession.DatabaseType); return entityList; } /// <summary> /// 根据表达式筛选 /// </summary> /// <typeparam name="TFirst"></typeparam> /// <typeparam name="TSecond"></typeparam> /// <typeparam name="TReturn"></typeparam> /// <param name="sql"></param> /// <param name="map"></param> /// <param name="param"></param> /// <param name="transaction"></param> /// <param name="buffered"></param> /// <param name="splitOn"></param> /// <param name="commandTimeout"></param> /// <returns></returns> public IEnumerable<TReturn> Get<TFirst, TSecond, TReturn>(string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) { return DBSession.Connection.Query(sql, map, param as object, transaction, buffered, splitOn); } /// <summary> /// 根据表达式筛选 /// </summary> /// <typeparam name="TFirst"></typeparam> /// <typeparam name="TSecond"></typeparam> /// <typeparam name="TReturn"></typeparam> /// <param name="sql"></param> /// <param name="map"></param> /// <param name="param"></param> /// <param name="transaction"></param> /// <param name="buffered"></param> /// <param name="splitOn"></param> /// <param name="commandTimeout"></param> /// <returns></returns> public IEnumerable<TReturn> Get<TFirst, TSecond, TThird, TReturn>(string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) { return DBSession.Connection.Query(sql, map, param as object, transaction, buffered, splitOn); } /// <summary> /// 获取多实体集合 /// </summary> /// <param name="sql"></param> /// <param name="param"></param> /// <param name="transaction"></param> /// <param name="commandTimeout"></param> /// <param name="commandType"></param> /// <returns></returns> public SqlMapper.GridReader GetMultiple(string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { return DBSession.Connection.QueryMultiple(sql, param as object, transaction, commandTimeout, commandType); } /// <summary> /// 执行sql操作 /// </summary> /// <param name="sql"></param> /// <param name="param"></param> /// <returns></returns> public int Execute(string sql, dynamic param = null, IDbTransaction transaction = null) { return DBSession.Connection.Execute(sql, param as object, transaction); } }}
View Code
说起DapperExtensions修改的小地点还蛮多的,下图是3个代码比较的截图。所以一会把代码打包贴上来吧。
上述代码就能够编译成 HY.ORM.DLL文件了。
下边就足以在 本人业务层继承HY.O奥迪Q7M中的RepositoryServiceBase类
,数据层继承HY.O奥迪Q7M中的 RepositoryBase类。
透过个其他构造函数也许,SetDBSession(Helper.CreateDBSession;
进行多少连接初步化。
接下去配置实体类和DB的照耀:
public class DemoEntity { public int ID { get; set; } public string Name { get; set; } } [Serializable] public class DomoEntityORMMapper : ClassMapper<DemoEntity> { public DomoEntityORMMapper() { base.Table("Demo"); //Map(f => f.UserID).Ignore();//设置忽略 //Map(f => f.Name).Key(KeyType.Identity);//设置主键 (如果主键名称不包含字母“ID”,请设置) AutoMap(); } }
那样就可以在类中 完毕 this.Get<德姆oEntity>(“select * from 德姆o
where ID=@ID”, new { ID = 1 }); 那样的语法了。
现实的行使方发
下图是本人要介绍达成的品种截图:
实际也是三层,只是名字不一样而已。
HY.Web
HY.Web.Iservice
HY.Web.Service(服务层,HY.Web.Iservice的贯彻类,
你也能够领会为工作逻辑层BLL)
HY.Web.DAO(数据访问层, 你也能够清楚为DAL)
HY.Web.Entity(实体层, 近日只定义了数额实体,
如若你的系列必要给app提供数据,
那么传输的数码要从简,就要求独自定义DTO了。 )
就那用户表来做个实例吧,表结构如下:(下图是用代码生成器截图效果,能够向来改动数据库的叙述新闻,开发利器。须求的仇人点那里【CodeBuilder-RazorEngine】)
HY.Web.Entity
在HY.Web.Entity的项目中新建Sys_UsersEntity.cs 定义实体类
View
Code
HY.Web.DAO
概念基类 BaseRepository.cs (能够设置默许的DBsession,方便扩充其余东东)
View
Code
定义数据访问层 Sys_UsersRepository.cs (代码里能够打包任何须求写sql
的代码)
View
Code
HY.Web.IService
概念接口 ISys_UsersService.cs ,提供给UI访问。
View
Code
HY.Web.Service
定义BaseService.cs,(能够设置暗许的DBsession,方便扩大别的东东)
View
Code
定义Sys_UsersService.cs, 去实现ISys_UsersService。
View
Code
HY.Web
壹 、定义相关的Controller
2、ISys_UsersService iSys_UsersService = new Sys_UsersService();
(那块其实可以动用 IoC, 相关内容且听后续分解)
③ 、调用接口
View
Code
**下载:**
HY.DataAccess
修改后的DapperExtensions:Dapperextensions.RAR
ps:已经更新版本了, 参加了对lambda的恢宏,点击那里进入
相关小说:
搭建一套本身实用的.net架构
搭建一套本人实用的.net架构【日志模块-log4net】
搭建一套自身实用的.net架构【O奥德赛M-Dapper+DapperExtensions】
搭建一套本人实用的.net架构续 【O翼虎M Dapper+DapperExtensions+Lambda】
搭建一套本身实用的.net框架结构【CodeBuilder-RazorEngine】
最初的小说链接: