Skip to content
This repository was archived by the owner on Jun 28, 2023. It is now read-only.

Latest commit

 

History

History
483 lines (379 loc) · 20.3 KB

CodeArts.Db.Lts.md

File metadata and controls

483 lines (379 loc) · 20.3 KB

CodeArts

"CodeArts.Db.Lts"是什么?

CodeArts.Db.Lts 是基于Linq分析实现的、支持分表和读写分离的轻量级ORM框架。

使用方式:

  • 定义实体。

    [DbConfig] // 设置数据库连接(不分读写)。
    // [DbReadConfig] // 设置只读连接。
    // [DbWriteConfig] // 设置读写连接。
    [Naming(NamingType.UrlCase, Name = "yep_users")] // 指定整个实体的字段格式,指定当前实体映射表名称。
    public class User : BaseEntity<int> // 必须继承IEntity接口或实现了IEntity接口的类。
    {
        [Key] // 指定主键(用户操作更新和删除)
        [Naming("uid")] // 字段映射,将字段Id和数据库user字段添加映射关系。
        [ReadOnly(true)] //指定字段只读,字段值由数据库生成,不被插入或更新。
        public override int Id { get => base.Id; set => base.Id = value; }
    
        [Required] //设置字段必填
        [Display(Name = "用户名")] // 遵循 System.ComponentModel.DataAnnotations.ValidationAttribute 约束
        public string Username { get; set; }
    
        [Naming(NamingType.CamelCase)] // 如果字段特殊,可单独为字段设置字段命名规则。
        [DateTimeToken] // 设置Token键(在更新时,将属性数据作为更新条件,并为属性创建新值;在插入时,如若属性值为类型默认值,会自动获取新值)
        public DateTime PasswordLastChanged { get; set; }
    
        [Ignore]// 忽略,不会被生成SQL操作的字段。
        public bool HasValue => Id > 0;
    }
  • 定义仓库:无特殊业务操作时,可不用定义仓库,可注入默认实现。

    • 只读仓库。

      [DbReadConfig] // (1)设置数据库连接。。
      // [DbConfig] // 设置数据库连接(不限读写)。
      public class UserRepository : Repository<User/* 表映射实体 */>
      {
          protected override ConnectionConfig GetDbConfig() => base.GetDbConfig(); // (2)设置数据库连接(会忽略(1)的配置)。
      }
    • 读写仓储。

      [DbWriteConfig] // (1)设置数据库连接。
      // [DbConfig] // 设置数据库连接(不限读写)。
      public class UserRepository : DbRepository<User/* 表映射实体 */>
      {
          protected override ConnectionConfig GetDbConfig() => new ConnectionConfig // (2)设置数据库连接(会忽略(1)的配置)。
          {
              Name = "yep.v3.invoice",
              ProviderName = "MySql",
              ConnectionString = "connectionString"
          };
      }
  • 设置数据库适配器。

    DbConnectionManager.RegisterAdapter(new SqlServerLtsAdapter());
    DbConnectionManager.RegisterDatabaseFor<DapperFor>();
  • UML

    UML

如何定义全局的 System.ComponentModel.DataAnnotations.ValidationAttribute 异常消息?

DbValidator.CustomValidate<RequiredAttribute>((attr, context) =>
{
    if (attr.AllowEmptyStrings)
    {
        return $"{context.DisplayName}不能为空!";
    }

    return $"{context.DisplayName}不能为null!";
});

特殊语法。

/// <summary>
/// 用户实体。
/// </summary>
[Naming(NamingType.UrlCase, Name = "fei_users")]
public class FeiUsers : BaseEntity<int>
{
    /// <summary>
    /// 用户ID。
    /// </summary>
    [Key]
    [Naming("uid")] // 真实的数据库字段。
    [ReadOnly(true)]
    public override int Id { get; set; }

    /// <summary>
    /// 公司ID。
    /// </summary>
    public int Bcid { get; set; }

    /// <summary>
    /// 用户名。
    /// </summary>
    public string Username { get; set; }

    /// <summary>
    /// 邮箱。
    /// </summary>
    [Required]
    [EmailAddress]
    public string Email { get; set; }

    /// <summary>
    /// 电话。
    /// </summary>
    public string Mobile { get; set; }

    /// <summary>
    /// 密码。
    /// </summary>
    [Required]
    public string Password { get; set; }

    /// <summary>
    /// 角色组。
    /// </summary>
    public short Mallagid { get; set; }

