first commit.

main
NoahLan 11 months ago
parent e859f8df42
commit c673532021

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/.idea.NPin.iml
/modules.xml
/projectSettingsUpdater.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,32 @@
namespace NPin.Framework.Core.Enums;
/// <summary>
/// 文件类型
/// </summary>
public enum FileTypeEnum
{
/// <summary>
/// 普通文件
/// </summary>
File,
/// <summary>
/// 图片
/// </summary>
Image,
/// <summary>
/// 缩略图
/// </summary>
Thumbnail,
/// <summary>
/// Excel
/// </summary>
Excel,
/// <summary>
/// 临时文件
/// </summary>
Temp
}

@ -0,0 +1,14 @@
namespace NPin.Framework.Core.Enums;
public enum OrderByEnum
{
/// <summary>
/// 升序
/// </summary>
Asc,
/// <summary>
/// 降序
/// </summary>
Desc,
}

@ -0,0 +1,6 @@
namespace NPin.Framework.Core.Extensions;
public static class HttpContextExtensions
{
// public static void FileInlineHandle(this HttpContext)
}

@ -0,0 +1,57 @@
namespace NPin.Framework.Core.Helper;
public static class ReflectHelper
{
/// <summary>
/// 反射获取对象属性值(string)
/// </summary>
/// <param name="fieldName"></param>
/// <param name="obj"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static string? GetModelValue(string fieldName, object obj)
{
try
{
var typ = obj.GetType();
object o = typ.GetProperty(fieldName).GetValue(obj, null);
if (o == null)
{
return null;
}
var value = Convert.ToString(o);
if (string.IsNullOrEmpty(value))
{
return null;
}
return value;
}
catch (Exception e)
{
throw e;
}
}
/// <summary>
/// 反射设置对象属性值
/// </summary>
/// <param name="fieldName"></param>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
public static bool SetModelValue(string fieldName, object value, object obj)
{
try
{
var typ = obj.GetType();
typ.GetProperty(fieldName)?.SetValue(obj, value, null);
return true;
}
catch (Exception e)
{
throw e;
}
}
}

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Lan.Framework.Core</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Volo.Abp.Core" Version="8.0.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data" />
</ItemGroup>
</Project>

@ -0,0 +1,7 @@
using Volo.Abp.Modularity;
namespace NPin.Framework.Core;
public class NPinFrameworkCoreModule : AbpModule
{
}

@ -0,0 +1,18 @@
using Mapster;
using Volo.Abp.ObjectMapping;
namespace NPin.Framework.Mapster;
public class MapsterAutoObjectMappingProvider: IAutoObjectMappingProvider
{
public TDestination Map<TSource, TDestination>(object source)
{
// var sss = typeof(TDestination).Name;
return source.Adapt<TDestination>();
}
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return source.Adapt(destination);
}
}

@ -0,0 +1,18 @@
using Volo.Abp.ObjectMapping;
namespace NPin.Framework.Mapster;
public class MapsterObjectMapper : IObjectMapper
{
public IAutoObjectMappingProvider AutoObjectMappingProvider => throw new NotImplementedException();
public TDestination Map<TSource, TDestination>(TSource source)
{
throw new NotImplementedException();
}
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
throw new NotImplementedException();
}
}

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Volo.Abp.ObjectMapping" Version="8.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NPin.Framework.Core\NPin.Framework.Core.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,16 @@
using Microsoft.Extensions.DependencyInjection;
using NPin.Framework.Core;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
namespace NPin.Framework.Mapster;
[DependsOn(typeof(NPinFrameworkCoreModule), typeof(AbpObjectMappingModule))]
public class NPinFrameworkMapsterModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 注入 MapsterAutoObjectMappingProvider
context.Services.AddTransient<IAutoObjectMappingProvider, MapsterAutoObjectMappingProvider>();
}
}

@ -0,0 +1,105 @@
using SqlSugar;
namespace NPin.Framework.SqlSugarCore.Abstractions;
public class DbConnOptions
{
/// <summary>
/// 连接字符串 必填
/// </summary>
public string? Url { get; set; }
/// <summary>
/// 数据库类型
/// </summary>
public DbType? DbType { get; set; }
/// <summary>
/// 开启种子数据
/// </summary>
public bool EnabledDbSeed { get; set; } = false;
/// <summary>
/// 开启 CodeFirst 自动化表结构
/// </summary>
public bool EnabledCodeFirst { get; set; } = false;
/// <summary>
/// 开启sql日志
/// </summary>
public bool EnabledSqlLog { get; set; } = true;
/// <summary>
/// 实体程序集
/// </summary>
public List<string>? EntityAssembly { get; set; }
/// <summary>
/// 开启读写分离
/// </summary>
public bool EnabledReadWrite { get; set; } = false;
/// <summary>
/// 读写分离
/// </summary>
public List<string>? ReadUrl { get; set; }
/// <summary>
/// 开启Saas多租户
/// </summary>
public bool EnabledSaasMultiTenancy { get; set; } = false;
/// <summary>
/// 默认租户库连接,如果不填,那就是默认库的地址
/// </summary>
public string? MasterSaasMultiTenancyUrl { get; set; }
/// <summary>
/// Saas租户连接
/// </summary>
public List<SaasMultiTenancyOptions>? SaasMultiTenancy { get; set; }
public static string MasterTenantDbDefaultName = "Master";
public static string TenantDbDefaultName = "Default";
public SaasMultiTenancyOptions GetDefaultSaasMultiTenancy()
{
return new SaasMultiTenancyOptions { Name = TenantDbDefaultName, Url = Url };
}
public SaasMultiTenancyOptions? GetDefaultMasterSaasMultiTenancy()
{
if (EnabledSaasMultiTenancy == false)
{
return null;
}
if (string.IsNullOrEmpty(MasterSaasMultiTenancyUrl))
{
return new SaasMultiTenancyOptions { Name = MasterTenantDbDefaultName, Url = Url };
}
else
{
return new SaasMultiTenancyOptions()
{
Name = MasterTenantDbDefaultName,
Url = MasterSaasMultiTenancyUrl
};
}
}
}
public class SaasMultiTenancyOptions
{
/// <summary>
/// 租户名称标识
/// </summary>
public string Name { get; set; }
/// <summary>
/// 连接Url
/// </summary>
public string Url { get; set; }
}

