.Net Core ORM 框架

一个简单的orm框架

作者在使用很多orm框架的时候觉得查询语句写法似乎不是很好用,例如sqlSugar,EF,sqlSugar呢链接的时候必须注意表的别称,而且多条件拼接似乎也不是很友好,而ef呢多表连接查询是非常不友好的,这个简单的orm呢查询语句写起来非常优雅,不信你可以试一试。

支持的数据库

mysql,sqlservice,sqlite
后续会加其他数据库,提供了扩展的方式

版本

.net6

新的版本

.NET Standard 2

项目地址

GitHub 项目地址:https://github.com/soulzdm/NetCore-ORM-Simple

使用方式

作者的经验并不是非常丰富,而且是第一个版本,因此可能存在诸多问题,不建议直接使用生产环境,建议测试,了解一下再使用。如果使用过程中遇到问题欢迎提问题。一些高级分库分表的功能还暂时不支持,可以不用配置,后续会更新。
工具已经被做成程序包了可以使用nuget下载。
搜素关键词Simple.ORM

创建一个数据库连接

// 创建一个客户端 老版本
 ISimpleClient simpleClient = new SimpleClient(
          new DataBaseConfiguration(false,
          new ConnectionEntity("数据库连接字符串")
          {
              IsAutoClose = true,//是否自动释放链接
              DBType = eDBType.Mysql,数据类型目前仅仅支持
              Name = "test1",连接名称
              ReadWeight = 5,//读的权重-不是多个数据库可以不用配置
              WriteReadType = eWriteOrReadType.ReadOrWrite//配置数据库支持读和写入
          }));
///新的版本
//需要手动引入相关数据的连接驱动
//以mysql为例子MySql.Data
//相对来说配置的步骤多了一点
//step 1.
 DataBaseConfiguration.DBDrives.Add(eDBType.Mysql, Tuple.Create(typeof(MySqlConnection), typeof(MySqlParameter)));
//step 2.
 client = new SimpleClient(
          new DataBaseConfiguration(true,
          new(MastStr)
          {
              IsAutoClose = true,
              DBType = eDBType.Mysql,
              Name = "master",
              ReadWeight=1,
              WriteReadType = eWriteOrReadType.ReadOrWrite
          } ));

测试用的实体

 [ClassName("dictionarytable")]//对应数据库的表名称
    public class DictionEntity
    {
        [Key(true)]
        public int ID { get; set; }
        public int MainID { get; set; }
        public int RowID { get; set; }
        public string RowName { get; set; }
        public string RowDesc { get; set; }
    }
     [ClassName("UserTable")]
    public class UserEntity
    {
        [Key(true)]
        public int ID { get; set; }
        public int UserName { get; set; }
        public int CompanyId { get; set; }
    }

    [ClassName("CompanyTable")]
    public class CompanyEntity
    {
        [Key(true)]
        public int ID { get; set; }
        public int CompanyName { get; set; }
    }

添加

 simpleClient.Insert(new DictionEntity());

更新

var entity=simpleClient.Queryable<DictionEntity>().FirstOrDefault();
        entity.RowName = "testUpdate";
        simpleClient.Update(entity);

查询

//单表查询
 var entity = simpleClient.Queryable<DictionEntity>().Select(d=>new{Name=d.RowName,Id=d.ID}).FirstOrDefault();
 var entity1 = simpleClient.Queryable<DictionEntity>().Select(d=>new{Name=d.RowName,Id=d.ID}).Where(d=>d.Name.Equals("test")).FirstOrDefault();
 //多表连接查询 无需关注参数的别称,只需要记住顺序即可
 var data1 = simpleClient.Queryable<UserEntity, CompanyEntity>((u,c)=>new JoinInfoEntity(new JoinMapEntity(eJoinType.Inner,u.CompanyId==c.ID))).FirstOrDefault();

