Chloe.ORM 是国人开发的一款数据库访问组件,很是简单易用。目前支持四种主流数据库:SqlServer、MySQL、Oracle,以及Sqlite,作者为这四种数据库划分出了各自对应的组件程序集,以 MySQL 为例即 Chloe.MySql.dll,其他以此类推,可以同时引用这些程序集从而在一个项目中访问多种数据库,另外 Chloe 用的是 Emit 生成 IL 代码,这样避免了反射机制造成的性能损耗。

Chloe 的文档对基础操作列举得很全面,我就针对实践中的一些应用体会做些记录,也当是备忘后查。

一、基于工厂模式多数据库访问机制的构建

1、数据库访问连接串

<!-- 默认数据库类型(其值参考枚举 DatabaseType 的项)-->
<add key="DefaultDb" value="MySQL" /> <!-- MySQL 默认数据库连接字符串 -->
<add key="MySQLConnectionString" value="Data Source=192.168.100.20;port=3306;Initial Catalog=Order;user id=sa;password=123456sa;pooling=true;AllowZeroDatetime=true;ConvertZeroDatetime=true;Charset=utf8" /> <!-- Oracle 默认数据库连接字符串 -->
<add key="OracleConnectionString" value="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=228.10.135.8)(PORT=1521)))(CONNECT_DATA=(SID=orcl2)));User Id=sa;Password=123456sa;Pooling=true;MAX Pool Size=20;Min Pool Size=2;Connection Lifetime=20;Connect Timeout=20;" />

定义在 appSettings 节点下。

DefaultDb 表示在构建数据库连接对象时,采用默认方式使用的数据库类型。

MySQLConnectionString 和 OracleConnectionString 表示针对指定数据库的默认连接字符串。

2、数据库类型枚举

/// <summary>
/// 数据库类型
/// </summary>
public enum DatabaseType
{
MySQL = 1,
Oracle = 2
}

如果需要,可以继续追加 SqlServer 和 Sqlite。

3、数据库连接工厂接口

using System.Data;

namespace Chloe.Infrastructure
{
public interface IDbConnectionFactory
{
IDbConnection CreateConnection();
}
}

注:该接口在 Chloe 的底层已为我们定义好了。

4、面向具体数据库工厂类的实现

/// <summary>
/// 针对 MySQL 数据库的连接工厂类
/// </summary>
public class MySqlConnectionFactory : IDbConnectionFactory
{
string _connString = string.Empty; public MySqlConnectionFactory()
{
this._connString = "server=192.168.120.68; port=3306; User Id=sa; password=123456sa; database=OrderAutoCategory; charSet=utf8;";
} public MySqlConnectionFactory(string connString)
{
this._connString = connString;
} public IDbConnection CreateConnection()
{
MySqlConnection conn = new MySqlConnection(this._connString);
return conn;
}
}
/// <summary>
/// 针对 Oracle 数据库的连接工厂类
/// </summary>
public class OracleConnectionFactory : IDbConnectionFactory
{
string _connString = string.Empty; public OracleConnectionFactory()
{
this._connString = @"Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=228.10.135.8)(PORT=1521)))(CONNECT_DATA=(SID=orcl2)));User Id=sa;Password=123456sa;Pooling=true;MAX Pool Size=20;Min Pool Size=2;Connection Lifetime=20;Connect Timeout=20;";
} public OracleConnectionFactory(string connString)
{
this._connString = connString;
} public IDbConnection CreateConnection()
{
OracleConnection oracleConnection = new OracleConnection(this._connString);
OracleConnectionDecorator conn = new OracleConnectionDecorator(oracleConnection);
return conn;
}
}

出于修改 DbCommand 参数绑定方式的目的,作者定义了一个装饰类 OracleConnectionDecorator,在项目实践中我们直接从官网复制过来使用即可。

5、用以构建 IDbContext 实例的自定义工厂类