    /// <summary>
    /// 盐。
    /// </summary>
    public string Salt { get; set; }

    /// <summary>
    /// 状态。
    /// </summary>
    public int? Userstatus { get; set; }

    /// <summary>
    /// 创建时间。
    /// </summary>
    public DateTime CreatedTime { get; set; }

    /// <summary>
    /// 修改时间。
    /// </summary>
    [DateTimeToken] // Token 在更新时,始终作为更新条件,以达到数据幂等。
    public DateTime ModifiedTime { get; set; }
}
  • 更新。

    • 数据库路由更新。

      await user
          .From(x => x.TableName)
          .Where(x => x.Username == "admin")
          .UpdateAsync(x => new FeiUsers
          {
                Mallagid = 2,
                Username = x.Username.Substring(0, 4) // 数据库字段幂等更新。
          });
      UPDATE [x] SET [mallagid]=2,[username]= CASE WHEN [x].[username] IS NULL THEN NULL WHEN LEN([x].[username]) < 4 THEN @__variable_2 ELSE SUBSTRING([x].[username],1,4) END,[modified_time]=@__variable_3 FROM [fei_users] [x] WHERE ([x].[username] = @__variable_1)
      • @__variable_1:"admin"
      • @__variable_2:string.Empty
      • @__variable_3:字段ModifiedTimeDateTimeToken标记生成的值。
    • 指定更新。

      var entry = new FeiUsers
      {
          Id = 1011,
          Bcid = 0,
          Userstatus = 1,
          Mobile = "18980861011",
          Email = "[email protected]",
          Password = "123456",
          Salt = string.Empty,
          CreatedTime = DateTime.Now,
          ModifiedTime = DateTime.Now
      };
      await user.AsUpdateable(entry) // 主键始终会作为更新条件。
          .Limit(x => x.Password) // 指定更新字段。
          .Where(x => x.Mobile) // 使用 Mobile 作为条件。
          .ExecuteCommandAsync();
      UPDATE [fei_users] SET [password]=@password,[modified_time]=@__token_modified_time WHERE [uid]=@id AND [mobile]=@mobile AND [modified_time]=@modified_time;
      • 支持批量更新。
      • @id:主键条件始终生效。
      • @password:实体的Password属性值。
      • @__token_modified_time:字段ModifiedTimeDateTimeToken标记生成的值。
      • @username/@mobile:实体的Username属性值为null时,生成【mobile】字段条件,否则生成【username】字段条件。
      • @modified_time:字段ModifiedTime的传入值。
  • 插入。

    • 数据库路由插入。

      await user.InsertAsync(details.Take(10).Select(x => new FeiUsers
      {
      	Username = x.Nickname,
      	Mobile = "18980861011",
      	Email = "[email protected]",
      	Password = "123456",
      	Salt = string.Empty
      }))
      INSERT INTO [fei_users]([username],[mobile],[email],[password],[salt])
      SELECT TOP (10) [x].[nickname],@__variable_1,@__variable_2,@__variable_3,@__variable_empty FROM [fei_userdetails] [x]
      • 只生成指定的字段。
    • 指定插入。

      var list = new List<FeiUsers>(10);
      
      for (int i = 0; i < 10; i++)
      {
          list.Add(new FeiUsers
          {
              Bcid = 0,
              Username = "admin",
              Userstatus = 1,
              Mobile = "18980861011",
              Email = "[email protected]",
              Password = "123456",
              Salt = string.Empty,
              CreatedTime = DateTime.Now,
              ModifiedTime = DateTime.Now
          });
      }
      await user.AsInsertable(list)
          .UseTransaction() // 启用事务。
          .ExecuteCommandAsync();
      INSERT INTO [fei_users]([bcid],[username],[email],[mobile],[password],[mallagid],[salt],[userstatus],[created_time],[modified_time]) VALUES (@bcid,@username,@email,@mobile,@password,@mallagid,@salt,@userstatus,@created_time,@modified_time),(@bcid_1,@username_1,@email_1,@mobile_1,@password_1,@mallagid_1,@salt_1,@userstatus_1,@created_time_1,@modified_time_1),(@bcid_2,@username_2,@email_2,@mobile_2,@password_2,@mallagid_2,@salt_2,@userstatus_2,@created_time_2,@modified_time_2),(@bcid_3,@username_3,@email_3,@mobile_3,@password_3,@mallagid_3,@salt_3,@userstatus_3,@created_time_3,@modified_time_3),(@bcid_4,@username_4,@email_4,@mobile_4,@password_4,@mallagid_4,@salt_4,@userstatus_4,@created_time_4,@modified_time_4),(@bcid_5,@username_5,@email_5,@mobile_5,@password_5,@mallagid_5,@salt_5,@userstatus_5,@created_time_5,@modified_time_5),(@bcid_6,@username_6,@email_6,@mobile_6,@password_6,@mallagid_6,@salt_6,@userstatus_6,@created_time_6,@modified_time_6),(@bcid_7,@username_7,@email_7,@mobile_7,@password_7,@mallagid_7,@salt_7,@userstatus_7,@created_time_7,@modified_time_7),(@bcid_8,@username_8,@email_8,@mobile_8,@password_8,@mallagid_8,@salt_8,@userstatus_8,@created_time_8,@modified_time_8),(@bcid_9,@username_9,@email_9,@mobile_9,@password_9,@mallagid_9,@salt_9,@userstatus_9,@created_time_9,@modified_time_9)
      • 只读主键,代表由数据库生成,不在SQL中体现。
  • 批量删除。

    • 数据库路由删除。

      string username = "admi";
      await user.DeleteAsync(x => x.Username == username);
      DELETE [x] FROM [fei_users] [x] WHERE ([x].[username] = @username)
      • @username:参数名称优先使用变量名称、参数名称或常量名称。
    • 指定删除。

      var entry = new FeiUsers
      {
          Id = 1011
      };
      await user.AsDeleteable(entry)
          .ExecuteCommandAsync()
      DELETE FROM [fei_users] WHERE uid=@id
      • 通过主键删除数据。
      • 支持批量删除。

