You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
11 KiB

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<T> : DbContext where T : DbContext
{
public static readonly ILoggerFactory DefaultLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
public string? _tenantId;
private static readonly ValueComparer<Dictionary<string, string>> 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<T> options) : base(options)
{
this._tablePrefix = GetTablePrefix();
this._tenantId = this.GetService<ITenantService>().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<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
var entries = GetEntries();
BeforeSave(entries);
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
protected virtual void BeforeSave(List<EntityEntry> entries)
{
var userName = this.GetService<IHttpContextAccessor>().HttpContext?.User.Identity?.Name;
var tenant = this.GetService<ITenantService>().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<BaseEntity, string>("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)
{
2 years ago
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<Dictionary<string, string>>(nameof(BaseEntity.Properties)).
HasConversion(v => v.ToJson(), v => v.FromJson<Dictionary<string, string>>()!, 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<DateTime?>(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<DateTime>(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<string>();
}
}
});
//TreeEntity
if (entityType.IsAssignableTo(typeof(BaseTreeEntity<>).MakeGenericType(entityType)))
{
entityTypeBuilder.HasOne(nameof(BaseTreeEntity<BaseEntity>.Parent))
.WithMany(nameof(BaseTreeEntity<BaseEntity>.Children))
.HasForeignKey(new string[] { nameof(BaseTreeEntity<BaseEntity>.ParentId) })
.OnDelete(DeleteBehavior.NoAction);
entityTypeBuilder.Property(nameof(BaseTreeEntity<BaseEntity>.Name)).IsRequired();
entityTypeBuilder.Property(nameof(BaseTreeEntity<BaseEntity>.Number)).IsRequired().HasMaxLength(64);
entityTypeBuilder.HasIndex(nameof(BaseTreeEntity<BaseEntity>.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<object>());
applyEntityConfigurationMethod.MakeGenericMethod(entityType).Invoke(modelBuilder, new[] { Activator.CreateInstance(configType) });
}
});
}
}
public void CreateQueryFilter<TEntity>(ModelBuilder builder) where TEntity : BaseEntity
{
builder.Entity<TEntity>().HasQueryFilter(o =>
(this.DisableSoftDeleteFilter == true || !o.IsDeleted) &&
(this.DisableTenantFilter == true || o.TenantId == this._tenantId));
}
private List<EntityEntry> 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 ?? "";
}
}