@ -0,0 +1,19 @@
using System.Reflection;
using SqlSugar;
namespace NPin.Framework.SqlSugarCore.Abstractions;
public interface ISqlSugarDbConnectionCreator
{
DbConnOptions Options { get; }
Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
Action<object, DataAfterModel> DataExecuted { get; set; }
Action<object, DataFilterModel> DataExecuting { get; set; }
Action<string, SugarParameter[]> OnLogExecuting { get; set; }
Action<string, SugarParameter[]> OnLogExecuted { get; set; }
Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
ConnectionConfig Build(Action<ConnectionConfig>? action = null);
void SetDbAop(ISqlSugarClient currentDb);
}

@ -0,0 +1,27 @@
using SqlSugar;
namespace NPin.Framework.SqlSugarCore.Abstractions;
public interface ISqlSugarDbContext
{
/// <summary>
/// Db 客户端
/// </summary>
ISqlSugarClient SqlSugarClient { get; }
/// <summary>
/// 连接配置
/// </summary>
DbConnOptions Options { get; }
/// <summary>
/// 备份数据库
/// </summary>
void Backup();
/// <summary>
/// 设置客户端
/// </summary>
/// <param name="sqlSugarClient"></param>
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
}

@ -0,0 +1,87 @@
using System.Linq.Expressions;
using SqlSugar;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
namespace NPin.Framework.SqlSugarCore.Abstractions;
public interface ISqlSugarRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity, new()
{
ISqlSugarClient Db { get; }
ISugarQueryable<TEntity> DbQueryable { get; }
Task<ISqlSugarClient> GetDbContextAsync();
Task<IDeleteable<TEntity>> AsDeleteable();
Task<IInsertable<TEntity>> AsInsertable(List<TEntity> insertObjs);
Task<IInsertable<TEntity>> AsInsertable(TEntity insertObj);
Task<IInsertable<TEntity>> AsInsertable(TEntity[] insertObjs);
Task<ISugarQueryable<TEntity>> AsQueryable();
Task<ISqlSugarClient> AsSugarClient();
Task<ITenant> AsTenant();
Task<IUpdateable<TEntity>> AsUpdateable(List<TEntity> updateObjs);
Task<IUpdateable<TEntity>> AsUpdateable(TEntity updateObj);
Task<IUpdateable<TEntity>> AsUpdateable();
Task<IUpdateable<TEntity>> AsUpdateable(TEntity[] updateObjs);
#region 单查
Task<TEntity> GetByIdAsync(dynamic id);
Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<int> CountAsync(Expression<Func<TEntity, bool>> whereExpression);
#endregion
#region 多查
Task<List<TEntity>> GetListAsync();
Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> whereExpression);
#endregion
#region 分页查
Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize);
Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression, int pageNum, int pageSize,
Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
#endregion
#region 插入
Task<bool> InsertAsync(TEntity insertObj);
Task<bool> InsertOrUpdateAsync(TEntity data);
Task<bool> InsertOrUpdateAsync(List<TEntity> datas);
Task<int> InsertReturnIdentityAsync(TEntity insertObj);
Task<long> InsertReturnBigIdentityAsync(TEntity insertObj);
Task<long> InsertReturnSnowflakeIdAsync(TEntity insertObj);
Task<TEntity> InsertReturnEntityAsync(TEntity insertObj);
Task<bool> InsertRangeAsync(List<TEntity> insertObjs);
#endregion
#region 更新
Task<bool> UpdateAsync(TEntity updateObj);
Task<bool> UpdateRangeAsync(List<TEntity> updateObjs);
Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns, Expression<Func<TEntity, bool>> whereExpression);
#endregion
#region 删除
Task<bool> DeleteAsync(TEntity deleteObj);
Task<bool> DeleteAsync(List<TEntity> deleteObjs);
Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> whereExpression);
Task<bool> DeleteByIdAsync(dynamic id);
Task<bool> DeleteByIdsAsync(dynamic[] ids);
#endregion
}
public interface ISqlSugarRepository<TEntity, TKey> : ISqlSugarRepository<TEntity>,IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
{
}

@ -0,0 +1,6 @@
namespace NPin.Framework.SqlSugarCore.Abstractions;
public interface ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
{
Task<TDbContext> GetDbContextAsync();
}

@ -0,0 +1,6 @@
namespace NPin.Framework.SqlSugarCore.Abstractions;
[AttributeUsage(AttributeTargets.Class)]
public class IgnoreCodeFirstAttribute : Attribute
{
}

@ -0,0 +1,6 @@
namespace NPin.Framework.SqlSugarCore.Abstractions;
[AttributeUsage(AttributeTargets.Class)]
public class MasterTenantAttribute : Attribute
{
}

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.140" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NPin.Framework.Core\NPin.Framework.Core.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,9 @@
using NPin.Framework.Core;
using Volo.Abp.Modularity;
namespace NPin.Framework.SqlSugarCore.Abstractions;
[DependsOn(typeof(NPinFrameworkCoreModule))]
public class NPinFrameworkSqlSugarCoreAbstractionsModule : AbpModule
{
}

