CloudEntity使用手册_02 - applelikai/CloudEntity GitHub Wiki

第02章 CloudEntity使用入门

  • 下面的所有步骤都不做详细说明,先照做,不明白的后面章节再说。

开发环境准备

这次以操作MySQL为例子,使用实体框架CloudEntity操作MySQL数据库。

  1. 安装MySQL
  2. 安装dotnet sdk,我在这里使用的是6.0版本的dotnet sdk
  3. 安装Visual Studio 2022

创建数据库,准备控制台工程

  1. 登录MySQL,并创建一个数据库,我在这里创建的数据库为pingguo_blog,连接字符串为Server=localhost;User Id=root;Password=000000;Database=pingguo_blog;
  2. 打开Visual Studio 2022,新建一个net6.0的控制台工程(netcore, 及其之后的版本都行),新建文件夹如下:
    工程
  3. 添加Nuget包如下:
    Nuget包

创建实体类和Mapper类,配置实体映射

  1. Entities文件夹下添加实体类如下:
    entities
    namespace PingGuo.Blog.Test.Entities
    {
        /// <summary>
        /// 角色类
        /// </summary>
        public class Role
        {
            /// <summary>
            /// 角色id
            /// </summary>
            public string RoleId { get; set; }
            /// <summary>
            /// 角色名称
            /// </summary>
            public string RoleName { get; set; }
            /// <summary>
            /// 录入时间
            /// </summary>
            public DateTime? CreatedTime { get; set; }
        }
    }
    namespace PingGuo.Blog.Test.Entities
    {
        /// <summary>
        /// 用户类
        /// </summary>
        public class User
        {
            /// <summary>
            /// 用户id
            /// </summary>
            public string UserId { get; set; }
            /// <summary>
            /// 用户名
            /// </summary>
            public string UserName { get; set; }
            /// <summary>
            /// 角色id
            /// </summary>
            public string RoleId { get; set; }
            /// <summary>
            /// 角色
            /// </summary>
            public Role Role { get; set; }
            /// <summary>
            /// 录入时间
            /// </summary>
            public DateTime? CreatedTime { get; set; }
        }
    }
  2. Mappers文件夹下添加Mapper类如下:
    mappers
    using CloudEntity.Mapping;
    using CloudEntity.Mapping.Common;
    using PingGuo.Blog.Test.Entities;
    
    namespace PingGuo.Blog.Test.Mappers
    {
        /// <summary>
        /// 角色的Mapper类
        /// </summary>
        internal class RoleMapper : TableMapper<Role>
        {
            /// <summary>
            /// 获取映射的Table信息
            /// </summary>
            /// <returns>映射的Table信息</returns>
            protected override ITableHeader GetHeader()
            {
                return base.GetHeader("Sys_Roles");
            }
            /// <summary>
            /// 设置属性映射
            /// </summary>
            /// <param name="setter">属性映射设置对象</param>
            protected override void SetColumnMappers(IColumnMapSetter<Role> setter)
            {
                setter.Map(r => r.RoleId, ColumnAction.PrimaryAndInsert).Length(36);
                setter.Map(r => r.RoleName, allowNull: false).Length(25);
                setter.Map(r => r.CreatedTime, ColumnAction.Default);
            }
        }
    }
    using CloudEntity.Mapping;
    using CloudEntity.Mapping.Common;
    using PingGuo.Blog.Test.Entities;
    
    namespace PingGuo.Blog.Test.Mappers
    {
        /// <summary>
        /// 用户的Mapper类
        /// </summary>
        internal class UserMapper : TableMapper<User>
        {
            /// <summary>
            /// 获取映射的Table信息
            /// </summary>
            /// <returns>映射的Table信息</returns>
            protected override ITableHeader GetHeader()
            {
                // 指定表名
                return base.GetHeader("Sys_Users");
            }
            /// <summary>
            /// 设置属性映射
            /// </summary>
            /// <param name="setter">属性映射设置对象</param>
            protected override void SetColumnMappers(IColumnMapSetter<User> setter)
            {
                setter.Map(u => u.UserId, ColumnAction.PrimaryAndInsert).Length(36);
                setter.Map(u => u.UserName, allowNull: false).Length(25);
                setter.Map(u => u.RoleId, ColumnAction.Insert).Length(36);
                setter.Map(u => u.CreatedTime, ColumnAction.Default);
            }
        }
    }
  3. Mappers文件夹下创建一个Mapper容器类:
    using CloudEntity.Mapping;
    using CloudEntity.Mapping.Common;
    
    namespace PingGuo.Blog.Test.Mappers
    {
        /// <summary>
        /// Mapper对象容器类
        /// </summary>
        internal class InnerMapperContainer : MapperContainerBase
        {
            /// <summary>
            /// 根据实体类型创建对应的Mapper对象
            /// </summary>
            /// <param name="entityType">实体类型</param>
            /// <returns>对应的Mapper对象</returns>
            protected override ITableMapper CreateTableMapper(Type entityType)
            {
                // Mapper类型全名
                // 假设这里的实体类型为:PingGuo.Blog.Test.Entities.Role
                // 那么如下方法获到的Mapper对象类型则为:PingGuo.Blog.Test.Mappers.RoleMapper
                string targetNameSpace = entityType.Namespace.Replace("Entities", "Mappers");
                string targetMapperTypeName = string.Format("{0}.{1}Mapper", targetNameSpace, entityType.Name);
                // 创建Mapper对象
                return Activator.CreateInstance(Type.GetType(targetMapperTypeName)) as ITableMapper;
            }
        }
    }