乐观锁。

​ CA、CB客户查阅并修改同一份数据,CA更新数据{A:"123",B:true},然后CB更新数据{B:true,C:100},解决CA更新的{A:"123"}被还原的问题。

​ 乐观锁的原理,是利用实体某字段。如:long Version,前端更新时,将查询接口返回的Version传回后端,更新时,ORM会自动将Version作为更新条件,并刷新Version值。更新失败时,返回影响行为0

​ 实体可以有任意多个乐观锁属性,每次更新,属性都会被作为更新条件,在属性前标记特性:TokenAttribute的实现类即可。

/// <summary>
/// 令牌(作为更新数据库的唯一标识,会体现在更新的条件语句中)。
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public abstract class TokenAttribute : Attribute
{
    /// <summary>
    /// 创建新令牌。
    /// </summary>
    /// <returns></returns>
    public abstract object Create();
}

/// <summary>
/// 时间戳令牌。
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class DateTimeTicksAttribute : TokenAttribute
{
    public override object Create() => DateTime.Now.Ticks;
}

实用于指定更新方式,数据库路由也会更新令牌属性

CRUD(异步支持:.NET 45+|.NETSTANDARD2_0+)。

数据库路由:无需查询,生成执行语句,在数据库中完成操作。

公共方法。
方法 返回值 参数 描述
From IDRepository<TEntity> Func<ITableInfo, string> 指定插入的表名称,默认:实体分析的名称。
TimeOut IDRepository<TEntity> int 设置当前命令执行超时时间。
新增。
方法 返回值 参数 描述
Insert int IQueryable<TEntity> 执行插入命令,返回影响行。

例如:

await user.InsertAsync(userEx
                 .Where(x=>x.Id > 10000 && x.Id < 1101)
                 .Select(x=> new User {
    				Id = x.UserId,
                    Name = x.UserName,
                    Version = x.Version + 10000,
                    Timestamp = DateTime.Now.Ticks
                 }).Skip(100).Take(50)
    );
查询:支持linq语法。
修改。
方法 返回值 参数 描述
Where IDRepository<TEntity> Expression<Func<TEntity, bool>> 限定更新范围。
Update int Expression<Func<TEntity, TEntity>> 指定更新字段和值,并执行更新命令,返回影响行。

例如1

await user.Where(x=>x.Id == 1101) // 更新条件。
    .UpdateAsync(x=>new User { Name = "影子和树", Version = x.Version + 1, Timestamp = DateTime.Now.Ticks });
删除。
方法 返回值 参数 描述
Where IDRepository<TEntity> Expression<Func<TEntity, bool>> 限定删除范围。
Delete int 执行删除命令。
Delete int Expression<Func<TEntity, bool>> 限定删除范围,并执行删除命令,返回影响行。

例如:

await user.DeleteAsync(x=>x.Id == 1101);

