Chloe.ORM框架应用实践

2017 年 10 月 18 日 DotNet

(点击上方蓝字,可快速关注我们)


来源:溪边静禅

cnblogs.com/ramantic/p/7677891.html


Chloe.ORM (http://www.52chloe.com/)是国人开发的一款数据库访问组件,很是简单易用。目前支持四种主流数据库: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群里发问他都能及时回复。园友们也可以尝试用用看。


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

登录查看更多
0

相关内容

干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
160+阅读 · 2020年5月14日
TensorFlow Lite指南实战《TensorFlow Lite A primer》,附48页PPT
专知会员服务
68+阅读 · 2020年1月17日
【阿里技术干货】知识结构化在阿里小蜜中的应用
专知会员服务
96+阅读 · 2019年12月14日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
2019年机器学习框架回顾
专知会员服务
35+阅读 · 2019年10月11日
机器学习入门的经验与建议
专知会员服务
90+阅读 · 2019年10月10日
浅谈 Kubernetes 在生产环境中的架构
DevOps时代
11+阅读 · 2019年5月8日
tensorflow LSTM + CTC实现端到端OCR
机器学习研究会
26+阅读 · 2017年11月16日
领域应用 | 图数据库及其在恒昌的应用简介
开放知识图谱
6+阅读 · 2017年10月10日
多任务深度学习框架在 ADAS 中的应用 | 分享总结
【机器学习】推荐13个机器学习框架
产业智能官
8+阅读 · 2017年9月10日
基于LDA的主题模型实践(一)
机器学习深度学习实战原创交流
20+阅读 · 2015年9月9日
A Modern Introduction to Online Learning
Arxiv
19+阅读 · 2019年12月31日
A Sketch-Based System for Semantic Parsing
Arxiv
4+阅读 · 2019年9月12日
Arxiv
8+阅读 · 2018年5月15日
Arxiv
8+阅读 · 2018年5月1日
Arxiv
10+阅读 · 2018年3月23日
VIP会员
相关VIP内容
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
160+阅读 · 2020年5月14日
TensorFlow Lite指南实战《TensorFlow Lite A primer》,附48页PPT
专知会员服务
68+阅读 · 2020年1月17日
【阿里技术干货】知识结构化在阿里小蜜中的应用
专知会员服务
96+阅读 · 2019年12月14日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
2019年机器学习框架回顾
专知会员服务
35+阅读 · 2019年10月11日
机器学习入门的经验与建议
专知会员服务
90+阅读 · 2019年10月10日
相关资讯
浅谈 Kubernetes 在生产环境中的架构
DevOps时代
11+阅读 · 2019年5月8日
tensorflow LSTM + CTC实现端到端OCR
机器学习研究会
26+阅读 · 2017年11月16日
领域应用 | 图数据库及其在恒昌的应用简介
开放知识图谱
6+阅读 · 2017年10月10日
多任务深度学习框架在 ADAS 中的应用 | 分享总结
【机器学习】推荐13个机器学习框架
产业智能官
8+阅读 · 2017年9月10日
基于LDA的主题模型实践(一)
机器学习深度学习实战原创交流
20+阅读 · 2015年9月9日
Top
微信扫码咨询专知VIP会员