到这里,创建实体类,配置映射工作基本完成

编写数据库驱动

这里以操作MySQL为例子,编写用于操作MySQL的数据库驱动。在文件夹MySqlClient新建如下类:
MySqlClient

  • 操作MySQL数据库的DbHelper类:MySqlHelper,具体如下:
    using CloudEntity.Data;
    using MySql.Data.MySqlClient;
    using System.Data;
    
    namespace PingGuo.Blog.Test.MySqlClient
    {
        /// <summary>
        /// 操作MySql数据库的DbHelper类
        /// </summary>
        internal class MySqlHelper : DbHelper
        {
            /// <summary>
            /// 获取数据库连接
            /// </summary>
            /// <param name="connectionString">连接字符串</param>
            /// <returns>数据库连接</returns>
            /// <exception cref="NotImplementedException"></exception>
            protected override IDbConnection Connect(string connectionString)
            {
                return new MySqlConnection(connectionString);
            }
            /// <summary>
            /// 获取数据适配器
            /// </summary>
            /// <returns>数据适配器</returns>
            protected override IDbDataAdapter CreateDataAdapter()
            {
                return new MySqlDataAdapter();
            }
            /// <summary>
            /// 记录所有执行的sql命令
            /// </summary>
            /// <param name="commandText">sql命令</param>
            protected override void RecordCommand(string commandText)
            {
                Console.WriteLine("================");
                Console.WriteLine(commandText);
                Console.WriteLine("================");
            }
    
            /// <summary>
            /// 初始化(pingguo_blog是通用的表名前缀,相当于Sql Server下的dbo)
            /// </summary>
            /// <param name="connectionString">连接字符串</param>
            public MySqlHelper(string connectionString)
                : base(connectionString, "pingguo_blog") { }
            /// <summary>
            /// 创建sql参数
            /// </summary>
            /// <returns>sql参数</returns>
            public override IDbDataParameter CreateParameter()
            {
                return new MySqlParameter();
            }
        }
    }
  • 用于创建表的Table初始化器类:
    using CloudEntity.CommandTrees;
    using CloudEntity.Core.Data.Entity;
    using CloudEntity.Data;
    using CloudEntity.Mapping;
    using System.Data;
    using System.Text;
    
    namespace PingGuo.Blog.Test.MySqlClient
    {
        /// <summary>
        /// MySql的表初始化类(Code First时使用)
        /// </summary>
        internal class MySqlTableInitializer : TableInitializer
        {
            /// <summary>
            /// 初始化
            /// </summary>
            /// <param name="dbHelper">数据库操作对象</param>
            /// <param name="commandFactory">SQL命令工厂</param>
            public MySqlTableInitializer(IDbHelper dbHelper, ICommandFactory commandFactory)
                : base(dbHelper, commandFactory) { }
            /// <summary>
            /// 检查表是否存在
            /// </summary>
            /// <param name="dbHelper">操作数据库的DbHelper对象</param>
            /// <param name="tableHeader">table信息</param>
            /// <returns>当前table是否存在</returns>
            public override bool IsExist(ITableHeader tableHeader)
            {
                // 获取数据库架构名(表名前缀)
                string schemaName = tableHeader.SchemaName ?? base.DbHelper.DefaultSchemaName;
                //获取sql参数数组
                IList<IDbDataParameter> parameters = new List<IDbDataParameter>
                {
                    base.DbHelper.CreateParameter("TableName", tableHeader.TableName)
                };
                //初始化sql命令
                StringBuilder commandText = new StringBuilder();
                commandText.AppendLine("SELECT COUNT(*)");
                commandText.AppendLine("  FROM information_schema.Tables t");
                commandText.AppendLine(" WHERE t.Table_Name = @TableName");
                //若有架构名则带上架构名
                if (!string.IsNullOrEmpty(schemaName))
                {
                    commandText.AppendLine("   AND t.Table_Schema = @SchemaName");
                    parameters.Add(base.DbHelper.CreateParameter("SchemaName", schemaName));
                }
                //执行获取结果
                int result = Convert.ToInt32(base.DbHelper.GetScalar(commandText.ToString(), parameters: parameters.ToArray()));
                return result > 0;
            }
        }
    }
  • 用于新增列的Column初始化器类:
    using CloudEntity.CommandTrees;
    using CloudEntity.Core.Data.Entity;
    using CloudEntity.Data;
    using CloudEntity.Mapping;
    using System.Data;
    using System.Text;
    
    namespace PingGuo.Blog.Test.MySqlClient
    {
        /// <summary>
        /// MySql列初始化器(Code First时使用,检查列是否完整,列不完整添加列)
        /// </summary>
        internal class MySqlColumnInitializer : ColumnInitializer
        {
            /// <summary>
            /// 获取当前表下所有的列
            /// </summary>
            /// <param name="tableHeader">表的基本信息对象</param>
            /// <returns>当前表下所有的列</returns>
            protected override IEnumerable<string> GetColumns(ITableHeader tableHeader)
            {
                // 获取数据库架构名(表名前缀)
                string schemaName = tableHeader.SchemaName ?? base.DbHelper.DefaultSchemaName;
                //初始化sql参数数组
                IList<IDbDataParameter> parameters = new List<IDbDataParameter>
                {
                    base.DbHelper.CreateParameter("TableName", tableHeader.TableName)
                };
                //初始化sql
                StringBuilder sqlBuilder = new StringBuilder();
                sqlBuilder.AppendLine("SELECT c.COLUMN_NAME");
                sqlBuilder.AppendLine("  FROM information_schema.COLUMNS c");
                sqlBuilder.AppendLine(" WHERE c.TABLE_NAME = @TableName");
                //若有TABLE_SCHEMA查询条件则带上
                if (!string.IsNullOrEmpty(schemaName))
                {
                    sqlBuilder.AppendLine("   AND c.TABLE_SCHEMA = @SchemaName");
                    parameters.Add(base.DbHelper.CreateParameter("SchemaName", schemaName));
                }
                //执行查询获取所有列
                return base.DbHelper.GetResults(reader => reader.GetString(0), sqlBuilder.ToString(), parameters: parameters.ToArray());
            }
    
            /// <summary>
            /// 初始化
            /// </summary>
            /// <param name="dbHelper">数据库操作对象</param>
            /// <param name="commandFactory">SQL命令工厂</param>
            public MySqlColumnInitializer(IDbHelper dbHelper, ICommandFactory commandFactory)
                : base(dbHelper, commandFactory) { }
        }
    }
  • 用户初始化数据容器的数据库初始化类:
    using CloudEntity.CommandTrees;
    using CloudEntity.CommandTrees.Commom.MySqlClient;
    using CloudEntity.Core.Data.Entity;
    using CloudEntity.Data;
    using CloudEntity.Mapping;
    using PingGuo.Blog.Test.Mappers;
    
    namespace PingGuo.Blog.Test.MySqlClient
    {
        /// <summary>
        /// 访问MySql数据库的初始化器
        /// </summary>
        internal class MySqlInitializer : DbInitializer
        {
            /// <summary>
            /// 创建SQL命令工厂
            /// </summary>
            /// <returns>SQL命令工厂</returns>
            public override ICommandFactory CreateCommandFactory()
            {
                return new MySqlCommandFactory();
            }
            /// <summary>
            /// 创建Mapper容器
            /// </summary>
            /// <returns>Mapper容器</returns>
            public override IMapperContainer CreateMapperContainer()
            {
                return new InnerMapperContainer();
            }
            /// <summary>
            /// 创建操作数据库的DbHelper
            /// </summary>
            /// <param name="connectionString">连接字符串</param>
            /// <returns>操作数据库的DbHelper对象</returns>
            public override DbHelper CreateDbHelper(string connectionString)
            {
                return new MySqlHelper(connectionString);
            }
            /// <summary>
            /// 创建Table初始化器(Code First时需要使用)
            /// </summary>
            /// <param name="dbHelper">数据库操作对象</param>
            /// <param name="commandFactory">SQL命令工厂</param>
            /// <returns>Table初始化器</returns>
            public override TableInitializer CreateTableInitializer(IDbHelper dbHelper, ICommandFactory commandFactory)
            {
                return new MySqlTableInitializer(dbHelper, commandFactory);
            }
            /// <summary>
            /// 获取Column初始化器(Code First时需要使用)
            /// </summary>
            /// <param name="dbHelper">数据库操作对象</param>
            /// <param name="commandFactory">SQL命令工厂</param>
            /// <returns>Column初始化器</returns>
            public override ColumnInitializer CreateColumnInitializer(IDbHelper dbHelper, ICommandFactory commandFactory)
            {
                return new MySqlColumnInitializer(dbHelper, commandFactory);
            }
        }
    }