using System;
using System.Configuration; using Chloe;
using Chloe.Infrastructure.Interception;
using Chloe.MySql;
using Chloe.Oracle; namespace Pro.Factory
{
public class DbContextFactory
{
public static IDbContext CreateDbContext()
{
// 数据库类型
DatabaseType dbType = GetDatabaseType(ConfigurationManager.AppSettings["DefaultDb"]); // 连接字符串
string connectionString = GetConnectionString(dbType);
return CreateDbContext(dbType, connectionString);
} public static IDbContext CreateDbContext(DatabaseType dbType)
{
string connectionString = GetConnectionString(dbType);
return CreateDbContext(dbType, connectionString);
} public static IDbContext CreateDbContext(string connectionString)
{
DatabaseType dbType = GetDatabaseType(ConfigurationManager.AppSettings["DefaultDb"]);
return CreateDbContext(dbType, connectionString);
} public static IDbContext CreateDbContext(DatabaseType dbType, string connectionString)
{
IDbContext context = null;
switch (dbType)
{
case DatabaseType.MySQL:
context = new MySqlContext(new MySqlConnectionFactory(connectionString));
break;
case DatabaseType.Oracle:
context = new OracleContext(new OracleConnectionFactory(connectionString));
break;
default:
throw new Exception("在工厂 DbContextFactory 中试图创建 IDbContext 时,发现数据库类型不明确(考虑遗漏了类型)");
} IDbCommandInterceptor interceptor = new DbCommandInterceptor();
// 全局拦截器
//DbInterception.Add(interceptor); // 单个DbContext拦截器
if (context != null)
{
context.Session.AddInterceptor(interceptor);
} return context;
} /* 公共函数 */
public static string GetConnectionString(DatabaseType dbType)
{
string connectionString = "";
switch (dbType)
{
case DatabaseType.MySQL:
connectionString = ConfigurationManager.AppSettings["MySQLConnectionString"];
break;
case DatabaseType.Oracle:
connectionString = ConfigurationManager.AppSettings["OracleConnectionString"];
break;
default:
throw new Exception("在工厂 DbContextFactory 中试图创建 IDbContext 时,发现数据库类型不明确(考虑遗漏了类型)");
} if (string.IsNullOrEmpty(connectionString))
{
throw new Exception(string.Format(@"基于 {0} 数据库的连接字符串为空,需进行配置", dbType.ToString()));
}
return connectionString;
} public static DatabaseType GetDatabaseType(string dbTypeName)
{
if (string.IsNullOrEmpty(dbTypeName))
{
throw new Exception("需配置默认数据库类型 DefaultDb ");
} DatabaseType dbType = (DatabaseType)Enum.Parse(typeof(DatabaseType), dbTypeName);
return dbType;
}
}
}

上述代码的核心方法为 CreateDbContext,共提供了 4 个重载。

第一个无参数重载方法表示一切按默认的配置项进行初始化,从其代码可以看到,“数据库类型”是由配置节点的 DefaultDb 决定,然后调用了 GetConnectionString 方法来确立“连接字符串”,它会针对不同种类数据库安排一个默认的连接。

第二个重载方法要求提供“数据库类型”,然后直接调用 GetConnectionString 来确立“连接字符串”即可。

第三个重载方法要求提供“连接字符串”,那么“数据库类型”是由配置节点的 DefaultDb 决定。

第四个重载方法要求提供“数据库类型”和“连接字符串”,在调用时要确保这两个参数的值是统一的,即如果“数据库类型”是 MySQL 的话,那么“连接字符串”也必须是基于 MySQL 数据库。

另外,在第四个重载方法中还实现了拦截器功能,目的在于截取 SQL 语句,以备后查。

二、实体类解析

[Table("OrderDistributeRouteConfigCode")]
public class RouteConfigCode
{
[NonAutoIncrement]
[Column(IsPrimaryKey = true, Name = "Guid")]
public string Guid { get; set; } [NotMapped]
public string DistributeSiteName { get; set; }
}

列举一下四个最常用的特性:

  • Table 为表名映射,对应数据库表名
  • Column 为列名映射,对应数据库列名
  • NonAutoIncrement 表示该列为“非自增长”,意味着开发者要自行赋值
  • NotMapped 表示该列不与任何表字段进行映射,比如在统计时,当某属性是通过二次计算得来时则可以标识该特性。

三、增删改查

1、新增