@ -0,0 +1,21 @@
using NPin.Framework.SqlSugarCore.Abstractions;
namespace NPin.Framework.SqlSugarCore;
public class AsyncLocalDbContextAccessor
{
public static AsyncLocalDbContextAccessor Instance { get; } = new();
private readonly AsyncLocal<ISqlSugarDbContext> _currentScope;
public ISqlSugarDbContext? Current
{
get => _currentScope.Value;
set => _currentScope.Value = value;
}
public AsyncLocalDbContextAccessor()
{
_currentScope = new();
}
}

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MySqlBackup.NET.MySqlConnector" Version="2.3.8" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.140" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NPin.Framework.SqlSugarCore.Abstractions\NPin.Framework.SqlSugarCore.Abstractions.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,95 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using NPin.Framework.SqlSugarCore.Abstractions;
using NPin.Framework.SqlSugarCore.Repositories;
using NPin.Framework.SqlSugarCore.Uow;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Modularity;
namespace NPin.Framework.SqlSugarCore;
[DependsOn(typeof(AbpDddDomainModule))]
public class NPinFrameworkSqlSugarCoreModule : AbpModule
{
public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
{
var service = context.Services;
var configuration = service.GetConfiguration();
Configure<DbConnOptions>(configuration.GetSection("DbConnOptions"));
// 开放 sqlSugarClient
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>();
// 不开放 sqlSugarClient
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient);
service.AddTransient(typeof(IRepository<>), typeof(SqlSugarRepository<>));
service.AddTransient(typeof(IRepository<,>), typeof(SqlSugarRepository<,>));
service.AddTransient(typeof(ISqlSugarRepository<>), typeof(SqlSugarRepository<>));
service.AddTransient(typeof(ISqlSugarRepository<,>), typeof(SqlSugarRepository<,>));
service.AddTransient(typeof(ISugarDbContextProvider<>), typeof(UnitOfWorkSqlSugarDbContextProvider<>));
return Task.CompletedTask;
}
public override async Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
{
// CodeFirst & DataSeed
var service = context.ServiceProvider;
var options = service.GetRequiredService<IOptions<DbConnOptions>>().Value;
// TODO 多租户 种子数据 和 CodeFirst
if (options.EnabledCodeFirst)
{
CodeFirst(service);
}
if (options.EnabledDbSeed)
{
await DataSeedAsync(service);
}
}
/// <summary>
/// CodeFirst 建库建表
/// </summary>
/// <param name="service"></param>
private void CodeFirst(IServiceProvider service)
{
var moduleContainer = service.GetRequiredService<IModuleContainer>();
var db = service.GetRequiredService<ISqlSugarDbContext>().SqlSugarClient;
// 尝试创建数据库
db.DbMaintenance.CreateDatabase();
var types = new List<Type>();
foreach (var module in moduleContainer.Modules)
{
types.AddRange(module.Assembly.GetTypes()
.Where(x => x.GetCustomAttribute<IgnoreCodeFirstAttribute>() == null)
.Where(x => x.GetCustomAttribute<SugarTable>() != null)
.Where(x => x.GetCustomAttribute<SplitTableAttribute>() is null));
}
if (types.Count > 0)
{
db.CopyNew().CodeFirst.InitTables(types.ToArray());
}
}
/// <summary>
/// 同步种子数据
/// </summary>
/// <param name="service"></param>
private async Task DataSeedAsync(IServiceProvider service)
{
var dataSeeder = service.GetRequiredService<IDataSeeder>();
await dataSeeder.SeedAsync();
}
}

