話說C#程式設計師人手一個ORM,確實沒有必要再寫ORM了,不過我的ORM並不是新的,是從DBHelper演化過來的,算是DBHelper魔改版。
目前流行的ORM有EF、Dapper、SqlSugar、FreeSql、Chloe等,有經驗的程式設計師總是在這些ORM基礎上或者在DBHelper基礎上再封裝一套,再加上自己寫ORM的,可謂人手一套ORM。可能是因為在框架當中,編寫ORM,入門相對簡單吧,但是做好很難。
本專案來源:專案來源於需求,2014年我在上家公司做ERP、CRM之類的BS管理系統專案,當時公司用的就是OracleHelper、SqlServerHelper之類的;2015年開始嘗試修改,當時支援了事務,但仍是靜態的幫助類;2016年我把OracleHelper、SqlServerHelper、MySqlHelper、SQLiteHelper合成了一個DBHelper,仍是靜態類。2017跳槽到現公司,公司大專案我的DBHelper就用不上了,平時小專案用用;2020年,終於發現靜態DBHelper多執行緒併發,事務這塊有BUG,所以改造成了非靜態的。後來又加入SqlString類,重構以方便擴充套件支援更多資料庫種類;最近簡單支援了一下Lambda表示式;幾經修改,原來DBHelper的那些方法幾乎沒怎麼變;長期不變的穩定的API才是好API。
主要是自己用的,如果別人要用的話,建議從gitee或github上拉原始碼下來,原始碼在手好控制,原始碼相對簡單,有經驗的程式設計師不難看懂,可以自己除錯修改擴充套件,Lambda表示式是新支援的,如果Lambda hold不住,就使用原生SQL。
跟主流ORM相比還是比較欠缺的,它只是一個DBHelper。
原始碼地址(VS2015、.NET Framework 4.5.2):
https://gitee.com/s0611163/DBHelper
https://github.com/0611163/DBHelper
.NET Core 5 版本原始碼地址(VS2019、.NET 5):
https://gitee.com/s0611163/DBHelperCore
效能測試
DBHelper SqlSugar EntityFramework 增刪改查 效能對比測試
https://www.cnblogs.com/s0611163/p/15814051.html
為什麼每個主流ORM都說效能比EF好,都說EF效能不好?而我的測試結果顯示EF效能挺好的,為了防止有快取,每項測試前,重啟程式再測,效能還是一樣的。批量新增和修改,別的ORM是快很多,但它使用了別的專門的方法。
該ORM介紹及使用示例如下:
DBHelper
簡介
一款輕量級ORM,查詢使用原生SQL,查詢結果對映到實體類,增刪改支援實體類,支援Oracle、MSSQL、MySQL、SQLite等多種資料庫,有配套Model生成器,方便自己擴充套件以支援更多資料庫
特點
- 支援Oracle、MSSQL、MySQL、SQLite四種資料庫
- 方便擴充套件以支援更多關聯式資料庫
- 有配套的Model生成器
- insert、update、delete操作無需寫SQL
- 查詢使用原生SQL
- 查詢結果通過對映轉成實體類或實體類集合
- 支援引數化查詢,通過SqlString類提供非常方便的引數化查詢
- 支援連線多個資料來源
- 單表查詢、單表分頁查詢、簡單的聯表分頁查詢支援Lambda表示式
- 支援原生SQL和Lambda表示式混寫
優點
- 程式碼實現比較簡單,有經驗的程式設計師容易掌控程式碼,自己修改和擴充套件
- 查詢使用原生SQL
缺點
- 聯表查詢對Lambda表示式的支援比較弱
- 複雜查詢不支援Lambda表示式
建議
- 單表查詢可以使用Lambda表示式
- 聯表查詢以及複雜查詢建議使用原生SQL或原生SQL和Lambda表示式混寫
作者郵箱
651029594@qq.com
示例
定義資料庫物件
public class DBHelper
{
#region 變數
private static ISessionHelper _sessionHelper = new SessionHelper(ConfigurationManager.ConnectionStrings["DefaultConnection"].ToString(), DBType.MySQL);
#endregion
#region 獲取 ISession
/// <summary>
/// 獲取 ISession
/// </summary>
public static ISession GetSession()
{
return _sessionHelper.GetSession();
}
#endregion
#region 獲取 ISession (非同步)
/// <summary>
/// 獲取 ISession (非同步)
/// </summary>
public static async Task<ISession> GetSessionAsync()
{
return await _sessionHelper.GetSessionAsync();
}
#endregion
}
使用Model生成器生成實體類
- 實體類放在Models資料夾中
- 擴充套件實體類放在ExtModels資料夾中
- 實體類和擴充套件實體類使用partial修飾,實際上是一個類,放在不同的檔案中
- 如果需要新增自定義屬性,請修改ExtModels,不要修改Models
實體類示例
/// <summary>
/// 訂單表
/// </summary>
[Serializable]
[DBTable("bs_order")]
public partial class BsOrder
{
/// <summary>
/// 主鍵
/// </summary>
[DBKey]
[DBField]
public string Id { get; set; }
/// <summary>
/// 訂單時間
/// </summary>
[DBField("order_time")]
public DateTime OrderTime { get; set; }
/// <summary>
/// 訂單金額
/// </summary>
[DBField]
public decimal? Amount { get; set; }
/// <summary>
/// 下單使用者
/// </summary>
[DBField("order_userid")]
public long OrderUserid { get; set; }
/// <summary>
/// 訂單狀態(0草稿 1已下單 2已付款 3已發貨 4完成)
/// </summary>
[DBField]
public int Status { get; set; }
/// <summary>
/// 備註
/// </summary>
[DBField]
public string Remark { get; set; }
/// <summary>
/// 建立者ID
/// </summary>
[DBField("create_userid")]
public string CreateUserid { get; set; }
/// <summary>
/// 建立時間
/// </summary>
[DBField("create_time")]
public DateTime CreateTime { get; set; }
/// <summary>
/// 更新者ID
/// </summary>
[DBField("update_userid")]
public string UpdateUserid { get; set; }
/// <summary>
/// 更新時間
/// </summary>
[DBField("update_time")]
public DateTime? UpdateTime { get; set; }
}
修改擴充套件實體類
- 修改擴充套件實體類,新增自定義屬性
- 下面的擴充套件實體類中,查詢時OrderUserRealName會被自動填充,查詢SQL:select t.*, u.real_name as OrderUserRealName from ......
- DetailList不會被自動填充,需要手動查詢
擴充套件實體類示例
/// <summary>
/// 訂單表
/// </summary>
public partial class BsOrder
{
/// <summary>
/// 訂單明細集合
/// </summary>
public List<BsOrderDetail> DetailList { get; set; }
/// <summary>
/// 下單使用者姓名
/// </summary>
public string OrderUserRealName { get; set; }
/// <summary>
/// 下單使用者名稱
/// </summary>
public string OrderUserName { get; set; }
}
新增
public void Insert(SysUser info)
{
using (var session = DBHelper.GetSession())
{
session.Insert(info);
}
}
批量新增
public void Insert(List<SysUser> list)
{
using (var session = DBHelper.GetSession())
{
session.Insert(list);
}
}
修改
public void Update(SysUser info)
{
using (var session = DBHelper.GetSession())
{
session.Update(info);
}
}
批量修改
public void Update(List<SysUser> list)
{
using (var session = DBHelper.GetSession())
{
session.Update(list);
}
}
刪除
public void Delete(string id)
{
using (var session = DBHelper.GetSession())
{
session.DeleteById<SysUser>(id);
}
}
條件刪除
using (var session = DBHelper.GetSession())
{
session.DeleteByCondition<SysUser>(string.Format("id>=12"));
}
查詢單個記錄
public SysUser Get(string id)
{
using (var session = DBHelper.GetSession())
{
return session.FindById<SysUser>(id);
}
}
using (var session = DBHelper.GetSession())
{
return session.FindBySql<SysUser>("select * from sys_user");
}
簡單查詢
using (var session = DBHelper.GetSession())
{
string sql = "select * from CARINFO_MERGE";
List<CarinfoMerge> result = session.FindListBySql<CarinfoMerge>(sql);
}
條件查詢
public List<BsOrder> GetList(int? status, string remark, DateTime? startTime, DateTime? endTime)
{
using (var session = DBHelper.GetSession())
{
SqlString sql = session.CreateSqlString(@"
select t.*, u.real_name as OrderUserRealName
from bs_order t
left join sys_user u on t.order_userid=u.id
where 1=1");
sql.AppendIf(status.HasValue, " and t.status=@status", status);
sql.AppendIf(!string.IsNullOrWhiteSpace(remark), " and t.remark like concat('%',@remark,'%')", remark);
sql.AppendIf(startTime.HasValue, " and t.order_time>=STR_TO_DATE(@startTime, '%Y-%m-%d %H:%i:%s') ", startTime.Value.ToString("yyyy-MM-dd HH:mm:ss"));
sql.AppendIf(endTime.HasValue, " and t.order_time<=STR_TO_DATE(@endTime, '%Y-%m-%d %H:%i:%s') ", endTime.Value.ToString("yyyy-MM-dd HH:mm:ss"));
sql.Append(" order by t.order_time desc, t.id asc ");
List<BsOrder> list = session.FindListBySql<BsOrder>(sql.SQL, sql.Params);
return list;
}
}
分頁查詢
public List<BsOrder> GetListPage(ref PageModel pageModel, int? status, string remark, DateTime? startTime, DateTime? endTime)
{
using (var session = DBHelper.GetSession())
{
SqlString sql = session.CreateSqlString(@"
select t.*, u.real_name as OrderUserRealName
from bs_order t
left join sys_user u on t.order_userid=u.id
where 1=1");
sql.AppendIf(status.HasValue, " and t.status=@status", status);
sql.AppendIf(!string.IsNullOrWhiteSpace(remark), " and t.remark like concat('%',@remark,'%')", remark);
sql.AppendIf(startTime.HasValue, " and t.order_time>=STR_TO_DATE(@startTime, '%Y-%m-%d %H:%i:%s') ", startTime.Value.ToString("yyyy-MM-dd HH:mm:ss"));
sql.AppendIf(endTime.HasValue, " and t.order_time<=STR_TO_DATE(@endTime, '%Y-%m-%d %H:%i:%s') ", endTime.Value.ToString("yyyy-MM-dd HH:mm:ss"));
string orderby = " order by t.order_time desc, t.id asc ";
pageModel = session.FindPageBySql<BsOrder>(sql.SQL, orderby, pageModel.PageSize, pageModel.CurrentPage, sql.Params);
return pageModel.GetResult<BsOrder>();
}
}
事務
public string Insert(BsOrder order, List<BsOrderDetail> detailList)
{
using (var session = DBHelper.GetSession())
{
try
{
session.BeginTransaction();
order.Id = Guid.NewGuid().ToString("N");
order.CreateTime = DateTime.Now;
decimal amount = 0;
foreach (BsOrderDetail detail in detailList)
{
detail.Id = Guid.NewGuid().ToString("N");
detail.OrderId = order.Id;
detail.CreateTime = DateTime.Now;
amount += detail.Price * detail.Quantity;
session.Insert(detail);
}
order.Amount = amount;
session.Insert(order);
session.CommitTransaction();
return order.Id;
}
catch (Exception ex)
{
session.RollbackTransaction();
Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace);
throw ex;
}
}
}
非同步查詢
public async Task<List<BsOrder>> GetListPageAsync(PageModel pageModel, int? status, string remark, DateTime? startTime, DateTime? endTime)
{
using (var session = await DBHelper.GetSessionAsync())
{
SqlString sql = session.CreateSqlString(@"
select t.*, u.real_name as OrderUserRealName
from bs_order t
left join sys_user u on t.order_userid=u.id
where 1=1");
sql.AppendIf(status.HasValue, " and t.status=@status", status);
sql.AppendIf(!string.IsNullOrWhiteSpace(remark), " and t.remark like concat('%',@remark,'%')", remark);
sql.AppendIf(startTime.HasValue, " and t.order_time>=STR_TO_DATE(@startTime, '%Y-%m-%d %H:%i:%s') ", startTime.Value.ToString("yyyy-MM-dd HH:mm:ss"));
sql.AppendIf(endTime.HasValue, " and t.order_time<=STR_TO_DATE(@endTime, '%Y-%m-%d %H:%i:%s') ", endTime.Value.ToString("yyyy-MM-dd HH:mm:ss"));
string orderby = " order by t.order_time desc, t.id asc ";
pageModel = await session.FindPageBySqlAsync<BsOrder>(sql.SQL, orderby, pageModel.PageSize, pageModel.CurrentPage, sql.Params);
return pageModel.GetResult<BsOrder>();
}
}
條件查詢(使用 ForContains、ForStartsWith、ForEndsWith、ForDateTime、ForList 等輔助方法)
public List<BsOrder> GetListExt(int? status, string remark, DateTime? startTime, DateTime? endTime, string ids)
{
using (var session = DBHelper.GetSession())
{
SqlString sql = session.CreateSqlString(@"
select t.*, u.real_name as OrderUserRealName
from bs_order t
left join sys_user u on t.order_userid=u.id
where 1=1");
sql.AppendIf(status.HasValue, " and t.status=@status", status);
sql.AppendIf(!string.IsNullOrWhiteSpace(remark), " and t.remark like @remark", sql.ForContains(remark));
sql.AppendIf(startTime.HasValue, " and t.order_time >= @startTime ", sql.ForDateTime(startTime.Value));
sql.AppendIf(endTime.HasValue, " and t.order_time <= @endTime ", sql.ForDateTime(endTime.Value));
sql.Append(" and t.id in @ids ", sql.ForList(ids.Split(',').ToList()));
sql.Append(" order by t.order_time desc, t.id asc ");
List<BsOrder> list = session.FindListBySql<BsOrder>(sql.SQL, sql.Params);
return list;
}
}
使用Lambda表示式單表查詢
單表分頁查詢使用ToPageList替換ToList即可
public void TestQueryByLambda6()
{
using (var session = DBHelper.GetSession())
{
SqlString<BsOrder> sql = session.CreateSqlString<BsOrder>();
string remark = "測試";
List<BsOrder> list = sql.Query()
.WhereIf(!string.IsNullOrWhiteSpace(remark),
t => t.Remark.Contains(remark)
&& t.CreateTime < DateTime.Now
&& t.CreateUserid == "10")
.OrderByDescending(t => t.OrderTime).OrderBy(t => t.Id)
.ToList();
foreach (BsOrder item in list)
{
Console.WriteLine(ModelToStringUtil.ToString(item));
}
}
}
使用Lambda表示式聯表分頁查詢(簡單的聯表查詢,複雜情況請使用原生SQL或原生SQL和Lambda表示式混寫)
public void TestQueryByLambda7()
{
using (var session = DBHelper.GetSession())
{
SqlString<BsOrder> sql = session.CreateSqlString<BsOrder>();
int total;
List<string> idsNotIn = new List<string>() { "100007", "100008", "100009" };
List<BsOrder> list = sql.Query()
.Select<SysUser>(u => u.UserName, t => t.OrderUserName)
.Select<SysUser>(u => u.RealName, t => t.OrderUserRealName)
.LeftJoin<SysUser>((t, u) => t.OrderUserid == u.Id)
.LeftJoin<BsOrderDetail>((t, d) => t.Id == d.OrderId)
.Where<SysUser, BsOrderDetail>((t, u, d) => t.Remark.Contains("訂單") && u.CreateUserid == "1" && d.GoodsName != null)
.WhereIf<BsOrder>(true, t => t.Remark.Contains("測試"))
.WhereIf<BsOrder>(true, t => !idsNotIn.Contains(t.Id))
.WhereIf<SysUser>(true, u => u.CreateUserid == "1")
.OrderByDescending(t => t.OrderTime).OrderBy(t => t.Id)
.ToPageList(1, 20, out total);
foreach (BsOrder item in list)
{
Console.WriteLine(ModelToStringUtil.ToString(item));
}
}
}
原生SQL和Lambda表示式混寫
public void TestQueryByLambda9()
{
using (var session = DBHelper.GetSession())
{
SqlString<BsOrder> sql = session.CreateSqlString<BsOrder>(@"
select t.*, u.real_name as OrderUserRealName
from bs_order t
left join sys_user u on t.order_userid=u.id
where 1=1");
List<BsOrder> list = sql.Where(t => t.Status == int.Parse("0")
&& t.Status == new BsOrder().Status
&& t.Remark.Contains("訂單")
&& t.Remark != null
&& t.OrderTime >= new DateTime(2010, 1, 1)
&& t.OrderTime <= DateTime.Now.AddDays(1))
.WhereIf<SysUser>(true, u => u.CreateTime < DateTime.Now)
.OrderByDescending(t => t.OrderTime).OrderBy(t => t.Id)
.ToList();
foreach (BsOrder item in list)
{
Console.WriteLine(ModelToStringUtil.ToString(item));
}
}
}