自制简单的.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个类型: QueryContextDbQuery , 其中 DbQuery 只是一个高级的SqlHelper, 故下文主要介绍 QueryContext.

目前只支持 SqlServer 2005+ .   Mysql的话以后再加上 

 

1.一些特性

MyORM的映射关系主要是用Linq To Sql 自带的2个特性来完成的 TableAttributeColumnAttribute 

例: 

[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框架 简介的主要内容,如果未能解决你的问题,请参考以下文章

自制 SQL 模型 vs. ORM?

ASP.NET Core 中的 ORM 之 Dapper

ORM简介 && MyBatis和Hibernate的不同 && 动态代理简单实现Mybatis基本使用

android ORM框架--greenDao简介

android ORM框架--greenDao简介

轻量级ORM框架PetaPoco