自制简单的.Net ORM框架 简介
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自制简单的.Net ORM框架 简介相关的知识,希望对你有一定的参考价值。
在自己研究ORM之前,也使用过几个成熟的ORM方案,例如:EntityFramework,PetaPoco,Dapper 等,用是很好用,但是对自己来说总是不那么方便,EF比较笨重,Dapper要自定义扩展等等,所以萌发了开发适合自己的ORM的想法,因为未起名字,所以下文所有地方都使用MyORM来代替.
Nuget地址: https://www.nuget.org/packages/Dai.CommonLib.Sql
简介: MyORM是一个小型的ORM,稍微参考了PetaPoco,支持简单的对象关系映射,非常智能化,整个框架主要使用2个类型: QueryContext 和 DbQuery , 其中 DbQuery 只是一个高级的SqlHelper, 故下文主要介绍 QueryContext.
目前只支持 SqlServer 2005+ . Mysql的话以后再加上
1.一些特性
MyORM的映射关系主要是用Linq To Sql 自带的2个特性来完成的 TableAttribute 和 ColumnAttribute
例:
[Table(Name="AlbumInfo")] public partial class AlbumInfo { [Column(Name="Id",IsPrimaryKey=true,IsDbGenerated=true)] public int Id { get; set; } [Column(Name="AlbumName")] public string AlbumName { get; set; } [Column(Name="CoverImg")] public string CoverImg { get; set; } [Column(Name="Desc")] public string Desc { get; set; } [Column(Name="Scope")] public int Scope { get; set; } [Column(Name="LookCount")] public int LookCount { get; set; } [Column(Name="PraiseCount")] public int PraiseCount { get; set; } [Column(Name="CommentCount")] public int CommentCount { get; set; } [Column(Name="CreatorId")] public int CreatorId { get; set; } [Column(Name="CreateTime")] public DateTime CreateTime { get; set; } [Column(Name="State")] public int State { get; set; } }
并且MyORM支持 MetadataTypeAttribute 特性
[MetadataType(typeof(MetadataTypeBase))] partial class AlbumInfo { } internal class MetadataTypeBase {
[System.Data.DeletedMapping(2)]
[System.Data.InvalidValue(2)]
[System.Data.ValidValue(0)]
[System.Data.ValidValue(1)]
public int State { get; set; } }
上面有几个定义的特性:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace System.Data { /// <summary> /// 一个特性,用于标记数据删除状态 /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public sealed class DeletedMappingAttribute : Attribute { public DeletedMappingAttribute(object value) { if (value == null || value == DBNull.Value) { throw new ArgumentNullException("值不能为null或DbNull.Value", "value"); } this.Value = value; } /// <summary> /// 获取标记为删除的值 /// </summary> public object Value { get; private set; } } }
//DeletedMappingAttribute 顾名思义,就是它用来标记一些并不需要真删除的数据,后面介绍删除的时候会用到.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace System.Data { /// <summary> /// 一个特性,用于标识数据字段数值的无效性 /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public sealed class InvalidValueAttribute : Attribute { public InvalidValueAttribute(object value) { //if (value == null || value == DBNull.Value) //{ // throw new ArgumentNullException("值不能为null或DbNull.Value", "value"); //} this.Value = value; } /// <summary> /// 获取字段的无效值 /// </summary> public object Value { get; private set; } } }
//这个特性主要用于根据主键查询数据,它会自动过滤掉不符合条件的数据,简化我们编码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace System.Data { /// <summary> /// 一个特性,用于标识数据字段数值的有效性 /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public sealed class ValidValueAttribute : Attribute { public ValidValueAttribute(object value) { //if (value == null || value == DBNull.Value) //{ // throw new ArgumentNullException("值不能为null或DbNull.Value", "value"); //} this.Value = value; } /// <summary> /// 获取字段的有效值 /// </summary> public object Value { get; private set; } } }
//这个特性主要用于根据主键查询数据,它会自动匹符合条件的数据,简化我们编码
2.开始使用:
表 AlbumInfo:
使用QueryContext前先初始化:
protected QueryContext QueryContext = new QueryContext("DBConnectionString");
插入一条数据:
[TestMethod] public void InsertTest() { AlbumInfo album = new AlbumInfo() { AlbumName = "测试", CreateTime = DateTime.Now, CreatorId = -1, Desc = "测试相册", Scope = 1, }; QueryContext.Insert(album); Assert.IsTrue(album.Id > 0); }
查询:
QueryContext 提供了多种查询的方式:
(1).根据主键查:
var album = QueryContext.ByPrimaryKey<AlbumInfo>(2);
注意这里有个重载
var album = QueryContext.ByPrimaryKey<AlbumInfo>(2,true);
他们之间的区别就是上面 ValidValueAttribute 和InvalidValueAttribute 介绍的,附带查询条件的区别
假设我给AlbumInfo的字段 State 设置的无效类型是 2 (删除状态),那么第一种方法查出来是null,第二种查出来才有值
有个泛型重载方法可以简单的将查询出来的数据转换成Dto
var albumDto = QueryContext.ByPrimaryKey<AlbumInfo,AlbumInfoDto>(2);
查询指定类型:
// // 摘要: // 查询指定数据 // // 参数: // sql: // // args: // // 类型参数: // T: public List<T> Fetch<T>(string sql, params object[] args);
sql可以是这种写法 : select * from albuminfo where ........,也可以精简为 where ..... ,程序会自动判断填充的,甚至可以这样:
QueryContext.Fetch<AlbumInfo>("");
它支持参数查询(PetaPoco),可以这样:
QueryContext.Fetch<AlbumInfo>("where id>@0 and [email protected]",1,-1);
也可以这样
var createTimePar=QueryContext.CreateParameter("createtime",DateTime.Now); QueryContext.Fetch<AlbumInfo>("where id>@0 and createtime>@createtime and [email protected]",1,createTimePar,-1);
Fetch方法支持简单类型的查询:
QueryContext.Fetch<int>("Select id From AlbumInfo");
如果你不确定返回的是什么类型,可以使用这个重载,它会自动填充的
// // 摘要: // 查询数据 // // 参数: // sql: // // args: public List<dynamic> Fetch(string sql, params object[] args);
Fetch方法有个重载:
// // 摘要: // 查询指定数据 // // 参数: // sql: // // args: // // 类型参数: // TTable: // 要查询的数据表类型 // // TResult: // 返回结果类型 public List<TResult> Fetch<TTable, TResult>(string sql, params object[] args)
可以返回需要的类型
List<AlbumInfoDto> list= QueryContext.Fetch<AlbumInfo,AlbumInfoDto>("");
Fetch方法有个延迟加载版本 Query ,与Fetch类似
分页: QueryContext支持简单的分页
// // 摘要: // 查询分页数据 // // 参数: // page: // // pageSize: // // sql: // // args: // // 类型参数: // T: public IPageResult<T> Page<T>(int page, int pageSize, string sql, params object[] args); // // 摘要: // 查询分页数据 // // 参数: // page: // // pageSize: // // sql: // // args: public IPageResult<dynamic> Page(int page, int pageSize, string sql, params object[] args); // // 摘要: // 异步查询分页 // // 参数: // page: // // pageSize: // // sql: // // args: // // 类型参数: // T: [DebuggerStepThrough] public Task<IPageResult<T>> PageAsync<T>(int page, int pageSize, string sql, params object[] args); // // 摘要: // 异步查询分页 // // 参数: // page: // // pageSize: // // sql: // // args: // // 类型参数: // TTable: // 要查询的数据表类型 // // TResult: // 返回结果类型 [DebuggerStepThrough] public Task<IPageResult<TResult>> PageAsync<TTable, TResult>(int page, int pageSize, string sql, params object[] args) where TTable : class where TResult : class; // // 摘要: // 异步查询分页 // // 参数: // page: // // pageSize: // // sql: // // args: [DebuggerStepThrough] public Task<IPageResult<dynamic>> PageAsync(int page, int pageSize, string sql, params object[] args);
这么用:
QueryContext.Page<AlbumInfo>("where id>@0",1);
它返回一个IPageResult类型的数据
using System.Collections; using System.Collections.Generic; namespace System { // 摘要: // 一个接口,表示一个分页结果集 // // 类型参数: // T: public interface IPageResult<out T> : IPageResult, IEnumerable<T>, IEnumerable { } } using System.Collections; namespace System { // 摘要: // 一个接口,表示一个分页结果集 public interface IPageResult : IEnumerable { // 摘要: // 获取当前集合的数量 int Count { get; } // // 摘要: // 获取当前页码 int CurrentPage { get; } // // 摘要: // 每页显示数 int ItemsPerPage { get; } // // 摘要: // 获取数据总数 long TotalItems { get; } // // 摘要: // 获取分页总数 int TotalPages { get; } } }
分页也与上面一样,有各种重载方法
FirstOrDefault:
如果查询单条数据就使用它:
// // 摘要: // 查询第一条数据 // // 参数: // sql: // // args: // // 类型参数: // T: public TResult FirstOrDefault<TTable, TResult>(string sql, params object[] args) where TTable : class where TResult : class; // // 摘要: // 查询第一条数据 // // 参数: // sql: // // args: // // 类型参数: // T: public T FirstOrDefault<T>(string sql, params object[] args); // // 摘要: // 查询第一条数据 // // 参数: // sql: // // args: public dynamic FirstOrDefault(string sql, params object[] args);
存储过程:
查询的方法都有存储过程版本,方法名多一个"Procedure":\
// // 摘要: // 调用存储过程查询指定数据 // // 参数: // procedureName: // // args: // // 类型参数: // T: public List<T> FetchProcedure<T>(string procedureName, params object[] args);
更新:
var album = QueryContext.ByPrimaryKey<AlbumInfo>(2); Assert.IsNotNull(album); album.AlbumName = "修改过的测试"; album.Desc = "修改过的测试相册"; //var result = QueryContext.Update(album); //var result = QueryContext.Update(album, "AlbumName", "Desc"); var result = QueryContext.Update(album, o => o.AlbumName, o => o.Desc); Assert.IsTrue(result); var newAlbum = QueryContext.ByPrimaryKey<AlbumInfo>(album.Id); Assert.IsTrue(newAlbum.AlbumName.Equals(album.AlbumName)); Assert.IsTrue(newAlbum.Desc.Equals(album.Desc));
更新可以完整更新,也可以约定更新字段,并且支持指定字段名和使用对象映射字段
删除:
var album = QueryContext.ByPrimaryKey<AlbumInfo>(2); QueryContext.Delete(album); Assert.IsNull(QueryContext.ByPrimaryKey<AlbumInfo>(2)); Assert.IsNotNull(QueryContext.ByPrimaryKey<AlbumInfo>(2, true)); album = QueryContext.ByPrimaryKey<AlbumInfo>(2, true); album.State = 0; album.CoverImg = ""; QueryContext.Update(album, o => o.State, o => o.CoverImg); album = QueryContext.ByPrimaryKey<AlbumInfo>(2); QueryContext.Delete(album, true); Assert.IsNull(QueryContext.ByPrimaryKey<AlbumInfo>(2)); Assert.IsNull(QueryContext.ByPrimaryKey<AlbumInfo>(2, true));
删除有2种,一种是删除数据,一种是修改删除状态(需要使用前面提到的System.Data.DeletedMapping来约定条件)
事务:
QueryContext支持简单的事务
// // 摘要: // 启动事务 // // 参数: // context: public void Transaction(Action<IQueryContext> action); // // 摘要: // 启动事务 // // 参数: // context: public void Transaction(IsolationLevel isolationLevel, Action<IQueryContext> action);
var album = QueryContext.ByPrimaryKey<AlbumInfo>(2); var albumDto = QueryContext.ByPrimaryKey<AlbumInfo, AlbumInfoDto>(2); QueryContext.Transaction(context => { album.AlbumName = "修改过的事务"; context.Update(album, "AlbumName"); }); string albumName = QueryContext.ExecuteScalar<string>("Select AlbumName From AlbumInfo Where [email protected]", 2); Assert.AreEqual(album.AlbumName, albumName); try { QueryContext.Transaction(context => { album.AlbumName = "修改回来的事务"; context.Update(album, "AlbumName"); throw new Exception(); }); } catch { } albumName = QueryContext.ExecuteScalar<string>("Select AlbumName From AlbumInfo Where [email protected]", 2); Assert.AreNotEqual(album.AlbumName, albumName);
在事务的执行过程中,如果不发生错误,则执行完之后会提交事务,如果发生错误,则回滚,同时将错误继续向上级抛,所有如果要手动控制流程的话,就抛出个异常吧!
以上就是QueryContext的一个简单介绍,而MyORM的核心就是QueryContext!
using System; namespace System.Data { // 摘要: // 数据查询上下文,支持映射实体的操作(使用默认设置) public sealed class QueryContext : QueryContextBase { public QueryContext(); public QueryContext(string connectionStringName); public static string DefaultConnectionStringName { get; set; } } } using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq.Expressions; using System.Threading.Tasks; namespace System.Data { // 摘要: // 数据查询上下文,支持映射实体的操作 public abstract class QueryContextBase : DbQueryMethod, IQueryContext, IAsyncQueryContext, IDisposable { protected QueryContextBase(string connectionStringName); // 摘要: // 根据主键获取数据(可使用 System.Data.ValidValueAttribute 来约定条件) // // 参数: // primaryValue: // // 类型参数: // T: public T ByPrimaryKey<T>(object primaryValue) where T : class; // // 摘要: // 根据主键获取数据(可使用 System.Data.ValidValueAttribute 来约定条件) // // 参数: // primaryValue: // // 类型参数: // TTable: // 要查询的数据表类型 // // TResult: // 返回结果类型 public TResult ByPrimaryKey<TTable, TResult>(object primaryValue) where TTable : class where TResult : class; // // 摘要: // 根据主键获取数据,并指定是否忽略约束条件 // // 参数: // primaryValue: // // ignore: // // 类型参数: // T: public T ByPrimaryKey<T>(object primaryValue, bool ignore) where T : class; // // 摘要: // 根据主键获取数据,并指定是否忽略约束条件 // // 参数: // primaryValue: // // ignore: // // 类型参数: // TTable: // 要查询的数据表类型 // // TResult: // 返回结果类型 public TResult ByPrimaryKey<TTable, TResult>(object primaryValue, bool ignore) where TTable : class where TResult : class; // // 摘要: // 异步根据主键获取数据(可使用 System.Data.ValidValueAttribute 来约定条件) // // 参数: // primaryValue: // // 类型参数: // T: [DebuggerStepThrough] public Task<T> ByPrimaryKeyAsync<T>(object primaryValue) where T : class; // // 摘要: // 异步根据主键获取数据(可使用 System.Data.ValidValueAttribute 来约定条件) // // 参数: // primaryValue: // // 类型参数: // TTable: // 要查询的数据表类型 // // TResult: // 返回结果类型 [DebuggerStepThrough] public Task<TResult> ByPrimaryKeyAsync<TTable, TResult>(object primaryValue) where TTable : class where TResult : class; // // 摘要: // 异步根据主键获取数据,并指定是否忽略约束条件 // // 参数: // primaryValue: // // ignore: // // 类型参数: // T: [DebuggerStepThrough] public Task<T> ByPrimaryKeyAsync<T>(object primaryValue, bool ignore) where T : class; // // 摘要: // 异步根据主键获取数据,并指定是否忽略约束条件 // // 参数: // primaryValue: // // ignore: // // 类型参数: // TTable: // 要查询的数据表类型 // // TResult: // 返回结果类型 [DebuggerStepThrough] public Task<TResult> ByPrimaryKeyAsync<TTable, TResult>(object primaryValue, bool ignore) where TTable : class where TResult : class; // // 摘要: // 基于当前提供的驱动程序创建一个Sys
以上是关于自制简单的.Net ORM框架 简介的主要内容,如果未能解决你的问题,请参考以下文章