Chloe.ORM框架應用實踐

Markdown

Chloe.ORM 是國人開發的一款數據庫訪問組件,非常簡單易用。目前支持四種主流數據庫:SqlServer、MySQL、Oracle,以及Sqlite,做者爲這四種數據庫劃分出了各自對應的組件程序集,以 MySQL 爲例即 Chloe.MySql.dll,其餘以此類推,能夠同時引用這些程序集從而在一個項目中訪問多種數據庫,另外 Chloe 用的是 Emit 生成 IL 代碼,這樣避免了反射機制形成的性能損耗。數據庫

Chloe 的文檔對基礎操做列舉得很全面,我就針對實踐中的一些應用體會作些記錄,也當是備忘後查。oracle

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 節點下。app

DefaultDb 表示在構建數據庫鏈接對象時,採用默認方式使用的數據庫類型。
MySQLConnectionString 和 OracleConnectionString 表示針對指定數據庫的默認鏈接字符串。函數

二、數據庫類型枚舉

/// <summary>
/// 數據庫類型
/// </summary>
public enum DatabaseType
{
    MySQL = 1,
    Oracle = 2
}

若是須要,能夠繼續追加 SqlServer 和 Sqlite。性能

三、數據庫鏈接工廠接口

using System.Data;

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

注:該接口在 Chloe 的底層已爲咱們定義好了。ui

四、面向具體數據庫工廠類的實現

/// <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,在項目實踐中咱們直接從官網複製過來使用便可。this

五、用以構建 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 個重載。spa

第一個無參數重載方法表示一切按默認的配置項進行初始化,從其代碼能夠看到,「數據庫類型」是由配置節點的 DefaultDb 決定,而後調用了 GetConnectionString 方法來確立「鏈接字符串」,它會針對不一樣種類數據庫安排一個默認的鏈接。日誌

第二個重載方法要求提供「數據庫類型」,而後直接調用 GetConnectionString 來確立「鏈接字符串」便可。code

第三個重載方法要求提供「鏈接字符串」,那麼「數據庫類型」是由配置節點的 DefaultDb 決定。

第四個重載方法要求提供「數據庫類型」和「鏈接字符串」,在調用時要確保這兩個參數的值是統一的,即若是「數據庫類型」是 MySQL 的話,那麼「鏈接字符串」也必須是基於 MySQL 數據庫。

另外,在第四個重載方法中還實現了攔截器功能,目的在於截取 SQL 語句,以備後查。

2、實體類解析

[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 表示該列不與任何表字段進行映射,好比在統計時,當某屬性是經過二次計算得來時則能夠標識該特性。

3、增刪改查

一、新增

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 語句中。

二、修改

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 方法,該方法支持參數化處理。

三、刪除

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 方法便可,該方法支持參數化處理。

四、分頁查詢

分頁 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 屬性以告知調用者。

五、單條查詢

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羣裏發問他都能及時回覆。園友們也能夠嘗試用用看。

相關文章
相關標籤/搜索