@ -0,0 +1,430 @@
using System.Linq.Expressions;
using NPin.Framework.Core.Helper;
using NPin.Framework.SqlSugarCore.Abstractions;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Linq;
namespace NPin.Framework.SqlSugarCore.Repositories;
public class SqlSugarRepository<TEntity> : ISqlSugarRepository<TEntity>, IRepository<TEntity>
where TEntity : class, IEntity, new()
{
public ISqlSugarClient Db => GetDbContextAsync().Result;
public ISugarQueryable<TEntity> DbQueryable => GetDbContextAsync().Result.Queryable<TEntity>();
private ISugarDbContextProvider<ISqlSugarDbContext> _sugarDbContextProvider;
public IAsyncQueryableExecuter AsyncExecuter { get; }
public bool? IsChangeTrackingEnabled => false;
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider)
{
_sugarDbContextProvider = sugarDbContextProvider;
}
public virtual async Task<ISqlSugarClient> GetDbContextAsync()
{
var db = (await _sugarDbContextProvider.GetDbContextAsync()).SqlSugarClient;
return db;
}
public virtual async Task<SimpleClient<TEntity>> GetDbSimpleClientAsync()
{
var db = await GetDbContextAsync();
return new SimpleClient<TEntity>(db);
}
#region Abp模块
public virtual async Task<TEntity?> FindAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await GetFirstAsync(predicate);
}
public virtual async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await GetFirstAsync(predicate);
}
public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await this.DeleteAsync(predicate);
}
public virtual async Task DeleteDirectAsync(Expression<Func<TEntity, bool>> predicate,
CancellationToken cancellationToken = default)
{
await this.DeleteAsync(predicate);
}
public IQueryable<TEntity> WithDetails()
{
throw new NotImplementedException();
}
public IQueryable<TEntity> WithDetails(params Expression<Func<TEntity, object>>[] propertySelectors)
{
throw new NotImplementedException();
}
public Task<IQueryable<TEntity>> WithDetailsAsync()
{
throw new NotImplementedException();
}
public Task<IQueryable<TEntity>> WithDetailsAsync(params Expression<Func<TEntity, object>>[] propertySelectors)
{
throw new NotImplementedException();
}
public Task<IQueryable<TEntity>> GetQueryableAsync()
{
throw new NotImplementedException();
}
public virtual async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate,
bool includeDetails = false, CancellationToken cancellationToken = default)
{
return await GetListAsync(predicate);
}
public virtual async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false,
CancellationToken cancellationToken = default)
{
return await InsertReturnEntityAsync(entity);
}
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await InsertRangeAsync(entities.ToList());
}
public virtual async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await UpdateAsync(entity);
return entity;
}
public virtual async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await UpdateRangeAsync(entities.ToList());
}
public virtual async Task DeleteAsync(TEntity entity, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await DeleteAsync(entity);
}
public virtual async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await DeleteAsync(entities.ToList());
}
public virtual async Task<List<TEntity>> GetListAsync(bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await GetListAsync();
}
public virtual async Task<long> GetCountAsync(CancellationToken cancellationToken = default)
{
return await this.CountAsync();
}
public virtual async Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting,
bool includeDetails = false, CancellationToken cancellationToken = default)
{
return await GetPageListAsync(_ => true, skipCount, maxResultCount);
}
#endregion
#region 内置DB快捷操作
public virtual async Task<IDeleteable<TEntity>> AsDeleteable()
{
return (await GetDbSimpleClientAsync()).AsDeleteable();
}
public virtual async Task<IInsertable<TEntity>> AsInsertable(List<TEntity> insertObjs)
{
return (await GetDbSimpleClientAsync()).AsInsertable(insertObjs);
}
public virtual async Task<IInsertable<TEntity>> AsInsertable(TEntity insertObj)
{
return (await GetDbSimpleClientAsync()).AsInsertable(insertObj);
}
public virtual async Task<IInsertable<TEntity>> AsInsertable(TEntity[] insertObjs)
{
return (await GetDbSimpleClientAsync()).AsInsertable(insertObjs);
}
public virtual async Task<ISugarQueryable<TEntity>> AsQueryable()
{
return (await GetDbSimpleClientAsync()).AsQueryable();
}
public virtual async Task<ISqlSugarClient> AsSugarClient()
{
return (await GetDbSimpleClientAsync()).AsSugarClient();
}
public virtual async Task<ITenant> AsTenant()
{
return (await GetDbSimpleClientAsync()).AsTenant();
}
public virtual async Task<IUpdateable<TEntity>> AsUpdateable(List<TEntity> updateObjs)
{
return (await GetDbSimpleClientAsync()).AsUpdateable(updateObjs);
}
public virtual async Task<IUpdateable<TEntity>> AsUpdateable(TEntity updateObj)
{
return (await GetDbSimpleClientAsync()).AsUpdateable(updateObj);
}
public virtual async Task<IUpdateable<TEntity>> AsUpdateable()
{
return (await GetDbSimpleClientAsync()).AsUpdateable();
}
public virtual async Task<IUpdateable<TEntity>> AsUpdateable(TEntity[] updateObjs)
{
return (await GetDbSimpleClientAsync()).AsUpdateable(updateObjs);
}
#endregion
#region SimpleClient模块
public virtual async Task<int> CountAsync(Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).CountAsync(whereExpression);
}
public virtual async Task<bool> DeleteAsync(TEntity deleteObj)
{
if (deleteObj is ISoftDelete)
{
ReflectHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, deleteObj);
return await (await GetDbSimpleClientAsync()).UpdateAsync(deleteObj);
}
else
{
return await (await GetDbSimpleClientAsync()).DeleteAsync(deleteObj);
}
}
public virtual async Task<bool> DeleteAsync(List<TEntity> deleteObjs)
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
deleteObjs.ForEach(e => ReflectHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, e));
return await (await GetDbSimpleClientAsync()).UpdateRangeAsync(deleteObjs);
}
else
{
return await (await GetDbSimpleClientAsync()).DeleteAsync(deleteObjs);
}
}
public virtual async Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> whereExpression)
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
return await (await GetDbSimpleClientAsync()).AsUpdateable().SetColumns(nameof(ISoftDelete), true)
.Where(whereExpression).ExecuteCommandAsync() > 0;
}
else
{
return await (await GetDbSimpleClientAsync()).DeleteAsync(whereExpression);
}
}
public virtual async Task<bool> DeleteByIdAsync(dynamic id)
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
var entity = await GetByIdAsync(id);
//反射赋值
ReflectHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, entity);
return await UpdateAsync(entity);
}
else
{
return await (await GetDbSimpleClientAsync()).DeleteByIdAsync(id);
}
}
public virtual async Task<bool> DeleteByIdsAsync(dynamic[] ids)
{
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
var simpleClient = (await GetDbSimpleClientAsync());
var entities = await simpleClient.AsQueryable().In(ids).ToListAsync();
if (entities.Count == 0)
{
return false;
}
// 反射赋值
entities.ForEach(e => ReflectHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, e));
return await UpdateRangeAsync(entities);
}
else
{
return await (await GetDbSimpleClientAsync()).DeleteByIdAsync(ids);
}
}
public virtual async Task<TEntity> GetByIdAsync(dynamic id)
{
return await (await GetDbSimpleClientAsync()).GetByIdAsync(id);
}
public virtual async Task<TEntity> GetFirstAsync(Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).GetFirstAsync(whereExpression);
}
public virtual async Task<List<TEntity>> GetListAsync()
{
return await (await GetDbSimpleClientAsync()).GetListAsync();
}
public virtual async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).GetListAsync(whereExpression);
}
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression,
int pageNum, int pageSize)
{
return await (await GetDbSimpleClientAsync()).GetPageListAsync(whereExpression,
new PageModel() { PageIndex = pageNum, PageSize = pageSize });
}
public virtual async Task<List<TEntity>> GetPageListAsync(Expression<Func<TEntity, bool>> whereExpression,
int pageNum, int pageSize,
Expression<Func<TEntity, object>>? orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
{
return await (await GetDbSimpleClientAsync()).GetPageListAsync(whereExpression,
new PageModel { PageIndex = pageNum, PageSize = pageSize }, orderByExpression, orderByType);
}
public virtual async Task<TEntity> GetSingleAsync(Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).GetSingleAsync(whereExpression);
}
public virtual async Task<bool> InsertAsync(TEntity insertObj)
{
return await (await GetDbSimpleClientAsync()).InsertAsync(insertObj);
}
public virtual async Task<bool> InsertOrUpdateAsync(TEntity data)
{
return await (await GetDbSimpleClientAsync()).InsertOrUpdateAsync(data);
}
public virtual async Task<bool> InsertOrUpdateAsync(List<TEntity> datas)
{
return await (await GetDbSimpleClientAsync()).InsertOrUpdateAsync(datas);
}
public virtual async Task<bool> InsertRangeAsync(List<TEntity> insertObjs)
{
return await (await GetDbSimpleClientAsync()).InsertRangeAsync(insertObjs);
}
public virtual async Task<long> InsertReturnBigIdentityAsync(TEntity insertObj)
{
return await (await GetDbSimpleClientAsync()).InsertReturnBigIdentityAsync(insertObj);
}
public virtual async Task<TEntity> InsertReturnEntityAsync(TEntity insertObj)
{
return await (await GetDbSimpleClientAsync()).InsertReturnEntityAsync(insertObj);
}
public virtual async Task<int> InsertReturnIdentityAsync(TEntity insertObj)
{
return await (await GetDbSimpleClientAsync()).InsertReturnIdentityAsync(insertObj);
}
public virtual async Task<long> InsertReturnSnowflakeIdAsync(TEntity insertObj)
{
return await (await GetDbSimpleClientAsync()).InsertReturnSnowflakeIdAsync(insertObj);
}
public virtual async Task<bool> IsAnyAsync(Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).IsAnyAsync(whereExpression);
}
public virtual async Task<bool> UpdateAsync(TEntity updateObj)
{
return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj);
}
public virtual async Task<bool> UpdateAsync(Expression<Func<TEntity, TEntity>> columns,
Expression<Func<TEntity, bool>> whereExpression)
{
return await (await GetDbSimpleClientAsync()).UpdateAsync(columns, whereExpression);
}
public virtual async Task<bool> UpdateRangeAsync(List<TEntity> updateObjs)
{
return await (await GetDbSimpleClientAsync()).UpdateRangeAsync(updateObjs);
}
#endregion
}
public class SqlSugarRepository<TEntity, TKey> : SqlSugarRepository<TEntity>, ISqlSugarRepository<TEntity, TKey>,
IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>, new()
{
public SqlSugarRepository(ISugarDbContextProvider<ISqlSugarDbContext> sugarDbContextProvider) : base(
sugarDbContextProvider)
{
}
public virtual async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
{
await DeleteByIdAsync(id);
}
public virtual async Task DeleteManyAsync(IEnumerable<TKey> ids, bool autoSave = false,
CancellationToken cancellationToken = default)
{
await DeleteByIdsAsync(ids.Select(x => (object)x).ToArray());
}
public virtual async Task<TEntity?> FindAsync(TKey id, bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await GetByIdAsync(id);
}
public virtual async Task<TEntity> GetAsync(TKey id, bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await GetByIdAsync(id);
}
}