public BaseResult Add(RouteConfigCodeEdit edit)
{
BaseResult result = BaseResult.Fail();
DateTime currentDatetime = DateTime.Now; using (IDbContext dbContext = DbContextFactory.CreateDbContext())
{
try
{
dbContext.Session.BeginTransaction(); RouteConfigCode entity = new RouteConfigCode(); entity.OrderDistributeRouteConfigGuid = edit.OrderDistributeRouteConfigGuid; entity.SiteCode = edit.SiteCode;
entity.SiteName = edit.SiteName;
entity.OrderType = edit.OrderType; entity.IsMQ = edit.IsMQ;
entity.Remarks = edit.Remarks;
entity.IsEnable = edit.IsEnable;
entity.Guid = Guid.NewGuid().ToString();
entity.CreateTime = currentDatetime;
entity.LastUpdateTime = currentDatetime; dbContext.Insert(entity); dbContext.Session.CommitTransaction();
result.Status = true;
result.StatusMessage = "新增成功";
}
catch (Exception ex)
{
dbContext.Session.RollbackTransaction();
NLogHelper.Error(ex); result.StatusMessage = ex.Message;
}
}
return result;
}

整个业务逻辑操作都囊括在 using 块中,这样确保由 DbContextFactory 工厂构建的 IDbContext 连接对象可以及时的被关闭和销毁。

紧接着,拟定 try/catch 来分管期望与意外这两种情形,如果所有业务操作都在期望之中则正常提交事务(Commit),并返回相关状态为 true;如果操作期间发生了不可预测的意外情形,则通过 catch 块来捕获异常,首当其冲是回滚事务(Rollback),然后记录文本日志(txt),并返回异常内容给调用方。

使用基于 Insert 方法可以做到参数化,要注意的是它会把实体中所有的属性组织到 SQL 语句中。

2、修改

public BaseResult Update(RouteConfigCodeEdit edit)
{
BaseResult result = BaseResult.Fail();
DateTime currentDatetime = DateTime.Now; using (IDbContext dbContext = DbContextFactory.CreateDbContext())
{
try
{
dbContext.Session.BeginTransaction(); RouteConfigCode entity = dbContext.Query<RouteConfigCode>().Where(p => p.Guid == edit.Guid).FirstOrDefault();
if (entity != null)
{
dbContext.TrackEntity(entity); entity.Guid = edit.Guid; entity.OrderDistributeRouteConfigGuid = edit.OrderDistributeRouteConfigGuid;
entity.SiteCode = edit.SiteCode;
entity.SiteName = edit.SiteName;
entity.OrderType = edit.OrderType; entity.IsMQ = edit.IsMQ;
entity.Remarks = edit.Remarks;
entity.IsEnable = edit.IsEnable; entity.LastUpdateTime = currentDatetime; int effectedRows = dbContext.Update(entity); result.Status = true;
result.StatusMessage = "修改成功";
}
else
{
result.Status = false;
result.StatusMessage = "修改失败,记录不存在";
} dbContext.Session.CommitTransaction();
}
catch (Exception ex)
{
dbContext.Session.RollbackTransaction();
NLogHelper.Error(ex); result.StatusMessage = ex.Message;
}
}
return result;
}

修改操作的重点在于属性跟踪,为避免不必要的属性更新,我们应尽量只更新那些发生了变化的属性,或者说被修改过的属性,所以为属性赋值之前就需要调用一次 TrackEntity 方法,最后才是调用 Update 方法,该方法支持参数化处理。

3、删除

public BaseResult Delete(string ids)
{
DateTime currentDatetime = DateTime.Now;
BaseResult result = BaseResult.Error("操作失败,"); using (IDbContext dbContext = DbContextFactory.CreateDbContext())
{
try
{
dbContext.Session.BeginTransaction(); // 批量操作时累计受影响行数
int total = 0;
string[] idArray = ids.Split(",");
foreach (string id in idArray)
{
RouteConfigCode entity = new RouteConfigCode();
entity.Guid = id;
int effectedRows = dbContext.Delete(entity);
if (effectedRows > 0)
{
total += effectedRows;
}
} dbContext.Session.CommitTransaction();
result.Status = true;
result.StatusMessage = string.Format("操作成功,总记录:{0},执行成功:{1}", idArray.Length, total);
}
catch (Exception ex)
{
dbContext.Session.RollbackTransaction();
NLogHelper.Error(ex); result.StatusMessage += ex.Message;
}
}
return result;
}

