using System.Diagnostics; using System.Reflection; using Autofac; using LinqToDB.EntityFrameworkCore; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using WTA.Shared.Attributes; using WTA.Shared.Domain; using WTA.Shared.Extensions; using WTA.Shared.Tenants; namespace WTA.Shared.Data; public abstract class BaseDbContext : DbContext where T : DbContext { public static readonly ILoggerFactory DefaultLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); }); public string? _tenantId; private static readonly ValueComparer> DictionaryValueComparer = new( (v1, v2) => v1 != null && v2 != null && v1.SequenceEqual(v2), o => o.GetHashCode()); private readonly string _tablePrefix; static BaseDbContext() { LinqToDBForEFTools.Initialize(); } public BaseDbContext(DbContextOptions options) : base(options) { this._tablePrefix = GetTablePrefix(); this._tenantId = this.GetService().TenantId; } public bool DisableSoftDeleteFilter { get; set; } public bool DisableTenantFilter { get; set; } public override int SaveChanges(bool acceptAllChangesOnSuccess) { var entries = GetEntries(); BeforeSave(entries); return base.SaveChanges(acceptAllChangesOnSuccess); } public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { var entries = GetEntries(); BeforeSave(entries); return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } protected virtual void BeforeSave(List entries) { var userName = this.GetService().HttpContext?.User.Identity?.Name; var tenant = this.GetService().TenantId; var now = DateTime.UtcNow; foreach (var item in entries.Where(o => o.State == EntityState.Added || o.State == EntityState.Modified || o.State == EntityState.Deleted)) { // 设置审计属性和租户 if (item.Entity is BaseEntity entity) { Debug.WriteLine($"{entity.Id},{entity.GetPropertyValue("Number")}"); if (item.State == EntityState.Added) { entity.CreatedOn = now; entity.CreatedBy = userName ?? "super"; entity.TenantId = tenant; entity.IsDisabled ??= false; entity.IsReadonly ??= false; } else if (item.State == EntityState.Modified) { entity.UpdatedOn = now; entity.UpdatedBy = userName; } else if (item.State == EntityState.Deleted) { if (entity is ISoftDeleteEntity) { item.State = EntityState.Modified; entity.IsDeleted = true; entity.DeletedOn = now; entity.DeletedBy = userName; } else if (entity.IsReadonly.HasValue && entity.IsReadonly.Value) { throw new Exception("内置数据无法删除"); } } entity.ConcurrencyStamp = Guid.NewGuid().ToString(); } } } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseLoggerFactory(DefaultLoggerFactory); optionsBuilder.EnableSensitiveDataLogging(); optionsBuilder.EnableDetailedErrors(); } protected override void OnModelCreating(ModelBuilder modelBuilder) { //默认配置 WebApp.Current.DbContextTypes.GetValueOrDefault(GetType()) ?.ForEach(entityType => { var entityTypeBuilder = modelBuilder.Entity(entityType); //实体 if (entityType.IsAssignableTo(typeof(BaseEntity))) { //软删除、租户过滤 //this.GetType().GetMethod(nameof(this.CreateQueryFilter))?.MakeGenericMethod(entityType).Invoke(this, new object[] { modelBuilder }); // //基类 entityTypeBuilder.HasKey(nameof(BaseEntity.Id)); entityTypeBuilder.Property(nameof(BaseEntity.Id)).ValueGeneratedNever(); entityTypeBuilder.Property(nameof(BaseEntity.IsDisabled)).IsRequired(); entityTypeBuilder.Property(nameof(BaseEntity.IsReadonly)).IsRequired(); entityTypeBuilder.Property(nameof(BaseEntity.CreatedOn)).IsRequired(); //行版本号 entityTypeBuilder.Property(nameof(BaseEntity.ConcurrencyStamp)).ValueGeneratedNever(); //扩展属性 entityTypeBuilder.Property>(nameof(BaseEntity.Properties)). HasConversion(v => v.ToJson(), v => v.FromJson>()!, DictionaryValueComparer); //表名 entityTypeBuilder.ToTable($"{this._tablePrefix}{entityTypeBuilder.Metadata.GetTableName()}"); //属性 entityTypeBuilder.Metadata.GetProperties().ForEach(prop => { if (prop.PropertyInfo != null) { //列注释 entityTypeBuilder.Property(prop.Name).HasComment(prop.PropertyInfo?.GetDisplayName()); if (prop.PropertyInfo!.PropertyType.GetUnderlyingType() == typeof(DateTime)) { //EF 默认使用 DateTimeKind.Unspecified 读取,数据库应存储 UTC 格式,客户端根据所在时区进行展示 if (prop.PropertyInfo!.PropertyType.IsNullableType()) { // HasConversion(toDBValue,fromDBValue) entityTypeBuilder.Property(prop.Name) .HasConversion(v => v.HasValue ? (v.Value.Kind == DateTimeKind.Utc ? v : v.Value.ToUniversalTime()) : null, v => v == null ? null : DateTime.SpecifyKind(v.Value, DateTimeKind.Utc)); } else { // HasConversion(toDBValue,fromDBValue) entityTypeBuilder.Property(prop.Name) .HasConversion(v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(), v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); } } if (prop.PropertyInfo!.PropertyType.GetUnderlyingType().IsEnum) { //枚举存为字符串 entityTypeBuilder.Property(prop.Name).HasConversion(); } } }); //TreeEntity if (entityType.IsAssignableTo(typeof(BaseTreeEntity<>).MakeGenericType(entityType))) { entityTypeBuilder.HasOne(nameof(BaseTreeEntity.Parent)) .WithMany(nameof(BaseTreeEntity.Children)) .HasForeignKey(new string[] { nameof(BaseTreeEntity.ParentId) }) .OnDelete(DeleteBehavior.NoAction); entityTypeBuilder.Property(nameof(BaseTreeEntity.Name)).IsRequired(); entityTypeBuilder.Property(nameof(BaseTreeEntity.Number)).IsRequired().HasMaxLength(64); entityTypeBuilder.HasIndex(nameof(BaseTreeEntity.Number)).IsUnique(); } } else if (entityType.IsAssignableTo(typeof(BaseViewEntity))) { //视图 entityTypeBuilder.HasNoKey().ToView($"{this._tablePrefix}{entityType.Name}"); } }); //自定义配置 var applyEntityConfigurationMethod = typeof(ModelBuilder) .GetMethods() .Single( e => e.Name == nameof(ModelBuilder.ApplyConfiguration) && e.ContainsGenericParameters && e.GetParameters().SingleOrDefault()?.ParameterType.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)); if (WebApp.Current.DbConfigTypes.TryGetValue(GetType(), out var configTypes)) { configTypes.ForEach(configType => { var interfaces = configType.GetInterfaces() .Where(type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)) .ToList(); foreach (var item in interfaces) { var entityType = item.GetGenericArguments()[0]; var entityTypeBuilder = modelBuilder.GetType().GetMethods() .FirstOrDefault(o => o.Name == "Entity" && o.IsGenericMethod)? .MakeGenericMethod(new Type[] { entityType }) .Invoke(modelBuilder, Array.Empty()); applyEntityConfigurationMethod.MakeGenericMethod(entityType).Invoke(modelBuilder, new[] { Activator.CreateInstance(configType) }); } }); } } public void CreateQueryFilter(ModelBuilder builder) where TEntity : BaseEntity { builder.Entity().HasQueryFilter(o => (this.DisableSoftDeleteFilter == true || !o.IsDeleted) && (this.DisableTenantFilter == true || o.TenantId == this._tenantId)); } private List GetEntries() { this.ChangeTracker.DetectChanges(); var entries = this.ChangeTracker.Entries().ToList(); return entries; } private string GetTablePrefix() { var prefix = this.GetType().GetCustomAttributes() .Where(o => o.GetType().IsGenericType && o.GetType().GetGenericTypeDefinition() == typeof(ModuleAttribute<>)) .Select(o => o as ITypeAttribute) .Select(o => o!.Type.Name) .FirstOrDefault()? .TrimEnd("Module"); if (!string.IsNullOrEmpty(prefix)) { prefix = $"{prefix}_"; } return prefix ?? ""; } }