@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NPin.Framework.SqlSugarCore.Abstractions;
namespace NPin.Framework.SqlSugarCore;
public static class SqlSugarCoreExtensions
{
public static IServiceCollection AddNPinDbContext<TDbContext>(this IServiceCollection service)
where TDbContext : class, ISqlSugarDbContext
{
service.Replace(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(TDbContext),
ServiceLifetime.Transient));
return service;
}
public static IServiceCollection TryAddNPinDbContext<TDbContext>(this IServiceCollection service)
where TDbContext : class, ISqlSugarDbContext
{
service.TryAdd(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(TDbContext),
ServiceLifetime.Transient));
return service;
}
public static IServiceCollection AddNPinDbContext<TDbContext>(this IServiceCollection service,
Action<DbConnOptions> options)
where TDbContext : class, ISqlSugarDbContext
{
service.Configure<DbConnOptions>(options.Invoke);
service.AddNPinDbContext<TDbContext>();
return service;
}
}

@ -0,0 +1,111 @@
using System.Reflection;
using Microsoft.Extensions.Options;
using NPin.Framework.SqlSugarCore.Abstractions;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace NPin.Framework.SqlSugarCore;
public class SqlSugarDbConnectionCreator : ISqlSugarDbConnectionCreator, ITransientDependency
{
[DisablePropertyInjection] public Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
[DisablePropertyInjection] public Action<object, DataAfterModel> DataExecuted { get; set; }
[DisablePropertyInjection] public Action<object, DataFilterModel> DataExecuting { get; set; }
[DisablePropertyInjection] public Action<string, SugarParameter[]> OnLogExecuting { get; set; }
[DisablePropertyInjection] public Action<string, SugarParameter[]> OnLogExecuted { get; set; }
[DisablePropertyInjection] public Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
public DbConnOptions Options { get; }
public SqlSugarDbConnectionCreator(IOptions<DbConnOptions> options)
{
Options = options.Value;
}
public void SetDbAop(ISqlSugarClient currentDb)
{
currentDb.Aop.OnLogExecuting = this.OnLogExecuting;
currentDb.Aop.OnLogExecuted = this.OnLogExecuted;
currentDb.Aop.DataExecuting = this.DataExecuting;
currentDb.Aop.DataExecuted = this.DataExecuted;
OnSqlSugarClientConfig(currentDb);
}
public ConnectionConfig Build(Action<ConnectionConfig>? action = null)
{
var dbConnOptions = Options;
#region 组装Options
if (dbConnOptions.DbType is null)
{
throw new ArgumentException("DbType配置为空");
}
// 读写分离 slave模式
var slaveConfigs = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite)
{
if (dbConnOptions.ReadUrl is null)
{
throw new ArgumentException("读写分离开启,但读库配置为空");
}
var readConn = dbConnOptions.ReadUrl;
readConn.ForEach(s =>
{
// 如果是动态分库,连接串不能写死,需要动态添加,所以此处只配置共享库的连接
slaveConfigs.Add(new SlaveConnectionConfig { ConnectionString = s });
});
}
#endregion
#region 组装连接配置
var connectionConfig = new ConnectionConfig
{
ConfigId = ConnectionStrings.DefaultConnectionStringName,
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true,
SlaveConnectionConfigs = slaveConfigs,
// CodeFirst 非空值判断
ConfigureExternalServices = new ConfigureExternalServices
{
EntityService = (c, p) =>
{
if (new NullabilityInfoContext().Create(c).WriteState is NullabilityState.Nullable)
{
p.IsNullable = true;
}
EntityService(c, p);
}
},
// Aop
AopEvents = new AopEvents
{
DataExecuted = DataExecuted,
DataExecuting = DataExecuting,
OnLogExecuted = OnLogExecuted,
OnLogExecuting = OnLogExecuting
}
};
if (action is not null)
{
action.Invoke(connectionConfig);
}
#endregion
return connectionConfig;
}
}

