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群里發問他都能及時回復。園友們也可以嘗試用用看。