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

  • 相关阅读:
    HDU 2196 Computer
    HDU 1520 Anniversary party
    POJ 1217 FOUR QUARTERS
    POJ 2184 Cow Exhibition
    HDU 2639 Bone Collector II
    POJ 3181 Dollar Dayz
    POJ 1787 Charlie's Change
    POJ 2063 Investment
    HDU 1114 Piggy-Bank
    Lca hdu 2874 Connections between cities
  • 原文地址:https://www.cnblogs.com/ramantic/p/7677891.html
Copyright © 2011-2022 走看看