@ -0,0 +1,238 @@
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NPin.Framework.SqlSugarCore.Abstractions;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Users;
namespace NPin.Framework.SqlSugarCore;
public class SqlSugarDbContext : ISqlSugarDbContext
{
private IAbpLazyServiceProvider LazyServiceProvider { get; }
public ISqlSugarClient SqlSugarClient { get; private set; }
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
public ICurrentTenant? CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
public IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
public IEntityChangeEventHelper EntityChangeEventHelper =>
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
{
SqlSugarClient = sqlSugarClient;
}
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
{
LazyServiceProvider = lazyServiceProvider;
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig;
connectionCreator.EntityService = EntityService;
connectionCreator.DataExecuting = DataExecuting;
connectionCreator.DataExecuted = DataExecuted;
connectionCreator.OnLogExecuting = OnLogExecuting;
connectionCreator.OnLogExecuted = OnLogExecuted;
SqlSugarClient = new SqlSugarClient(connectionCreator.Build());
connectionCreator.SetDbAop(SqlSugarClient);
}
/// <summary>
/// 上下文对象扩展
/// </summary>
/// <param name="sqlSugarClient"></param>
protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{
//需自定义扩展
if (IsSoftDeleteFilterEnabled)
{
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
}
if (IsMultiTenantFilterEnabled)
{
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == GuidGenerator.Create());
}
// 自定义其它Filter
CustomDataFilter(sqlSugarClient);
}
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
}
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{
}
protected virtual void DataExecuting(object? oldValue, DataFilterModel entityInfo)
{
// 审计日志
switch (entityInfo.OperationType)
{
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
if (!DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
break;
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGenerator.Create());
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{
//为空或者为默认最小值
if (oldValue is null || DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
//插入时需要租户id,先预留
if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
{
if (CurrentTenant is not null)
{
entityInfo.SetValue(CurrentTenant.Id);
}
}
break;
}
//领域事件
switch (entityInfo.OperationType)
{
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
}
break;
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//软删除,发布的是删除事件
if (entityInfo.EntityValue is ISoftDelete softDelete)
{
if (softDelete.IsDeleted)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
break;
case DataFilterType.DeleteByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
break;
}
}
protected virtual void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========NPin-SQL==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("============================");
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sb.ToString());
}
}
protected virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{
}
protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column)
{
}
public void Backup()
{
string directoryName = "database_backup";
string fileName = $"{SqlSugarClient.Ado.Connection.Database}_" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
switch (Options.DbType)
{
case DbType.MySql:
// mysql 只支持.net core
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.sql");
break;
case DbType.Sqlite:
// sqlite 只支持.net core
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db");
break;
case DbType.SqlServer:
// 第一个参数库名
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.bak");
break;
default:
throw new NotImplementedException("其他数据库备份未实现");
}
}
}

@ -0,0 +1,28 @@
using System.Data.Common;
using SqlSugar.DistributedSystem.Snowflake;
namespace NPin.Framework.SqlSugarCore;
public class SqlSugarDbContextCreationContext
{
public static readonly AsyncLocal<SqlSugarDbContextCreationContext> _current = new();
public static SqlSugarDbContextCreationContext Current => _current.Value;
public string ConnectionStringName { get; }
public string ConnectionString { get; }
// public DbConnection ExistsConnection { get; internal set; }
public SqlSugarDbContextCreationContext(string connectionStringName, string connectionString)
{
ConnectionStringName = connectionStringName;
ConnectionString = connectionString;
}
public static IDisposable Use(SqlSugarDbContextCreationContext context)
{
var previousValue = Current;
_current.Value = context;
return new DisposableAction(() => _current.Value = previousValue);
}
}

@ -0,0 +1,14 @@
using NPin.Framework.SqlSugarCore.Abstractions;
using Volo.Abp.Uow;
namespace NPin.Framework.SqlSugarCore.Uow;
public class SqlSugarDatabaseApi : IDatabaseApi
{
public ISqlSugarDbContext DbContext { get; }
public SqlSugarDatabaseApi(ISqlSugarDbContext dbContext)
{
DbContext = dbContext;
}
}