实例化一个对象,并对主键列赋值,然后传递给 Delete 方法即可,该方法支持参数化处理。

4、分页查询

分页 Pager:

public class Pager
{
public int totalRows { set; get; } public int pageSize { set; get; } public int pageNo { set; get; } public int totalPages { set; get; } public string direction { set; get; } public string sort { set; get; } public object rows { set; get; } public Pager()
{
totalRows = 0;
pageSize = 20;
pageNo = 1;
totalPages = 0;
}
}

业务查询实体:

public class RouteConfigCodeSearch
{
public Pager Pager { get; set; } public string SiteCode { get; set; }
public string SiteName { get; set; }
}

业务查询实体除了包含 Pager 之外还包含了查询栏里的各项条件,比如按编号(SiteCode)、按名称(SiteName)。

分页查询:

public List<RouteConfigCode> GetListByPage(RouteConfigCodeSearch search)
{
List<RouteConfigCode> routeConfigCodeList = new List<RouteConfigCode>();
using (IDbContext dbContext = DbContextFactory.CreateDbContext())
{
var query = dbContext.Query<RouteConfigCode>()
.LeftJoin<RouteConfig>((code, routeConfig) => code.OrderDistributeRouteConfigGuid == routeConfig.Guid)
.Select((code, routeConfig) => new RouteConfigCode
{
DistributeSiteName = routeConfig.DistributeSiteName,
Guid = code.Guid,
OrderDistributeRouteConfigGuid = code.OrderDistributeRouteConfigGuid,
SiteCode = code.SiteCode,
SiteName = code.SiteName,
OrderType = code.OrderType,
Remarks = code.Remarks,
CreateTime = code.CreateTime,
LastUpdateTime = code.LastUpdateTime,
IsEnable = code.IsEnable,
IsMQ = code.IsMQ
}); #region 查询条件
if (!string.IsNullOrEmpty(search.SiteCode))
{
query = query.Where(p => p.SiteCode.Contains(search.SiteCode));
} if (!string.IsNullOrEmpty(search.SiteName))
{
query = query.Where(p => p.SiteName.Contains(search.SiteName));
}
#endregion routeConfigCodeList = query.OrderBy(p => p.CreateTime).TakePage(search.Pager.pageNo, search.Pager.pageSize).ToList();
search.Pager.totalRows = query.Count();
}
return routeConfigCodeList;
}

通过 TakePage 方法就可以很方便的实现分页功能了,同时把总记录数赋给 totalRows 属性以告知调用者。

5、单条查询

public BaseResult GetItemById(string id)
{
JsonResult<RouteConfigCode> result = new JsonResult<RouteConfigCode>(); using (IDbContext dbContext = DbContextFactory.CreateDbContext())
{
try
{
RouteConfigCode entity = dbContext.Query<RouteConfigCode>().Where(p => p.Guid == id).FirstOrDefault();
if (entity == null)
{
result.Status = false;
result.StatusMessage = "查询记录失败";
}
else
{
result.Data = entity;
}
}
catch (Exception ex)
{
NLogHelper.Error(ex); result.Status = false;
result.StatusMessage = ex.Message;
}
}
return result;
}

通过 FirstOrDefault 可以确保只查询一条记录,如果找不到则返回 null。

在使用 Chloe.ORM 的过程中总体感觉非常顺畅,满足了简单、易用的图快心理,重点是作者很热心,在QQ群里发问他都能及时回复。园友们也可以尝试用用看。