实现方式

  • 实现思路
    生成sql语句中最过于麻烦的在于查询语句,有表与表的链接,分组,排序还有一些函数。.net core 提供了表达式目录树(Expression)该对象内部可以放置一个委托。官方呢也非常贴心提供了表达式目录树的解析相关类库expressionVisitor
    使用该类库呢可以将例如查询语句中的条件二元表达式解析,映射规则(select 部分),以及表与表的链接关系。将条件,连接还有映射部分解析完成后再将这些信息存放到相应类中,直到最后需要返回查询数据的时候根据不同数据库生成不同的语句,因为不同数据库的某些语句可能存在查询这样做呢可以屏蔽这种查询。当然最上层的一些接口的话其实和ef 和sqlsugar 非常相似,但是有些地方做了改进,改成了符合我自己的习惯。
    一.其中呢有些地方用到了动态类型,它好像可以跨过编译检查在有些地方不得不用例如存放(有时候使用者可能直接返回匿名对象但是不同通过反射构造,表达式内部的委托执行后可以返回匿名对象,但是这个委托的存放不太好声明变量存储,因此用动态类型,但是应当注意类型安全问题)
    二.在表达式目录树种取出变量的值也不是一件看似简单的类型,默认呢会将这一局部的所有变量保存放在一个匿名对象中,第一步是从该匿名对象中取出值,然而这还有吗结束,应为你可能是用了该变量的某个属性因此还得再通过反射取值。如果是数组字典集合又会有差异。

  • 遇到的问题
    在分组之后返回匿名对象目前是不支持的,因为分组之后又可以调用一些函数例如(原型:Sum(Expression<Func<TResult, TField>> expression) 实际使用还是便于看懂:Count=v.Count()),然后我前面实现构造匿名对象的方式是将最后一个具体的对象(最后一个具体的对象指的是select 中的最后一个具体的对象)构造出来然后使用表达式目录树中保存的委托将该对象放入内部然后执行返回匿名对象,如果内部调用了方法方法的参数又是一个委托似乎就有点麻烦事了;目前暂时还没有解决

 var JoinData2 = client.Queryable<UserEntity, RoleEntity, CompanyEntity>((u, r, c) => new JoinInfoEntity(
                  new JoinMapEntity(eJoinType.Inner, u.RoleId.Equals(r.Id)),
                  new JoinMapEntity(eJoinType.Inner, u.CompanyId.Equals(c.Id))
                  )).
                  Where((u, r, c) => u.Id > 10).Select((u,r,c)=>
                  new ViewEntity {
                      UserName=u.Name,
                      CompanyId=c.Id,
                      CompanyName=c.CompanyName,
                      RoleId=r.Id
                  }
                  ).
                  GroupBy(v=>v.RoleId).
                  Select((v) =>new GroupEntity()
                  {
                      Count=v.Count(),
                      FirstOrDefaultName=v.FirstOrDefault(s=>s.UserName),
                      Max=v.Max(s=>s.RoleId)
                  }).ToList();

高级特性

  • 支持定义特性映射表名称和列名称(需要继承相关接口)
public class MyColumnAttrbute :AbsAttribute, IColumn
  {
      public MyColumnAttrbute(string name=null,bool key=false,bool autoIncrease=false,bool ignore=false) :base(name)
      {
          Key= key;
          AutoIncrease= autoIncrease;
          Ignore= ignore;
      }

      public bool Key { get{ return key; } set { key = value; } }
      public bool AutoIncrease { get{ return autoIncrease; } set { autoIncrease = value; } }
      public bool Ignore { get { return ignore; } set { ignore = value; } }

      private bool key;
      private bool autoIncrease;
      private bool ignore;
  }
   public class MyTableAttrbute : AbsAttribute,IName
  {
      public MyTableAttrbute(string name = null) : base(name)
      {
        
      }
  }