@ -0,0 +1,33 @@
using NPin.Framework.SqlSugarCore.Abstractions;
using Volo.Abp.Uow;
namespace NPin.Framework.SqlSugarCore.Uow;
public class SqlSugarTransactionApi : ITransactionApi, ISupportsRollback
{
private ISqlSugarDbContext _sqlSugarDbContext;
public SqlSugarTransactionApi(ISqlSugarDbContext sqlSugarDbContext)
{
_sqlSugarDbContext = sqlSugarDbContext;
}
public ISqlSugarDbContext GetDbContext()
{
return _sqlSugarDbContext;
}
public void Dispose()
{
}
public async Task CommitAsync(CancellationToken cancellationToken = default)
{
await _sqlSugarDbContext.SqlSugarClient.Ado.CommitTranAsync();
}
public async Task RollbackAsync(CancellationToken cancellationToken = default)
{
await _sqlSugarDbContext.SqlSugarClient.Ado.RollbackTranAsync();
}
}

@ -0,0 +1,195 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NPin.Framework.SqlSugarCore.Abstractions;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
using Check = Volo.Abp.Check;
namespace NPin.Framework.SqlSugarCore.Uow;
public class UnitOfWorkSqlSugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext>
where TDbContext : ISqlSugarDbContext
{
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
private readonly string MasterTenantDbDefaultName = DbConnOptions.MasterTenantDbDefaultName;
private ILogger<UnitOfWorkSqlSugarDbContextProvider<TDbContext>> Logger { get; set; }
private IServiceProvider ServiceProvider { get; set; }
private static AsyncLocalDbContextAccessor ContextInstance => AsyncLocalDbContextAccessor.Instance;
protected readonly IUnitOfWorkManager UnitOfWorkManager;
protected readonly IConnectionStringResolver ConnectionStringResolver;
protected readonly ICancellationTokenProvider CancellationTokenProvider;
protected readonly ICurrentTenant CurrentTenant;
public UnitOfWorkSqlSugarDbContextProvider(
IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver,
ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant,
ISqlSugarDbConnectionCreator dbConnectionCreator
)
{
UnitOfWorkManager = unitOfWorkManager;
ConnectionStringResolver = connectionStringResolver;
CancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant;
Logger = NullLogger<UnitOfWorkSqlSugarDbContextProvider<TDbContext>>.Instance;
_dbConnectionCreator = dbConnectionCreator;
}
public virtual async Task<TDbContext> GetDbContextAsync()
{
var connectionStringName = ConnectionStrings.DefaultConnectionStringName;
// 获取当前的连接字符串,未开启多租户时为空
var connectionString = await ResolveConnectionStringAsync(connectionStringName);
var dbContextKey = $"{GetType().FullName}_{connectionString}";
var unitOfWork = UnitOfWorkManager.Current;
if (unitOfWork == null || !unitOfWork.Options.IsTransactional)
{
// set if is null
ContextInstance.Current ??= (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
var dbContext = (TDbContext)ContextInstance.Current;
var output = DatabaseChange(dbContext, connectionStringName, connectionString);
// 提高体验取消uow的强制性
// 若不启用uow创建新db不开启事务
return output;
}
// 尝试当前工作单元获取db
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);
if (databaseApi == null)
{
databaseApi =
new SqlSugarDatabaseApi(CreateDbContextAsync(unitOfWork, connectionStringName, connectionString)
.Result);
unitOfWork.AddDatabaseApi(dbContextKey, databaseApi);
}
return (TDbContext)((SqlSugarDatabaseApi)databaseApi).DbContext;
}
/// <summary>
/// 解析连接字符串
/// </summary>
/// <param name="connectionStringName"></param>
/// <returns></returns>
protected virtual async Task<string> ResolveConnectionStringAsync(string connectionStringName)
{
if (typeof(TDbContext).IsDefined(typeof(IgnoreMultiTenancyAttribute), false))
{
using (CurrentTenant.Change(null))
{
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
}
}
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
}
protected virtual TDbContext DatabaseChange(TDbContext dbContext, string configId, string connectionString)
{
// 没有检测到使用多租户功能,默认使用默认库即可
if (string.IsNullOrWhiteSpace(connectionString))
{
connectionString = dbContext.Options.Url;
configId = DbConnOptions.TenantDbDefaultName;
}
var dbOption = dbContext.Options;
var db = dbContext.SqlSugarClient.AsTenant();
// 主库的Db切换当操作的是租户表的时候
if (CurrentTenant.Name == MasterTenantDbDefaultName)
{
//直接切换
configId = MasterTenantDbDefaultName;
var conStrOrNull = dbOption.GetDefaultMasterSaasMultiTenancy();
Check.NotNull(conStrOrNull, "租户主库未找到");
connectionString = conStrOrNull.Url;
}
// 租户Db的动态切换
// 二级缓存
var changed = false;
if (!db.IsAnyConnection(configId))
{
var config = _dbConnectionCreator.Build(options =>
{
options.DbType = dbOption.DbType!.Value;
options.ConfigId = configId; //设置库的唯一标识
options.IsAutoCloseConnection = true;
options.ConnectionString = connectionString;
});
//添加一个db到当前上下文 (Add部分不写上下文不会共享)
db.AddConnection(config);
changed = true;
}
var currentDb = db.GetConnection(configId) as ISqlSugarClient;
//设置Aop
if (changed)
{
_dbConnectionCreator.SetDbAop(currentDb);
}
dbContext.SetSqlSugarClient(currentDb);
return dbContext;
}
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName,
string connectionString)
{
var creationContext = new SqlSugarDbContextCreationContext(connectionStringName, connectionString);
//将连接key进行传值
using (SqlSugarDbContextCreationContext.Use(creationContext))
{
var dbContext = await CreateDbContextAsync(unitOfWork);
//获取到DB之后对多租户多库进行处理
var changedDbContext = DatabaseChange(dbContext, connectionStringName, connectionString);
return changedDbContext;
}
}
protected virtual async Task<TDbContext> CreateDbContextAsync(IUnitOfWork unitOfWork)
{
return unitOfWork.Options.IsTransactional
? await CreateDbContextWithTransactionAsync(unitOfWork)
: unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
}
protected virtual async Task<TDbContext> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
{
//事务key
var transactionApiKey = $"SqlsugarCore_{SqlSugarDbContextCreationContext.Current.ConnectionString}";
//尝试查找事务
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarTransactionApi;
//该db还没有进行开启事务
if (activeTransaction == null)
{
//获取到db添加事务即可
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
var transaction = new SqlSugarTransactionApi(
dbContext
);
unitOfWork.AddTransactionApi(transactionApiKey, transaction);
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
return dbContext;
}
else
{
return (TDbContext)activeTransaction.GetDbContext();
}
}
}