至此,实体框架CloudEntityMySQL驱动编写完成。
有些细心的人可能已经注意到了,要想让它支持所有能够通过ADO.Net访问的数据库,关键在于用于生成SQL的SQL命令工厂接口ICommandFactory,除了本次使用的MySQL,我在CloudEntity只内置了SQL Server OraclePostgresql的SQL命令工厂类,具体如下:

命名空间 类名 描述
CloudEntity.CommandTrees.Commom.MySqlClient MySqlCommandFactory 用于生成针对MySQLSQL命令的工厂类
CloudEntity.CommandTrees.Commom.SqlClient SqlCommandFactory 用于生成针对SQL ServerSQL命令的工厂类
CloudEntity.CommandTrees.Commom.OracleClient OracleCommandFactory 用于生成针对OracleSQL命令的工厂类
CloudEntity.CommandTrees.Commom.PostgreSqlClient PostgreSqlCommandFactory 用于生成针对PostgresqlSQL命令的工厂类

因此,要想支持其他数据库,除了上面配置的数据库驱动类,还要自己写用于生成SQL的SQL命令工厂,有兴趣的可以自己看CloudEntity源码

首次使用CloudEntity操作数据库

OK,下面开始正式操作数据库。

  1. 修改Program类,初始化数据容器,初始化表,具体如下:
    using CloudEntity.Core.Data.Entity;
    using CloudEntity.Data.Entity;
    using PingGuo.Blog.Test.Entities;
    using PingGuo.Blog.Test.MySqlClient;
    
    public static class Program
    {
        /// <summary>
        /// 连接字符串
        /// </summary>
        private static string _connectionString;
    
        /// <summary>
        /// 静态初始化
        /// </summary>
        static Program()
        {
            // 获取连接字符串
            _connectionString = "Server=localhost;User Id=root;Password=000000;Database=pingguo_blog;";
            // 初始化数据容器
            DbContainer.Init<MySqlInitializer>(_connectionString);
        }
        /// <summary>
        /// 初始化表(表不存在创建表,缺少列则添加列)
        /// </summary>
        private static void InitTables()
        {
            // 获取数据容器
            IDbContainer container = DbContainer.Get(_connectionString);
            // 初始化角色表
            container.InitTable<Role>();
            // 初始化用户表
            container.InitTable<User>();
        }
        /// <summary>
        /// 程序启动时执行的方法
        /// </summary>
        private static void Main()
        {
            // 初始化表
            Program.InitTables();
        }
    }
    好了,按下Ctrl + F5,开始创建表,会打印创建表的sql,执行结果如下:
    ================
    SELECT COUNT(*)
      FROM information_schema.Tables t
     WHERE t.Table_Name = @TableName
       AND t.Table_Schema = @SchemaName
    
    ================
    ================
    CREATE TABLE pingguo_blog.`Sys_Roles`
    (
            `RoleId`        VARCHAR(36)     PRIMARY KEY,
            `RoleName`      VARCHAR(25)     NOT NULL,
            `CreatedTime`   DATETIME        DEFAULT NOW()   NULL
    )
    
    ================
    ================
    SELECT COUNT(*)
      FROM information_schema.Tables t
     WHERE t.Table_Name = @TableName
       AND t.Table_Schema = @SchemaName
    
    ================
    ================
    CREATE TABLE pingguo_blog.`Sys_Users`
    (
            `UserId`        VARCHAR(36)     PRIMARY KEY,
            `UserName`      VARCHAR(25)     NOT NULL,
            `RoleId`        VARCHAR(36)     NOT NULL,
            `CreatedTime`   DATETIME        DEFAULT NOW()   NULL
    )
    
    ================
    
    这里,先来分析下关键代码:
    1. 初始化数据容器(只需在程序启动时执行一次即可):
      // 获取连接字符串
      _connectionString = "Server=localhost;User Id=root;Password=000000;Database=pingguo_blog;";
      // 初始化数据容器
      DbContainer.Init<MySqlInitializer>(_connectionString);
    2. 后面操作数据库时,先获取数据容器:
      // 获取数据容器
      IDbContainer container = DbContainer.Get(_connectionString);
    3. 然后才是,执行其他操作,如初始化表(没有表,创建表,缺少列,添加列):
      // 初始化角色表
      container.InitTable<Role>();
      // 初始化用户表
      container.InitTable<User>();
  2. 删除表
    1. 添加方法DropTables,如下:
      /// <summary>
      /// 删除所有表
      /// </summary>
      private static void DropTables()
      {
          // 获取数据容器
          IDbContainer container = DbContainer.Get(_connectionString);
          // 删除角色表
          container.DropTable<Role>();
          // 删除用户表
          container.DropTable<User>();
      }
    2. 修改Main方法,调用方法DropTables,如下:
      /// <summary>
      /// 程序启动时执行的方法
      /// </summary>
      private static void Main()
      {
         // 删除表
         Program.DropTables();
      }
    3. 好了,按下Ctrl + F5开始执行,会打印出删除表的SQL,具体如下:
      ================
      SELECT COUNT(*)
        FROM information_schema.Tables t
       WHERE t.Table_Name = @TableName
      
      ================
      ================
      DROP TABLE pingguo_blog.`Sys_Roles`
      ================
      ================
      SELECT COUNT(*)
        FROM information_schema.Tables t
       WHERE t.Table_Name = @TableName
      
      ================
      ================
      DROP TABLE pingguo_blog.`Sys_Users`
      ================
      
  3. 以上步骤都试过后,重新创建表,下面添加测试数据
    1. 添加方法InsertData,如下:
      /// <summary>
      /// 添加测试数据
      /// </summary>
      private static void InsertData()
      {
          // 创建一个角色
          Role role = new Role()
          {
              RoleId = Guid.NewGuid().ToString(),
              RoleName = "管理员"
          };
          // 创建多个User
          User[] users = new User[]
          {
              new User()
              {
                  UserId = Guid.NewGuid().ToString(),
                  UserName = "apple",
                  RoleId = role.RoleId
              },
              new User()
              {
                  UserId = Guid.NewGuid().ToString(),
                  UserName = "admin",
                  RoleId = role.RoleId
              },
              new User()
              {
                  UserId = Guid.NewGuid().ToString(),
                  UserName = "bob",
                  RoleId = role.RoleId
              }
          };
          // 获取数据容器
          IDbContainer container = DbContainer.Get(_connectionString);
          // 添加角色
          container.List<Role>().Add(role);
          // 添加所有的用户
          foreach (User user in users)
          {
              container.List<User>().Add(user);
          }
      }
    2. 修改Main方法,调用方法InsertData,如下:
      /// <summary>
      /// 程序启动时执行的方法
      /// </summary>
      private static void Main()
      {
          // 添加测试数据
          Program.InsertData();
      }
    3. 按下Ctrl + F5开始执行,会打印出添加数据的SQL,具体如下:
      ================
      INSERT INTO pingguo_blog.`Sys_Roles`
                  (`RoleId`,
                   `RoleName`)
           VALUES (@RoleId,
                   @RoleName)
      ================
      ================
      INSERT INTO pingguo_blog.`Sys_Users`
                  (`UserId`,
                   `UserName`,
                   `RoleId`)
           VALUES (@UserId,
                   @UserName,
                   @RoleId)
      ================
      ================
      INSERT INTO pingguo_blog.`Sys_Users`
                  (`UserId`,
                   `UserName`,
                   `RoleId`)
           VALUES (@UserId,
                   @UserName,
                   @RoleId)
      ================
      ================
      INSERT INTO pingguo_blog.`Sys_Users`
                  (`UserId`,
                   `UserName`,
                   `RoleId`)
           VALUES (@UserId,
                   @UserName,
                   @RoleId)
      ================
      
  4. 查询测试
    1. 添加打印用户信息方法PrintUsers
      /// <summary>
      /// 打印用户列表
      /// </summary>
      /// <param name="users">用户列表</param>
      private static void PrintUsers(IEnumerable<User> users)
      {
          foreach (User user in users)
          {
              Console.WriteLine("{0} {1}", user.Role.RoleName, user.UserName);
          }
      }
    2. 添加查询方法Query如下:
      /// <summary>
      /// 查询测试
      /// </summary>
      private static void Query()
      {
          // 获取数据容器
          IDbContainer container = DbContainer.Get(_connectionString);
          // 构建角色查询数据源
          IDbQuery<Role> roles = container.CreateQuery<Role>()
              // 并设置只查询RoleId 和 RoleName
              .SetIncludeBy(r => new { r.RoleId, r.RoleName })
              // 设置进一步检索角色名称不为空的角色数据
              .SetIsNull(r => r.RoleName, false)
              // 设置按排序时间倒序排序
              .SetSortBy(r => r.CreatedTime, true);
          // 构建用户查询数据源
          IDbQuery<User> users = container.CreateQuery<User>()
              // 并设置只查询UserId 和 UserName(不设置则查询所有)
              .SetIncludeBy(u => new { u.UserId, u.UserName })
              // 并按RoleId关联角色查询数据源
              .SetJoin(roles, u => u.Role, (u, r) => u.RoleId == r.RoleId)
              // 设置进一步检索在某时间段内录入的用户数据
              .SetBetween(u => u.CreatedTime, DateTime.Parse("2023/01/01"), DateTime.Now);
          // 打印用户列表
          Program.PrintUsers(users);
      }
    3. 修改Main方法,调用方法Query,如下:
      /// <summary>
      /// 程序启动时执行的方法
      /// </summary>
      private static void Main()
      {
          // 查询测试
          Program.Query();
      }
    4. 按下Ctrl + F5开始执行,在打印用户信息之前,会先打印生成的查询SQL,具体如下:
      ================
          SELECT `user`.`UserId`,
                 `user`.`UserName`,
                 `role`.`RoleId`,
                 `role`.`RoleName`
            FROM pingguo_blog.`Sys_Users` `user`
      INNER JOIN pingguo_blog.`Sys_Roles` `role`
              ON `user`.`RoleId` = `role`.`RoleId`
           WHERE `role`.`RoleName` IS NOT NULL
             AND `user`.`CreatedTime` BETWEEN @CreatedTime0 AND @CreatedTime1
      ================
      管理员 admin
      管理员 bob
      管理员 apple
      
  5. 好了,本章到这里就结束了,有兴趣的小伙伴可以把上一章简介里面的查询测试都试下。
⚠️ **GitHub.com Fallback** ⚠️