指定实体:主键始终为条件,额外指定的条件作为幂等条件。

公共方法。
方法 返回值 参数 描述
WatchSql ICommandByCommit<TEntity> Action<CommandSql> 执行语句监视。
Transaction ICommandByCommit<TEntity> 默认事务级别提交。
Transaction ICommandByCommit<TEntity> IsolationLevel 指定事务级别提交。
ExecuteCommand int [int?] 执行命令,返回影响行。
插入。
方法 返回值 参数 描述
AsInsertable IInsertable<TEntity> TEntity|TEntity[]|List<TEntity> 通过IDbRepository<TEntity>获得指定插入能力。
Into IInsertable<TEntity> Func<ITableInfo, string> 指定插入表名称,默认:实体分析的名称。
Into IInsertable<TEntity> Func<ITableInfo,TEntity, string> 指定特定规则实体的更新表名称,默认:实体分析的名称。
Limit IInsertable<TEntity> string[]|Expression<Func<TEntity, TColumn>> 指定字段插入。
Except IInsertable<TEntity> string[]|Expression<Func<TEntity, TColumn>> 排除字段插入。

例如2

user.AsInsertable(new User { Id = 1011, Name = "影子和树", Version = 1, Timestamp = DateTime.Now.Ticks})
    .Limit(x=>new { x.Id, x.Name, x.Timestamp }) // 指定插入的字段。
    //.Except(x=> x.Id) // 不插入的字段。
    .ExecuteCommand();
修改。
方法 返回值 参数 描述
AsUpdateable IUpdateable<TEntity> TEntity|TEntity[]|List<TEntity> 通过IDbRepository<TEntity>获得指定更新能力。
SkipIdempotentValid IUpdateable<TEntity> 跳过幂等验证。
Table IUpdateable<TEntity> Func<ITableInfo, string> 指定更新表名称,默认:实体分析的名称。
Table IUpdateable<TEntity> Func<ITableInfo,TEntity, string> 指定特定规则实体的更新表名称,默认:实体分析的名称。
Where IUpdateable<TEntity> string[]|Expression<Func<TEntity, T>> 指定更新条件字段。
Set IUpdateable<TEntity> string[]|Expression<Func<TEntity, T>> 指定字段插入。
SetExcept IUpdateable<TEntity> string[]|Expression<Func<TEntity, T>> 排除字段插入。

例如3

await user.AsUpdateable(new User { Id = 1011, Name = "影子和树", Version = 1, Timestamp = DateTime.Now.Ticks})
    .SetExcept(x=> new {x.Id, x.Name})
    .ExecuteCommandAsync();
删除。
方法 返回值 参数 描述
AsDeleteable IDeleteable<TEntity> TEntity|TEntity[]|List<TEntity> 通过IDbRepository<TEntity>获得指定删除能力。
SkipIdempotentValid IDeleteable<TEntity> 跳过幂等验证。
From IDeleteable<TEntity> Func<ITableInfo, string> 指定更新表名称,默认:实体分析的名称。
Where IDeleteable<TEntity> string[]|Expression<Func<TEntity, TColumn>> 指定删除条件字段。

例如:

await user.AsDeleteable(new User { Id = 1011, Name = "影子和树", Version = 1, Timestamp = DateTime.Now.Ticks})
    .ExecuteCommandAsync();

说明:

  • 参数分析,当实体属性为值类型,且不为Nullable类型时,比较的参数为NULL时,自动忽略该条件。
  • 由于“Take”和“Skip”参数的特殊性,在生成SQL语句时,将直接生成到SQL语句中,不会向参数字典中添加KeyValue。
  • 支持Linq表达式。
  • 支持几乎所有的Linq使用场景(可能会有函数顺序上的优化,目的是让翻译出来的SQL更加简单高效)。
  • 支持特殊函数扩展(自定义“ICustomVisitor”接口,添加到适配器中即可)。
  • 使用“SqlCapture”可捕获范围内分析的SQL语句以及参数。
  • 支持大多数常见的字符串属性和函数,以及可空的类型支持。有关详细信息,请参阅单元测试。
  • 详情请查阅:"CodeArts.Db.Linq2Sql"是什么?

Footnotes

  1. 将Id为1101的数据,Name更新为"影子和树"、Version递增,时间戳刷新。

  2. 指定字段在排查字段中时,指定字段也会被忽略;只读字段不会被插入。

  3. 使用Id作为更新条件,且不更新Id,Name;主键或TokenAttribute标记字段,自动作为更新条件。