zoukankan      html  css  js  c++  java
  • Chloe.ORM框架应用实践

    Markdown

    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群里发问他都能及时回复。园友们也可以尝试用用看。

  • 相关阅读:
    vue 跨域访问http
    vue 生命周期小结
    koa的教程
    spoj104 HIGH
    loj2026 「JLOI / SHOI2016」成绩比较
    loj2024「JLOI / SHOI2016」侦查守卫
    loj2016 「SCOI2016」美味
    loj2014 「SCOI2016」萌萌哒
    loj2013 「SCOI2016」幸运数字
    loj2012 「SCOI2016」背单词
  • 原文地址:https://www.cnblogs.com/ramantic/p/7677891.html
Copyright © 2011-2022 走看看