## 读写分离
1.配置一个写库多个从库从库
从库根据配置的权重进行均衡
```csharp
client = new SimpleClient(
        new DataBaseConfiguration(true,
        new ConnectionEntity(MastStr)
        {
            IsAutoClose = true,
            DBType = eDBType.Mysql,
            Name = "master",
            WriteReadType = eWriteOrReadType.Write
        }, new (db1)
        {
            IsAutoClose = true,
            DBType = eDBType.Mysql,
            Name = "db1",
            ReadWeight = 1,
            WriteReadType = eWriteOrReadType.Read
        },
        new(db2)
        {
            IsAutoClose = true,
            DBType = eDBType.Mysql,
            Name = "db2",
            ReadWeight = 3,
            WriteReadType = eWriteOrReadType.Read
        },
        new(db3)
        {
            IsAutoClose = true,
            DBType = eDBType.Mysql,
            Name = "db3",
            ReadWeight = 6,
            WriteReadType = eWriteOrReadType.Read
        }));

          client.SetAOPLog((sql, Params) =>
          {
              Console.WriteLine(sql);
          });
          client.SetAttr(typeof(MyTableAttrbute), typeof(MyColumnAttrbute));
          // var data=client.Queryable<MatchLog>().Take(10).ToList();
      }
      2.测试代码
      var master = 0;
          var db1 = 0;
          var db2 = 0;
          var db3 = 0;

          UserEntity iuser = new UserEntity();
          iuser.Name = "插入小明";
          iuser = client.Insert(iuser).ReturnEntity();
          iuser.Name = "更新小明";
          var result = client.Update(iuser).SaveChange();

          for (int i = 0; i < 100; i++)
          {
              var user=client.Queryable<UserEntity>().Where(u=>u.Id.Equals(1)).First();
              if (user!=null)
              {
                  if (user.RoleId==1)
                  {
                      db1++;
                  }else if (user.RoleId==2)
                  {
                      db2++;
                  }
                  else if (user.RoleId == 3)
                  {
                      db3++;
                  }
                  else if (user.RoleId == 0)
                  {
                      master++;
                  }
              }
          }
          Console.WriteLine($"db1={db1}\n db2={db2}\n db3={db3} \n master={master}");

扩展函数

public static bool Contains<T>(T[]datas,T targe);
      /// <summary>
      /// 左边模糊查询
      /// </summary>
      /// <param name="value"></param>
      /// <param name="left"></param>
      /// <returns></returns>
      public static bool LeftContains(string value, string left);
      /// <summary>
      /// 右边模糊查询
      /// </summary>
      /// <param name="value"></param>
      /// <param name="left"></param>
      /// <returns></returns>
      public static bool RightContains(string value, string right);
      /// <summary>
      /// 日期函数
      /// </summary>
      /// <param name="start"></param>
      /// <param name="end"></param>
      /// <param name="type"></param>
      /// <returns></returns>
      public static int DateDiff(DateTime start,DateTime end,eDateType type);
      /// <summary>
      /// 日期函数
      /// </summary>
      /// <param name="start"></param>
      /// <param name="end"></param>
      /// <param name="type"></param>
      /// <returns></returns>
      public static int DateDiff(TimeSpan start, TimeSpan end, eDateType type);
      /// <summary>
      /// 
      /// </summary>
      /// <param name="time"></param>
      /// <returns></returns>
      public static int Year(DateTime time);
      public static int Month(DateTime time);
      public static int Day(DateTime time);
      public static int Hour(DateTime time);
      public static int Second(DateTime time);
      public static DateTime Now();
      /// <summary>
      /// 向上取整数
      /// </summary>
      /// <param name="value"></param>
      /// <param name="len"></param>
      /// <returns></returns>
      public static float Round(float value,int len);
      public static double Round(double value, int len);
      public static decimal Round( decimal value, int len);
      /// <summary>
      /// 向下取整
      /// </summary>
      /// <param name="value"></param>
      /// <param name="len"></param>
      /// <returns></returns>
      public static float Truncate( float value, int len);
      public static double Truncate( double value, int len);
      public static decimal Truncate( decimal value, int len);
      public static bool IsNullOrEmpty( string strValue);
      public static bool IsNull<T>(T t);

性能

我自己简单测试了单表查询主要事件耗费在打开连接和command读取数据大约占用了百分之九十五以上的时间拼接sql和映射数据占用的时间非常少(以二十条而基准进行测试),单次查询一百毫秒左右映射数据生成sql只用了几毫秒,因此性能还是可以的。
单条语句查询一百次测试

Console.WriteLine("*****************Simple***************");
          Stopwatch watch = new Stopwatch();
          watch.Start();
          ISimpleClient client = new SimpleClient(new DataBaseConfiguration(false,new ConnectionEntity(strConnection)
          {
              DBType=eDBType.Mysql,
              IsAutoClose=false,
              Name="Test"
          })
              );
          client.SetAPOLog((sql, pars) =>
          {
              //Console.WriteLine($"Date:{DateTime.Now}\t \t sql:{sql}");
          });
          int DataLength = 20;
          int count=100;
          long l = 0;
          long sum = 0;
          long sum2 = 0;
          long l2 = 0;
          for (int i = 0; i < count; i++)
          {
              l = watch.ElapsedMilliseconds;
              var data = client.Queryable<MissionDetailEntity>().Where(m => !m.IsDelete || (m.EndTime < DateTime.Now && m.StartTime > DateTime.MinValue)).Take(DataLength);
              //Console.WriteLine(i);
              sum += watch.ElapsedMilliseconds-l;
              l2=watch.ElapsedMilliseconds;
              data.ToList();
              sum2+=watch.ElapsedMilliseconds-l2;
          }
          watch.Stop();
          Console.WriteLine($"耗时{watch.ElapsedMilliseconds}ms");
          Console.WriteLine($"平均每次耗时:{(watch.ElapsedMilliseconds+0.0) / count}ms");

          Console.WriteLine($"拼接语句耗时{sum}ms");
          Console.WriteLine($"拼接语句平均每次耗时:{(sum+0.0)/ count}ms");

          Console.WriteLine($"读取数据耗时{sum2}ms");
          Console.WriteLine($"读取数据平均每次耗时:{(sum2+0.0) / count}ms");

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以测试结果来看的话性能还是非常好的

bug

欢迎发现bug

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值