@ -0,0 +1,51 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.Core", "Framework\NPin.Framework.Core\NPin.Framework.Core.csproj", "{088B4948-AE5B-45B3-A9FA-B853671CFA05}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Framework", "Framework", "{F2A0A89E-A2F9-48CF-AD38-0318B5ACD11C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{86F61EBB-4ACC-459C-AB3C-C8D486C3017D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Web", "src\NPin.Web\NPin.Web.csproj", "{91A9536C-FD3D-4099-8459-6C1C5238C037}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.Mapster", "Framework\NPin.Framework.Mapster\NPin.Framework.Mapster.csproj", "{E6BCF6D7-A0B0-445D-AFE0-8F8C779FAB6B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.SqlSugarCore.Abstractions", "Framework\NPin.Framework.SqlSugarCore.Abstractions\NPin.Framework.SqlSugarCore.Abstractions.csproj", "{CBC83167-9806-41ED-98FA-105D38825A88}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPin.Framework.SqlSugarCore", "Framework\NPin.Framework.SqlSugarCore\NPin.Framework.SqlSugarCore.csproj", "{CC3D8269-F965-4C87-93F2-351AFEF9E3D2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{088B4948-AE5B-45B3-A9FA-B853671CFA05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{088B4948-AE5B-45B3-A9FA-B853671CFA05}.Debug|Any CPU.Build.0 = Debug|Any CPU
{088B4948-AE5B-45B3-A9FA-B853671CFA05}.Release|Any CPU.ActiveCfg = Release|Any CPU
{088B4948-AE5B-45B3-A9FA-B853671CFA05}.Release|Any CPU.Build.0 = Release|Any CPU
{91A9536C-FD3D-4099-8459-6C1C5238C037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91A9536C-FD3D-4099-8459-6C1C5238C037}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91A9536C-FD3D-4099-8459-6C1C5238C037}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91A9536C-FD3D-4099-8459-6C1C5238C037}.Release|Any CPU.Build.0 = Release|Any CPU
{E6BCF6D7-A0B0-445D-AFE0-8F8C779FAB6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6BCF6D7-A0B0-445D-AFE0-8F8C779FAB6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6BCF6D7-A0B0-445D-AFE0-8F8C779FAB6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6BCF6D7-A0B0-445D-AFE0-8F8C779FAB6B}.Release|Any CPU.Build.0 = Release|Any CPU
{CBC83167-9806-41ED-98FA-105D38825A88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBC83167-9806-41ED-98FA-105D38825A88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBC83167-9806-41ED-98FA-105D38825A88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBC83167-9806-41ED-98FA-105D38825A88}.Release|Any CPU.Build.0 = Release|Any CPU
{CC3D8269-F965-4C87-93F2-351AFEF9E3D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC3D8269-F965-4C87-93F2-351AFEF9E3D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC3D8269-F965-4C87-93F2-351AFEF9E3D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC3D8269-F965-4C87-93F2-351AFEF9E3D2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{088B4948-AE5B-45B3-A9FA-B853671CFA05} = {F2A0A89E-A2F9-48CF-AD38-0318B5ACD11C}
{91A9536C-FD3D-4099-8459-6C1C5238C037} = {86F61EBB-4ACC-459C-AB3C-C8D486C3017D}
{E6BCF6D7-A0B0-445D-AFE0-8F8C779FAB6B} = {F2A0A89E-A2F9-48CF-AD38-0318B5ACD11C}
{CBC83167-9806-41ED-98FA-105D38825A88} = {F2A0A89E-A2F9-48CF-AD38-0318B5ACD11C}
{CC3D8269-F965-4C87-93F2-351AFEF9E3D2} = {F2A0A89E-A2F9-48CF-AD38-0318B5ACD11C}
EndGlobalSection
EndGlobal

@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["NPin/NPin.csproj", "NPin/"]
RUN dotnet restore "NPin/NPin.csproj"
COPY . .
WORKDIR "/src/NPin"
RUN dotnet build "NPin.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "NPin.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "NPin.dll"]

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<RootNamespace>NPin</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>

@ -0,0 +1,6 @@
@NPin_HostAddress = http://localhost:5122
GET {{NPin_HostAddress}}/weatherforecast/
Accept: application/json
###

@ -0,0 +1,44 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:27344",
"sslPort": 44340
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5122",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7122;http://localhost:5122",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Loading…
Cancel
Save