Chloe.ORM框架应用实践的更多相关文章

  1. Node.js ORM 框架 sequelize 实践

    最近在做团队的一个内部系统,这次使用的nodejs web框架是团队统一的hapi.js,而数据库依然是mysql,ORM 框架选用有着6000+ stars 的 sequelize.js,hapi- ...

  2. [开源].NET数据库访问框架Chloe.ORM

    扯淡 13年毕业之际,进入第一家公司实习,接触了 EntityFramework,当时就觉得这东西太牛了,访问数据库都可以做得这么轻松.优雅!毕竟那时还年轻,没见过世面.工作之前为了拿个实习机会混个工 ...

  3. 类EF框架Chloe.ORM升级:只为更完美

    扯淡 Chloe.ORM:一款轻量.高效的.NET C#数据库访问框架(ORM).查询接口借鉴 Linq(但不支持 Linq).借助 lambda 表达式,可以完全用面向对象的方式就能轻松执行多表连接 ...

  4. .NET ORM框架 SqlSuagr4.0 功能详解与实践【开源】

    SqlSugar 4.0 ORM框架的优势 为了未来能够更好的支持多库分布式的存储,并行计算等功能,将SqlSugar3.x全部重写,现有的架构可以轻松扩展多库. 源码下载: https://gith ...

  5. [开源]无sql之旅-Chloe.ORM之增删查改

    扯淡 这是一款轻量.高效的.NET C#数据库访问框架(ORM).查询接口借鉴 Linq(但不支持 Linq).借助 lambda 表达式,可以完全用面向对象的方式就能轻松执行多表连接查询.分组查询. ...

  6. 高品质开源工具Chloe.ORM:支持存储过程与Oracle

    扯淡 这是一款高质量的.NET C#数据库访问框架(ORM).查询接口借鉴 Linq.借助 lambda 表达式,可以完全用面向对象的方式就能轻松执行多表连接查询.分组查询.聚合查询.插入数据.批量删 ...

  7. 吉特仓库管理系统-ORM框架的使用

    最近在园子里面连续看到几篇关于ORM的文章,其中有两个印象比较深刻<<SqliteSugar>>,另外一篇文章是<<我的开发框架之ORM框架>>, 第一 ...

  8. 如何做好一个ORM框架

    很多人都不太认可以第三方ORM,因为考虑的点不够全面,没有用户群体大的ORM有保证,这点是不可否认确是事实. 但是往往用户群体大的ORM又有不足之处,就拿用户群体最多的两个ORM来说一下吧 1.EF ...

  9. 利用抽象、多态实现无反射的绿色环保ORM框架

    最近一直在忙新公司的基础库建设,对系统架构.开发框架及快速开发平台的设计实施都积累了一定的实践经验. 一般的中小型的软件开发公司,如果按照技术储备来衡量软件项目的技术含量的评定依据是可行的.但如果光是 ...

随机推荐

  1. 201521123082 《Java程序设计》第14周学习总结

    201521123082 <Java程序设计>第14周学习总结 标签(空格分隔):java 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. Answ ...

  2. 201521123020 《Java程序设计》第4周学习总结

    本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. (1)类注释的使用方法是: /** *(要注释的内容) */ (2)学习了Object类,学会了覆 ...

  3. 201521123019《Java程序设计》第1周学习总结

    一.本周章学习总结 1.了解了JDK和JRE的区别 2.学会用ALT+/快速写代码 3.成功安装JDK和Eclipse 4.初步了解JAVA的发展史 二.书面作业 1.为什么java程序可以跨平台运行 ...

  4. 201521123096《Java程序设计》第十三周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  5. lintcode.44 最小子数组

    最小子数组   描述 笔记 数据 评测 给定一个整数数组,找到一个具有最小和的子数组.返回其最小和. 注意事项 子数组最少包含一个数字 您在真实的面试中是否遇到过这个题? Yes 哪家公司问你的这个题 ...

  6. Log4j.properties属性文件

    log4j.properties文件属性介绍log4j.rootLogger = [ level ] , appenderName1, appenderName2, …#level : 设定日志记录的 ...

  7. 离线安装 Cloudera Manager 5 和 CDH5.10

    关于CDH和Cloudera Manager CDH (Cloudera's Distribution, including Apache Hadoop),是Hadoop众多分支中的一种,由Cloud ...

  8. YYHS-鏖战字符串

    题目描述 Abwad在nbc即将完成她的程序的时候,急中生智拔掉了她电脑的电源线,争取到了宝贵的时间.作为著名论文<论Ctrl-C与Ctrl-V在信息学竞赛中的应用>的作者,他巧妙地使用了 ...

  9. 渗透相关website

    开源安全测试方法论:http://www.isecom.org/research/osstmm.html 信息系统安全评估框架:www.oissg.org/issaf 开放式web应用程序安全项目(O ...

  10. 由throw new Error() 引发的探讨

    问题复现 在工作时遇到了需要抛出异常并且需要自己捕获处理的地方,于是在抛出的地方写下 function parseExcel(con) { try { // doSomething } catch ( ...