仓储模式
仓储模式是在业务逻辑处理和数据访问实现之间引入一个负责聚合根或实体操作的抽象隔离层,业务逻辑通过稳定接口访问数据,而不直接依赖具体ORM、表结构或SQL细节,降低业务逻辑与数据访问技术的耦合,便于统一处理事务、审计、软删、缓存等横切逻辑
使用仓储模式有以下好处
- 为不同的聚合根或实体提供统一的CRUD操作方法,减少重复代码
- 便于进行单元测试,利于以假实现或内存实现做单元测试
- 解耦业务逻辑层与数据访问层,不依赖于具体的ORM实现,后续可方便替换其他的ORM实现
- 方便进行数据库层面的AOP设计
仓储定义
接下来,本文以SqlSugar为具体的ORM实现,实现仓储模式
首先创建两个类库,名称为SugarSqlCore和SugarSqlCore.Abstraction,SugarSqlCore.Abstraction类库存放抽象类和接口,SugarSqlCore类库存放基于SqlSugar的数据库访问和操作实现类,下文以接口库指代SugarSqlCore.Abstraction类库,实现库指代SugarSqlCore,实现库项目引用接口库项目
在接口库中的程序包管理控制台执行以下命令安装SqlSugar、依赖注入、选项功能相关的Nuget包
Install-Package SqlSugarCore -v 5.1.4.175
Install-Package Microsoft.Extensions.DependencyInjection.Abstractions -v 8.0.2
Install-Package Microsoft.Extensions.Options -v 8.0.2
实体
接口库中新建基础实体接口类和对应的泛型基础实体接口类
public interface IEntity
{object? GetKeys();
}public interface IEntity<TKey> : IEntity
{TKey Id { get; }
}
实现库中新建基础实体类和对应的泛型基础实体类
public abstract class Entity : IEntity
{protected Entity(){}public abstract object? GetKeys();
}public abstract class Entity<TKey> : Entity, IEntity<TKey>
{public virtual TKey Id { get; protected set; } = default!;protected Entity(){}protected Entity(TKey id){Id = id;}public override object? GetKeys(){return Id;}
}
这里默认使用实体中Id映射为表的主键,表的主键一般使用Guid、Long,所以这里需要定义成泛型的Id
审计属性
接口库中新建创建审计接口类、更新审计接口类和删除审计接口类
public interface ICreationAuditedEntity
{Guid? CreatorId { get; set; }DateTime CreationTime { get; set; }
}public interface ICreationAuditedEntity<TKey>
{TKey? CreatorId { get; set; }DateTime CreationTime { get; set; }
}
public interface IUpdationAuditedEntity
{Guid? LastModifierId { get; set; }DateTime? LastModificationTime { get; set; }
}public interface IUpdationAuditedEntity<TKey>
{TKey? LastModifierId { get; set; }DateTime? LastModificationTime { get; set; }
}
public interface IDeletionAuditedEntity
{Guid? DeleterId { get; set; }DateTime? DeletionTime { get; set; }bool IsDeleted { get; set; }
}public interface IDeletionAuditedEntity<TKey>
{TKey? DeleterId { get; set; }DateTime? DeletionTime { get; set; }bool IsDeleted { get; set; }
}
实现库中新建创建审计类、更新审计类和删除审计类
public class CreationAuditedEntity : ICreationAuditedEntity
{public Guid? CreatorId { get; set; }public DateTime CreationTime { get; set; }
}public class CreationAuditedEntity<TKey> : ICreationAuditedEntity<TKey>
{public TKey? CreatorId { get; set; }public DateTime CreationTime { get; set; }
}
public class UpdationAuditedEntity : IUpdationAuditedEntity
{public Guid? LastModifierId { get; set; }public DateTime? LastModificationTime { get; set; }
}public class UpdationAuditedEntity<TKey> : IUpdationAuditedEntity<TKey>
{public TKey? LastModifierId { get; set; }public DateTime? LastModificationTime { get; set; }
}
public class DeletionAuditedEntity : IDeletionAuditedEntity
{public Guid? DeleterId { get; set; }public DateTime? DeletionTime { get; set; }public bool IsDeleted { get; set; }
}public class DeletionAuditedEntity<TKey> : IDeletionAuditedEntity<TKey>
{public TKey? DeleterId { get; set; }public DateTime? DeletionTime { get; set; }public bool IsDeleted { get; set; }
}
后续业务实体可以实现这些审计接口类,扩展对应的审计功能
仓储
接口库中新建仓储接口类ISugarSqlRepository,定义一些常用的数据库操作方法
public interface ISugarSqlRepository<TEntity> where TEntity : class, new()
{ISqlSugarClient SugarSqlClient { get; }ISugarQueryable<TEntity> DbQueryable { get; }ISqlSugarClient GetDbContext();ISugarQueryable<TEntity> AsQueryable();InsertNavTaskInit<TEntity, TEntity> AsInsertNav(TEntity entity);InsertNavTaskInit<TEntity, TEntity> AsInsertNav(List<TEntity> entities);UpdateNavTaskInit<TEntity, TEntity> AsUpdateNav(TEntity entity);UpdateNavTaskInit<TEntity, TEntity> AsUpdateNav(List<TEntity> entities);DeleteNavTaskInit<TEntity, TEntity> AsDeleteNav(TEntity entity);DeleteNavTaskInit<TEntity, TEntity> AsDeleteNav(List<TEntity> entities);#region 查询Task<bool> AnyAsync();Task<bool> AnyAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default);Task<TEntity?> FindIncludeAsync<TChildEntity>(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, CancellationToken cancellationToken = default) where TChildEntity : class, new();Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default);Task<TEntity?> FindAsync(dynamic id, CancellationToken cancellationToken = default);Task<TEntity> GetIncludeAsync<TChildEntity>(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, CancellationToken cancellationToken = default) where TChildEntity : class, new();Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default);Task<TEntity> GetAsync(dynamic id, CancellationToken cancellationToken = default);Task<List<TEntity>> GetListIncludeAsync<TChildEntity>(Expression<Func<TEntity, List<TChildEntity>>> includeExpression, CancellationToken cancellationToken = default) where TChildEntity : class, new();Task<List<TEntity>> GetListAsync(CancellationToken cancellationToken = default);Task<List<TEntity>> GetListIncludeAsync<TChildEntity>(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, CancellationToken cancellationToken = default) where TChildEntity : class, new();Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default);Task<int> GetCountAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default);Task<long> GetCountAsync(CancellationToken cancellationToken = default);Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default);#endregion#region 新增Task<bool> InsertAsync(TEntity entity, CancellationToken cancellationToken = default);Task<bool> InsertIncludeAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> expression, InsertNavOptions? insertNavOptions = null) where TChildEntity : class, IEntity, new();Task<int> InsertRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default);Task<int> InsertRangeIncludeAsync<TChildEntity>(List<TEntity> entities, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, InsertNavOptions? insertNavOptions = null) where TChildEntity : class, IEntity, new();Task<TEntity> InsertReturnAsync(TEntity entity, CancellationToken cancellationToken = default);Task<TEntity> InsertIncludeReturnAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> expression, InsertNavOptions? insertNavOptions = null) where TChildEntity : class, IEntity, new();Task<long> InsertReturnIdAsync(TEntity entity, CancellationToken cancellationToken = default);#endregion#region 更新Task<bool> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);Task<bool> UpdateIncludeAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> expression, UpdateNavOptions? updateNavOptions = null) where TChildEntity : class, IEntity, new();Task<bool> UpdateRangeIncludeAsync<TChildEntity>(List<TEntity> entities, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, UpdateNavOptions? updateNavOptions = null) where TChildEntity : class, IEntity, new();Task<TEntity> UpdateReturnAsync(TEntity entity, CancellationToken cancellationToken = default);Task<TEntity> UpdateIncludeReturnAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> expression, UpdateNavOptions? updateNavOptions = null) where TChildEntity : class, IEntity, new();Task<bool> UpdateRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default);#endregion#region 删除Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> expression, bool isSoftDelete = true, CancellationToken cancellationToken = default);#endregion
}public interface ISugarSqlRepository<TEntity, TKey> : ISugarSqlRepository<TEntity> where TEntity : class, new()
{#region 查询Task<TEntity?> FindByIdAsync(TKey id, CancellationToken cancellationToken = default);Task<TEntity> GetByIdAsync(TKey id, CancellationToken cancellationToken = default);#endregion#region 新增#endregion#region 更新#endregion#region 删除Task<bool> DeleteAsync(TEntity entity, bool isSoftDelete = true, CancellationToken cancellationToken = default);Task<bool> DeleteIncludeAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, DeleteNavOptions? deleteNavOptions = null) where TChildEntity : class, IEntity, new();Task<bool> DeleteAsync(List<TEntity> entities, bool isSoftDelete = true, CancellationToken cancellationToken = default);Task<bool> DeleteIncludeAsync<TChildEntity>(List<TEntity> entities, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, DeleteNavOptions? deleteNavOptions = null) where TChildEntity : class, IEntity, new();Task<bool> DeleteByIdAsync(TKey id, bool isSoftDelete = true, CancellationToken cancellationToken = default);Task<bool> DeleteByIdAsync(List<TKey> ids, bool isSoftDelete = true, CancellationToken cancellationToken = default);#endregion
}
接口库中新建数据库上下文接口类ISugarSqlDbContext,定义ISqlSugarClient类型数据库操作对象
public interface ISugarSqlDbContext
{public ISqlSugarClient SugarSqlClient { get; }
}
接口库中新建数据库上下文提供器接口类ISugarSqlDbContextProvider,提供获取具体的数据库上下文类功能
public interface ISugarSqlDbContextProvider<TDbContext> where TDbContext : ISugarSqlDbContext
{public TDbContext GetDbContext();
}
接口库中新建数据库连接构建器接口类ISugarSqlDbConnectionBuilder,提供赋值具体的数据库连接信息功能
public interface ISugarSqlDbConnectionBuilder
{ConnectionConfig Build(Action<ConnectionConfig>? action = null);
}
接下来在实现库中一一实现这些接口,在这之前先定义数据库连接选项类DbConnectionOptions和当前用户上下文类CurrentUserAmbient
public class DbConnectionOptions
{public const string DbConnectionOption = "DbConnection";/// <summary>/// 数据库连接字符串/// </summary>public string ConnectionString { get; set; } = string.Empty;/// <summary>/// 数据库类型/// </summary>public string Type { get; set; } = string.Empty;
}
public class CurrentUserAmbient : CurrentUserAmbient<Guid>
{}public class CurrentUserAmbient<TKey>
{private static readonly AsyncLocal<TKey?> UserIdHolder = new();public static TKey? UserId => Get();public static TKey? Get(){return UserIdHolder.Value;}public static void Set(TKey? userId){UserIdHolder.Value = userId;}public static void Clear(){UserIdHolder.Value = default;}
}
实现库中新建数据库连接构建器类SugarSqlDbConnectionBuilder,Build方法返回数据库连接配置,在构建SqlSugar的具体数据库操作对象时使用
public class SugarSqlDbConnectionBuilder : ISugarSqlDbConnectionBuilder
{public SugarSqlDbConnectionBuilder() {}public ConnectionConfig Build(Action<ConnectionConfig>? action = null){var connectionConfig = new ConnectionConfig(){};action?.Invoke(connectionConfig);return connectionConfig;}
}
实现库中新建数据库上下文提供器类SugarSqlDbContextProvider
public class SugarSqlDbContextProvider<TDbContext> : ISugarSqlDbContextProvider<TDbContext> where TDbContext : ISugarSqlDbContext
{private readonly IServiceProvider _serviceProvider;public SugarSqlDbContextProvider(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;}public TDbContext GetDbContext(){var dbContext = (TDbContext)_serviceProvider.GetRequiredService<ISugarSqlDbContext>();return dbContext;}
}
实现库中新建数据库上下文类SugarSqlDbContext,在这里构建数据库操作对象SugarSqlClient,同时定义实体数据插入事件和更新事件,自动赋值审计属性,添加查询过滤器,不查询已经删除的数据条目,实现软删除功能
审计属性中的创建人Id和更新人Id,都从CurrentUserAmbient中的用户Id获取,而CurrentUserAmbient中的用户Id在进行认证成功后进行赋值
public class SugarSqlDbContext : ISugarSqlDbContext{private readonly IServiceProvider _serviceProvider;public ISqlSugarClient SugarSqlClient { get; private set; }public SugarSqlDbContext(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;var dbConnectionBuilder = _serviceProvider.GetRequiredService<ISugarSqlDbConnectionBuilder>();var dbConnectionOptions = _serviceProvider.GetRequiredService<IOptions<DbConnectionOptions>>().Value;DbType dbType;switch (dbConnectionOptions.Type){case "MySql":dbType = DbType.MySql;break;case "SqlServer":dbType = DbType.SqlServer;break;case "Sqlite":dbType = DbType.Sqlite;break;case "Oracle":dbType = DbType.Oracle;break;case "PostgreSQL":dbType = DbType.PostgreSQL;break;default:dbType = DbType.MySql;break;}SugarSqlClient = new SqlSugarClient(dbConnectionBuilder.Build(options =>{options.DbType = dbType;options.ConnectionString = dbConnectionOptions.ConnectionString;options.IsAutoCloseConnection = true;options.InitKeyType = InitKeyType.Attribute;options.AopEvents = new AopEvents{DataExecuting = (oldValue, entityInfo) =>{//插入事件if (entityInfo.OperationType == DataFilterType.InsertByObject){if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id))){if (Guid.Empty.Equals(oldValue)){entityInfo.SetValue(Guid.NewGuid());}}//if (entityInfo.PropertyName.Equals(nameof(ICreationAuditedEntity.CreationTime)))//{// if (oldValue is null || DateTime.MinValue.Equals(oldValue))// {// entityInfo.SetValue(DateTime.Now);// }//}//if (entityInfo.PropertyName.Equals(nameof(ICreationAuditedEntity.CreatorId)))//{// entityInfo.SetValue(CurrentUserAmbient.UserId);//}//行级别事件if (entityInfo.EntityColumnInfo.IsPrimarykey){var entity = entityInfo.EntityValue;if (entity is ICreationAuditedEntity creationAuditedEntity){creationAuditedEntity.CreationTime = DateTime.Now;creationAuditedEntity.CreatorId = CurrentUserAmbient.UserId;}}}//更新事件if (entityInfo.OperationType == DataFilterType.UpdateByObject){//行级别事件if (entityInfo.EntityColumnInfo.IsPrimarykey){var entity = entityInfo.EntityValue;if (entity is IUpdationAuditedEntity updationAuditedEntity){updationAuditedEntity.LastModificationTime = DateTime.Now;updationAuditedEntity.LastModifierId = CurrentUserAmbient.UserId;}}}},OnLogExecuting = (sql, param) =>{
#if DEBUGConsole.WriteLine("\r\n" + "【SQL语句】:" + GetWholeSql(param, sql));
#endif}};}), dbContext =>{dbContext.QueryFilter.AddTableFilter<IDeletionAuditedEntity>(entity => entity.IsDeleted == false);});}private static string GetWholeSql(SugarParameter[] paramArr, string sql){foreach (var param in paramArr){sql = sql.Replace(param.ParameterName, param.Value?.ToString());}return sql;}}
实现库中新建SugarSqlRepository,简单封装一些常用的数据库操作方法
public class SugarSqlRepository<TEntity> : ISugarSqlRepository<TEntity> where TEntity : class, IEntity, new()
{private const int BatchInsertUpdateCount = 1000;private readonly ISugarSqlDbContextProvider<ISugarSqlDbContext> _dbContextProvider;public ISqlSugarClient SugarSqlClient => GetDbContext();public ISugarQueryable<TEntity> DbQueryable => GetDbContext().Queryable<TEntity>();public SugarSqlRepository(ISugarSqlDbContextProvider<ISugarSqlDbContext> dbContextProvider){_dbContextProvider = dbContextProvider;}public ISqlSugarClient GetDbContext(){return _dbContextProvider.GetDbContext().SugarSqlClient;}public SimpleClient<TEntity> GetDbSimpleClient(){return GetDbContext().GetSimpleClient<TEntity>();}public ISugarQueryable<TEntity> AsQueryable(){return GetDbSimpleClient().AsQueryable();}public InsertNavTaskInit<TEntity, TEntity> AsInsertNav(TEntity entity){return GetDbContext().InsertNav(entity);}public InsertNavTaskInit<TEntity, TEntity> AsInsertNav(List<TEntity> entities){return GetDbContext().InsertNav(entities);}public UpdateNavTaskInit<TEntity, TEntity> AsUpdateNav(TEntity entity){return GetDbContext().UpdateNav(entity);}public UpdateNavTaskInit<TEntity, TEntity> AsUpdateNav(List<TEntity> entities){return GetDbContext().UpdateNav(entities);}public DeleteNavTaskInit<TEntity, TEntity> AsDeleteNav(TEntity entity){return GetDbContext().DeleteNav(entity);}public DeleteNavTaskInit<TEntity, TEntity> AsDeleteNav(List<TEntity> entities){return GetDbContext().DeleteNav(entities);}#region 查询public async Task<bool> AnyAsync(){var result = await DbQueryable.AnyAsync();return result;}public async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default){var result = await DbQueryable.AnyAsync(expression, cancellationToken);return result;}public async Task<TEntity?> FindIncludeAsync<TChildEntity>(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, CancellationToken cancellationToken = default) where TChildEntity : class, new(){var entity = await DbQueryable.Includes(includeExpression).FirstAsync(expression, cancellationToken);return entity;}public async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default){var entity = await DbQueryable.FirstAsync(expression, cancellationToken);return entity;}public async Task<TEntity?> FindAsync(dynamic id, CancellationToken cancellationToken = default){var entity = await GetDbSimpleClient().GetByIdAsync(id, cancellationToken);return entity;}public async Task<TEntity> GetIncludeAsync<TChildEntity>(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, CancellationToken cancellationToken = default) where TChildEntity : class, new(){var entity = await DbQueryable.Includes(includeExpression).FirstAsync(expression, cancellationToken);return entity;}public async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default){var entity = await GetDbSimpleClient().GetFirstAsync(expression, cancellationToken);return entity;}public async Task<TEntity> GetAsync(dynamic id, CancellationToken cancellationToken = default){var entity = await GetDbSimpleClient().GetByIdAsync(id, cancellationToken);return entity;}public async Task<List<TEntity>> GetListIncludeAsync<TChildEntity>(Expression<Func<TEntity, List<TChildEntity>>> includeExpression, CancellationToken cancellationToken = default) where TChildEntity : class, new(){var entities = await DbQueryable.Includes(includeExpression).ToListAsync(cancellationToken);return entities;}public async Task<List<TEntity>> GetListAsync(CancellationToken cancellationToken = default){var entities = await GetDbSimpleClient().GetListAsync(cancellationToken);return entities;}public async Task<List<TEntity>> GetListIncludeAsync<TChildEntity>(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, CancellationToken cancellationToken = default) where TChildEntity : class, new(){var entities = await DbQueryable.Includes(includeExpression).Where(expression).ToListAsync(cancellationToken);return entities;}public async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default){var entities = await GetDbSimpleClient().GetListAsync(expression, cancellationToken);return entities;}public async Task<int> GetCountAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default){var count = await GetDbSimpleClient().CountAsync(expression, cancellationToken);return count;}public async Task<long> GetCountAsync(CancellationToken cancellationToken = default){var count = await DbQueryable.CountAsync(cancellationToken);return count;}public async Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> expression, CancellationToken cancellationToken = default){return await GetDbSimpleClient().IsAnyAsync(expression, cancellationToken);}#endregion 查询#region 新增public async Task<bool> InsertAsync(TEntity entity, CancellationToken cancellationToken = default){var result = await GetDbSimpleClient().InsertAsync(entity, cancellationToken);return result;}public async Task<bool> InsertIncludeAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, InsertNavOptions? insertNavOptions = null) where TChildEntity : class, IEntity, new(){var result = await AsInsertNav(entity).Include(includeExpression, insertNavOptions).ExecuteCommandAsync();return result;}public async Task<int> InsertRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default){var num = 0;if (entities.Count > BatchInsertUpdateCount){var index = 0;while (index < entities.Count && !cancellationToken.IsCancellationRequested){var takeCount = index + BatchInsertUpdateCount > entities.Count ? entities.Count - index : BatchInsertUpdateCount;var partitionEntities = entities.Skip(index).Take(takeCount).ToList();var partitionInsertResult = await GetDbSimpleClient().InsertRangeAsync(partitionEntities, cancellationToken);if (partitionInsertResult){num += partitionEntities.Count;}index += takeCount;}}else{await GetDbSimpleClient().InsertRangeAsync(entities, cancellationToken);num = entities.Count;}return num;}public async Task<int> InsertRangeIncludeAsync<TChildEntity>(List<TEntity> entities, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, InsertNavOptions? insertNavOptions = null) where TChildEntity : class, IEntity, new(){var num = 0;if (entities.Count > BatchInsertUpdateCount){var index = 0;while (index < entities.Count){var takeCount = index + BatchInsertUpdateCount > entities.Count ? entities.Count - index : BatchInsertUpdateCount;var partitionEntities = entities.Skip(index).Take(takeCount).ToList();var partitionInsertResult = await AsInsertNav(partitionEntities).Include(includeExpression, insertNavOptions).ExecuteCommandAsync();if (partitionInsertResult){num += partitionEntities.Count;}index += takeCount;}}else{await AsInsertNav(entities).Include(includeExpression, insertNavOptions).ExecuteCommandAsync();num = entities.Count;}return num;}public async Task<TEntity> InsertReturnAsync(TEntity entity, CancellationToken cancellationToken = default){var newEntity = await GetDbSimpleClient().InsertReturnEntityAsync(entity, cancellationToken);return newEntity;}public async Task<TEntity> InsertIncludeReturnAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, InsertNavOptions? insertNavOptions = null) where TChildEntity : class, IEntity, new(){var newEntity = await AsInsertNav(entity).Include(includeExpression, insertNavOptions).ExecuteReturnEntityAsync();return newEntity;}public async Task<long> InsertReturnIdAsync(TEntity entity, CancellationToken cancellationToken = default){var id = await GetDbSimpleClient().InsertReturnBigIdentityAsync(entity, cancellationToken);return id;}#endregion 新增#region 更新public async Task<bool> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default){var result = await GetDbSimpleClient().UpdateAsync(entity, cancellationToken);return result;}public async Task<bool> UpdateIncludeAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, UpdateNavOptions? updateNavOptions = null) where TChildEntity : class, IEntity, new(){var result = await AsUpdateNav(entity).Include(includeExpression, updateNavOptions).ExecuteCommandAsync();return result;}public async Task<bool> UpdateRangeIncludeAsync<TChildEntity>(List<TEntity> entities, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, UpdateNavOptions? updateNavOptions = null) where TChildEntity : class, IEntity, new(){bool result;var num = 0;if (entities.Count > BatchInsertUpdateCount){var index = 0;while (index < entities.Count){var takeCount = index + BatchInsertUpdateCount > entities.Count ? entities.Count - index : BatchInsertUpdateCount;var partitionEntities = entities.Skip(index).Take(takeCount).ToList();var partitionInsertResult = await AsUpdateNav(partitionEntities).Include(includeExpression, updateNavOptions).ExecuteCommandAsync();if (partitionInsertResult){num += partitionEntities.Count;}index += takeCount;}result = num == entities.Count;}else{result = await AsUpdateNav(entities).Include(includeExpression, updateNavOptions).ExecuteCommandAsync();return result;}return result;}public async Task<TEntity> UpdateReturnAsync(TEntity entity, CancellationToken cancellationToken = default){await GetDbSimpleClient().UpdateAsync(entity, cancellationToken);return entity;}public async Task<TEntity> UpdateIncludeReturnAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, UpdateNavOptions? updateNavOptions = null) where TChildEntity : class, IEntity, new(){await AsUpdateNav(entity).Include(includeExpression, updateNavOptions).ExecuteCommandAsync();return entity;}public async Task<bool> UpdateRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default){bool result;var num = 0;if (entities.Count > BatchInsertUpdateCount){var index = 0;while (index < entities.Count){var takeCount = index + BatchInsertUpdateCount > entities.Count ? entities.Count - index : BatchInsertUpdateCount;var partitionEntities = entities.Skip(index).Take(takeCount).ToList();var partitionInsertResult = await GetDbSimpleClient().UpdateRangeAsync(partitionEntities, cancellationToken);if (partitionInsertResult){num += partitionEntities.Count;}index += takeCount;}result = num == entities.Count;}else{result = await GetDbSimpleClient().UpdateRangeAsync(entities, cancellationToken);return result;}return result;}#endregion 更新#region 删除public async Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> expression, bool isSoftDelete = true, CancellationToken cancellationToken = default){if (isSoftDelete && typeof(IDeletionAuditedEntity).IsAssignableFrom(typeof(TEntity))){var result = await GetDbContext().Updateable<TEntity>().Where(expression).SetColumns("DeletionTime", DateTime.Now).SetColumns("DeleterId", CurrentUserAmbient.UserId).SetColumns("IsDeleted", true).ExecuteCommandAsync(cancellationToken);return result > 0;}else{var result = await GetDbSimpleClient().DeleteAsync(expression, cancellationToken);return result;}}#endregion
}public class SugarSqlRepository<TEntity, TKey> : SugarSqlRepository<TEntity>, ISugarSqlRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
{public SugarSqlRepository(ISugarSqlDbContextProvider<ISugarSqlDbContext> dbContextProvider) : base(dbContextProvider){}#region 查询public async Task<TEntity?> FindByIdAsync(TKey id, CancellationToken cancellationToken = default){var entity = await FindAsync(id!, cancellationToken);return entity;}public async Task<TEntity> GetByIdAsync(TKey id, CancellationToken cancellationToken = default){var entity = await GetAsync(id!, cancellationToken);return entity;}#endregion 查询#region 删除public async Task<bool> DeleteAsync(TEntity entity, bool isSoftDelete = true, CancellationToken cancellationToken = default){if (isSoftDelete && typeof(IDeletionAuditedEntity).IsAssignableFrom(typeof(TEntity))){var idColumn = GetDbContext().EntityMaintenance.GetDbColumnName<TEntity>(nameof(IEntity<TKey>.Id));var result = await GetDbContext().Updateable<TEntity>().Where($"{idColumn} = @rowId", new { rowId = entity.Id }).SetColumns("DeletionTime", DateTime.Now).SetColumns("DeleterId", CurrentUserAmbient.UserId).SetColumns("IsDeleted", true).ExecuteCommandAsync(cancellationToken);return result == 1;}else{var result = await GetDbSimpleClient().DeleteAsync(entity, cancellationToken);return result;}}public async Task<bool> DeleteIncludeAsync<TChildEntity>(TEntity entity, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, DeleteNavOptions? deleteNavOptions = null) where TChildEntity : class, IEntity, new(){var result = await AsDeleteNav(entity).Include(includeExpression, deleteNavOptions).ExecuteCommandAsync();return result;}public async Task<bool> DeleteAsync(List<TEntity> entities, bool isSoftDelete = true, CancellationToken cancellationToken = default){if (isSoftDelete && typeof(IDeletionAuditedEntity).IsAssignableFrom(typeof(TEntity))){var result = await GetDbContext().Updateable<TEntity>().Where(x => entities.Select(entity => entity.Id).Contains(x.Id)).SetColumns("DeletionTime", DateTime.Now).SetColumns("DeleterId", CurrentUserAmbient.UserId).SetColumns("IsDeleted", true).ExecuteCommandAsync(cancellationToken);return result == entities.Count;}else{var result = await GetDbSimpleClient().DeleteAsync(entities, cancellationToken);return result;}}public async Task<bool> DeleteIncludeAsync<TChildEntity>(List<TEntity> entities, Expression<Func<TEntity, List<TChildEntity>>> includeExpression, DeleteNavOptions? deleteNavOptions = null) where TChildEntity : class, IEntity, new(){var result = await AsDeleteNav(entities).Include(includeExpression, deleteNavOptions).ExecuteCommandAsync();return result;}public async Task<bool> DeleteByIdAsync(TKey id, bool isSoftDelete = true, CancellationToken cancellationToken = default){if (isSoftDelete && typeof(IDeletionAuditedEntity).IsAssignableFrom(typeof(TEntity))){var idColumn = GetDbContext().EntityMaintenance.GetDbColumnName<TEntity>(nameof(IEntity<TKey>.Id));var result = await GetDbContext().Updateable<TEntity>().Where($"{idColumn} = @rowId", new { rowId = id }).SetColumns("DeletionTime", DateTime.Now).SetColumns("DeleterId", CurrentUserAmbient.UserId).SetColumns("IsDeleted", true).ExecuteCommandAsync(cancellationToken);return result == 1;}else{var result = await GetDbSimpleClient().DeleteByIdAsync(id, cancellationToken);return result;}}public async Task<bool> DeleteByIdAsync(List<TKey> ids, bool isSoftDelete = true, CancellationToken cancellationToken = default){if (isSoftDelete && typeof(IDeletionAuditedEntity).IsAssignableFrom(typeof(TEntity))){var result = await GetDbContext().Updateable<TEntity>().Where(x => ids.Contains(x.Id)).SetColumns("DeletionTime", DateTime.Now).SetColumns("DeleterId", CurrentUserAmbient.UserId).SetColumns("IsDeleted", true).ExecuteCommandAsync(cancellationToken);return result == ids.Count;}else{var result = await GetDbSimpleClient().DeleteAsync(x => ids.Contains(x.Id), cancellationToken);return result;}}#endregion 删除
}
其中的删除数据的方法全部根据软删除参数,来决定是硬删除还是赋值删除审计属性进行更新
新建名称为SugarSqlExtension的扩展类,用来进行相关实例的依赖注入,以及添加必要的服务
public static class SugarSqlExtension
{public static void AddSugarSql(this IServiceCollection services, Action<DbConnectionOptions> configureOptions){services.AddOptions();services.Configure(configureOptions);services.AddTransient<ISugarSqlDbConnectionBuilder, SugarSqlDbConnectionBuilder>();services.AddScoped<ISugarSqlDbContext, SugarSqlDbContext>();services.AddTransient(typeof(ISugarSqlDbContextProvider<>), typeof(SugarSqlDbContextProvider<>));services.AddTransient(typeof(ISugarSqlRepository<>), typeof(SugarSqlRepository<>));services.AddTransient(typeof(ISugarSqlRepository<,>), typeof(SugarSqlRepository<,>));}
}
仓储使用
在appsetting.json中配置数据库连接信息
{"DbConnection": {"Type": "MySql","ConnectionString": "server=localhost;database=sugarsql;user=youruser;password=yourpassword;port=yourport;CharSet=utf8mb4;Allow User Variables=True;SslMode=none;AllowLoadLocalInfile=true;"}
}
新建基础仓储接口类IBaseRepository和基础仓储类BaseRepository,提供复用的辅助功能,例如生成排序字段信息
IBaseRepository
public interface IBaseRepository<TEntity> : ISugarSqlRepository<TEntity> where TEntity : class, IEntity, new()
{string GenerateSortExpression(string sortField, string sortType, Type type);string GenerateSortExpression(string sortField, string sortType);
}public interface IBaseRepository<TEntity, TKey> : IBaseRepository<TEntity>, ISugarSqlRepository<TEntity, TKey> where TEntity : class, IEntity, new()
{}
BaseRepository
public abstract class BaseRepository<TEntity> : SugarSqlRepository<TEntity>, IBaseRepository<TEntity> where TEntity : class, IEntity, new()
{public BaseRepository(ISugarSqlDbContextProvider<ISugarSqlDbContext> dbContextProvider) : base(dbContextProvider){}public string GenerateSortExpression(string sortField, string sortType, Type type){string sortExpression = string.Empty;if (IsValidField(sortField, type)){sortExpression = sortField;if (sortType != "asc"){sortExpression += " desc";}else{sortExpression += " asc";}}return sortExpression;}public string GenerateSortExpression(string sortField, string sortType){string sortExpression = string.Empty;if (IsValidField(sortField)){sortExpression = sortField;if (sortType != "asc"){sortExpression += " desc";}else{sortExpression += " asc";}}return sortExpression;}private static bool IsValidField(string field, Type type){PropertyInfo[] properties = type.GetProperties();foreach (PropertyInfo property in properties){if (property.Name.Equals(field)){return true;}}return false;}private bool IsValidField(string field){try{string property = GetDbContext().EntityMaintenance.GetDbColumnName<TEntity>(field);}catch (Exception){return false;}return true;}
}public abstract class BaseRepository<TEntity, TKey> : SugarSqlRepository<TEntity, TKey>, IBaseRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
{public BaseRepository(ISugarSqlDbContextProvider<ISugarSqlDbContext> dbContextProvider) : base(dbContextProvider){}public string GenerateSortExpression(string sortField, string sortType, Type type){string sortExpression = string.Empty;if (IsValidField(sortField, type)){sortExpression = sortField;if (sortType != "asc"){sortExpression += " desc";}else{sortExpression += " asc";}}return sortExpression;}public string GenerateSortExpression(string sortField, string sortType){string sortExpression = string.Empty;if (IsValidField(sortField)){sortExpression = sortField;if (sortType != "asc"){sortExpression += " desc";}else{sortExpression += " asc";}}return sortExpression;}private static bool IsValidField(string field, Type type){PropertyInfo[] properties = type.GetProperties();foreach (PropertyInfo property in properties){if (property.Name.Equals(field)){return true;}}return false;}private bool IsValidField(string field){try{string property = GetDbContext().EntityMaintenance.GetDbColumnName<TEntity>(field);}catch (Exception){return false;}return true;}
}
定义业务实体User和对应的User仓储类
User
/// <summary>
/// 用户
/// </summary>
[SugarTable("user")]
public class User : Entity<Guid>, ICreationAuditedEntity, IUpdationAuditedEntity, IDeletionAuditedEntity
{/// <summary>/// Id/// </summary>[SugarColumn(IsPrimaryKey = true)]public override Guid Id { get; protected set; }/// <summary>/// 姓名/// </summary>public string Name { get; set; } = null!;/// <summary>/// 年龄/// </summary>public int Age { get; set; }/// <summary>/// 邮箱/// </summary>public string? Email { get; set; }/// <summary>/// 电话/// </summary>public string? Phone { get; set; }/// <summary>/// 创建时间/// </summary>public DateTime CreationTime { get; set; }/// <summary>/// 创建人/// </summary>public Guid? CreatorId { get; set; }/// <summary>/// 更新时间/// </summary>public DateTime? LastModificationTime { get; set; }/// <summary>/// 更新人/// </summary>public Guid? LastModifierId { get; set; }/// <summary>/// 删除时间/// </summary>public DateTime? DeletionTime { get; set; }/// <summary>/// 删除人/// </summary>public Guid? DeleterId { get; set; }/// <summary>/// 是否删除,0:未删除,1:已删除/// </summary>public bool IsDeleted { get; set; }
}
IUserRepository
public interface IUserRepository : IBaseRepository<User, Guid>
{}
UserRepository
public class UserRepository : BaseRepository<User, Guid>, IUserRepository
{public UserRepository(ISugarSqlDbContextProvider<ISugarSqlDbContext> dbContextProvider) : base(dbContextProvider){}
}
新建控制器UserController类和对应User数据传输对象类,进行简单的数据操作功能测试
用户Dto类
public class UserDto
{/// <summary>/// Id/// </summary>public Guid Id { get; set; }/// <summary>/// 姓名/// </summary>public string Name { get; set; } = null!;/// <summary>/// 年龄/// </summary>public int Age { get; set; }/// <summary>/// 邮箱/// </summary>public string? Email { get; set; }/// <summary>/// 电话/// </summary>public string? Phone { get; set; }
}public class UserCreateDto
{/// <summary>/// 姓名/// </summary>public string Name { get; set; } = null!;/// <summary>/// 年龄/// </summary>public int Age { get; set; }/// <summary>/// 邮箱/// </summary>public string? Email { get; set; }/// <summary>/// 电话/// </summary>public string? Phone { get; set; }
}public class UserQueryDto
{/// <summary>/// 姓名/// </summary>public string? Name { get; set; }
}public class UserUpdateDto
{/// <summary>/// Id/// </summary>public Guid Id { get; set; }/// <summary>/// 年龄/// </summary>public int Age { get; set; }/// <summary>/// 邮箱/// </summary>public string? Email { get; set; }/// <summary>/// 电话/// </summary>public string? Phone { get; set; }
}
UserController
[ApiController]
[Route("api/[controller]")]
public class UserController : Controller
{private readonly IUserRepository _userRepository;public UserController(IUserRepository userRepository){_userRepository = userRepository;}[HttpGet]public async Task<List<UserDto>> GetListAsync([FromQuery] UserQueryDto input){// 分页信息和排序方式可以通过参数传递,这里只是示例,默认排序方式为按Name降序,默认分页大小为10,分页序号为1var sort = _userRepository.GenerateSortExpression("Name", "desc");var totalNumber = new RefAsync<int>();var totalPage = new RefAsync<int>();var userList = await _userRepository.AsQueryable().WhereIF(!string.IsNullOrWhiteSpace(input.Name), user => user.Name.Contains(input.Name!)).Select(user => new UserDto{Id = user.Id,Name = user.Name,Age = user.Age,Email = user.Email,Phone = user.Phone}).OrderByIF(!string.IsNullOrWhiteSpace(sort), sort).ToPageListAsync(1, 10, totalNumber, totalPage);return userList;}[HttpGet("{id}")]public async Task<UserDto> GetAsync(Guid id){var user = await _userRepository.FindAsync(id) ?? throw new Exception("User not found");return new UserDto{Id = user.Id,Name = user.Name,Age = user.Age,Email = user.Email,Phone = user.Phone};}[HttpPost]public async Task CreateAsync(UserCreateDto input){var existUser = await _userRepository.GetAsync(user => user.Name == input.Name);if (existUser != null){throw new Exception("User with the same name already exists");}var user = new User{Name = input.Name,Age = input.Age,Email = input.Email,Phone = input.Phone};await _userRepository.InsertAsync(user);}[HttpPut]public async Task UpdateAsync(UserUpdateDto input){var user = await _userRepository.FindAsync(input.Id) ?? throw new Exception("User not found");user.Age = input.Age;user.Email = input.Email;user.Phone = input.Phone;await _userRepository.UpdateAsync(user);}[HttpDelete("{id}")]public async Task DeleteAsync(Guid id){await _userRepository.DeleteByIdAsync(id);}[HttpDelete]public async Task BatchDeleteAsync([FromBody] List<Guid> ids){await _userRepository.DeleteByIdAsync(ids);}
}
新建认证中间件类AuthorizeMiddleware,模拟认证成功后,将获取到的UserId赋值到异步上下文CurrentUserAmbient中,待后续数据操作时,获取并赋值到对应的审计属性上
public class AuthorizeMiddleware
{private readonly RequestDelegate _next;public AuthorizeMiddleware(RequestDelegate next){_next = next;}public async Task InvokeAsync(HttpContext context){try{// 认证逻辑,从请求中提取用户Id并设置到CurrentUserAmbient中// todo CurrentUserAmbient.Set(Guid.NewGuid());await _next(context);}finally{CurrentUserAmbient.Clear();}}
}public static class AuthorizeMiddlewareExtension
{public static IApplicationBuilder UseAuthorize(this IApplicationBuilder builder){return builder.UseMiddleware<AuthorizeMiddleware>();}
}
在程序启动类中,使用该中间件app.UseAuthorize();
添加SqlSugar服务,配置数据库连接信息和手动进行仓储的依赖注入
var dbConnectionOptions = new DbConnectionOptions();
builder.Configuration.GetSection(DbConnectionOptions.DbConnectionOption).Bind(dbConnectionOptions);
builder.Services.AddSugarSql(options =>
{options.Type = dbConnectionOptions.Type;options.ConnectionString = dbConnectionOptions.ConnectionString;
});
builder.Services.AddTransient<IUserRepository, UserRepository>();
最后,不要忘记在数据库创建User表
DROP DATABASE IF EXISTS `sugarsql`;
CREATE DATABASE IF NOT EXISTS `sugarsql`;USE `sugarsql`;DROP TABLE IF EXISTS `user`;
CREATE TABLE IF NOT EXISTS `user` (`Id` CHAR(36) NOT NULL COMMENT 'Id' COLLATE 'utf8mb4_general_ci',`Name` VARCHAR(64) NOT NULL COMMENT '姓名' COLLATE 'utf8mb4_general_ci',`Age` INT(3) NOT NULL COMMENT '年龄',`Email` VARCHAR(64) NULL DEFAULT NULL COMMENT '邮箱' COLLATE 'utf8mb4_general_ci',`Phone` VARCHAR(64) NULL DEFAULT NULL COMMENT '电话' COLLATE 'utf8mb4_general_ci',`CreationTime` DATETIME NOT NULL COMMENT '创建时间',`CreatorId` CHAR(36) NULL DEFAULT NULL COMMENT '创建人' COLLATE 'utf8mb4_general_ci',`LastModificationTime` DATETIME NULL DEFAULT NULL COMMENT '更新时间',`LastModifierId` CHAR(36) NULL DEFAULT NULL COMMENT '更新人' COLLATE 'utf8mb4_general_ci',`IsDeleted` TINYINT(2) NOT NULL DEFAULT '0' COMMENT '是否删除,0:未删除,1:已删除',`DeleterId` CHAR(36) NULL DEFAULT NULL COMMENT '删除人' COLLATE 'utf8mb4_general_ci',`DeletionTime` DATETIME NULL DEFAULT NULL COMMENT '删除时间',PRIMARY KEY (`Id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
仓储测试
结果如下图所示
-
创建一个用户
![]()
![]()
-
查询指定用户
![]()
-
更新指定用户
![]()
![]()
-
删除指定用户
![]()
![]()







