¶Ô±ÈÐÂÎļþ |
| | |
| | | using Admin.NET.Core; |
| | | using Admin.NET.Core.Entity; |
| | | using Furion; |
| | | using Furion.DatabaseAccessor; |
| | | using Microsoft.EntityFrameworkCore; |
| | | using Microsoft.EntityFrameworkCore.Diagnostics; |
| | | using Microsoft.EntityFrameworkCore.Metadata; |
| | | using Microsoft.EntityFrameworkCore.Metadata.Builders; |
| | | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; |
| | | using System.Linq.Expressions; |
| | | using Yitter.IdGenerator; |
| | | |
| | | namespace Admin.NET.EntityFramework.Core |
| | | { |
| | | [AppDbContext("DefaultConnection", DbProvider.SqlServer)] |
| | | public class DefaultDbContext : AppDbContext<DefaultDbContext>, IModelBuilderFilter |
| | | { |
| | | public DefaultDbContext(DbContextOptions<DefaultDbContext> options) : base(options) |
| | | { |
| | | // å¯ç¨å®ä½æ°æ®æ´æ¹çå¬ |
| | | EnabledEntityChangedListener = true; |
| | | |
| | | // 忽ç¥ç©ºå¼æ´æ° |
| | | InsertOrUpdateIgnoreNullValues = true; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// è·åç§æ·Id |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | //public object GetTenantId() |
| | | //{ |
| | | // // æµç¨ä¸æ²¡æç¨å°å¤ç§æ· è¿éé»è®¤è¿åä¸ä¸ªç§æ· |
| | | // if (App.User == null) return 142307070918780; |
| | | // return Convert.ToInt64(App.User.FindFirst(ClaimConst.TENANT_ID)?.Value); |
| | | //} |
| | | |
| | | protected override void OnModelCreating(ModelBuilder builder) |
| | | { |
| | | if (Database.ProviderName == DbProvider.Sqlite) |
| | | { |
| | | // SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations |
| | | // here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations |
| | | // To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset |
| | | // use the DateTimeOffsetToBinaryConverter |
| | | // Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754 |
| | | // This only supports millisecond precision, but should be sufficient for most use cases. |
| | | foreach (var entityType in builder.Model.GetEntityTypes()) |
| | | { |
| | | var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset) |
| | | || p.PropertyType == typeof(DateTimeOffset?)); |
| | | foreach (var property in properties) |
| | | { |
| | | builder |
| | | .Entity(entityType.Name) |
| | | .Property(property.Name) |
| | | .HasConversion(new DateTimeOffsetToBinaryConverter()); |
| | | } |
| | | } |
| | | } |
| | | // å¤çmysqlæ¶åºé®é¢ https://gitee.com/dotnetchina/Furion/issues/I3RSCO#note_5685893_link |
| | | else if (Database.ProviderName == DbProvider.MySql || Database.ProviderName == DbProvider.MySqlOfficial) |
| | | { |
| | | var converter = new ValueConverter<DateTimeOffset, DateTime>(v => v.LocalDateTime, v => v); |
| | | |
| | | // æ«æç¨åºéï¼è·åæ°æ®åºå®ä½ç¸å
³ç±»å |
| | | var types = App.EffectiveTypes.Where(t => (typeof(IPrivateEntity).IsAssignableFrom(t) || typeof(IPrivateModelBuilder).IsAssignableFrom(t)) |
| | | && t.IsClass && !t.IsAbstract && !t.IsGenericType && !t.IsInterface && !t.IsDefined(typeof(ManualAttribute), true)); |
| | | |
| | | if (types.Any()) |
| | | { |
| | | foreach (var item in types) |
| | | { |
| | | if (item.IsSubclassOf(typeof(DEntityBase)) || item.IsSubclassOf(typeof(EntityBase))) |
| | | { |
| | | foreach (var property in item.GetProperties()) |
| | | { |
| | | if (property.PropertyType == typeof(DateTimeOffset?) || property.PropertyType == typeof(DateTimeOffset)) |
| | | { |
| | | builder.Entity(item).Property(property.Name).HasConversion(converter); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | base.OnModelCreating(builder); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// é
ç½®ç§æ·Idè¿æ»¤å¨ |
| | | /// </summary> |
| | | /// <param name="modelBuilder"></param> |
| | | /// <param name="entityBuilder"></param> |
| | | /// <param name="dbContext"></param> |
| | | /// <param name="dbContextLocator"></param> |
| | | public void OnCreating(ModelBuilder modelBuilder, EntityTypeBuilder entityBuilder, DbContext dbContext, Type dbContextLocator) |
| | | { |
| | | // é
ç½®åå é¤è¿æ»¤å¨ |
| | | LambdaExpression expression = FakeDeleteQueryFilterExpression(entityBuilder, dbContext); |
| | | if (expression != null) |
| | | entityBuilder.HasQueryFilter(expression); |
| | | // é
ç½®æ°æ®æéå¨æè¡¨è¾¾å¼ |
| | | LambdaExpression dataScopesExpression = DataScopesFilterExpression(entityBuilder, dbContext); |
| | | if (dataScopesExpression != null) |
| | | entityBuilder.HasQueryFilter(dataScopesExpression); |
| | | } |
| | | |
| | | protected override void SavingChangesEvent(DbContextEventData eventData, InterceptionResult<int> result) |
| | | { |
| | | // è·åå½åäºä»¶å¯¹åºä¸ä¸æ |
| | | var dbContext = eventData.Context; |
| | | // è·åæææ´æ¹ï¼å é¤ï¼æ°å¢çå®ä½ï¼ä½æé¤å®¡è®¡å®ä½ï¼é¿å
æ»å¾ªç¯ï¼ |
| | | var entities = dbContext.ChangeTracker.Entries() |
| | | .Where(u => u.Entity.GetType() != typeof(SysLogAudit) && u.Entity.GetType() != typeof(SysLogOp) && |
| | | u.Entity.GetType() != typeof(SysLogVis) && u.Entity.GetType() != typeof(SysLogEx) && |
| | | (u.State == EntityState.Modified || u.State == EntityState.Deleted || u.State == EntityState.Added)).ToList(); |
| | | if (entities == null || entities.Count < 1) return; |
| | | |
| | | //// 夿æ¯å¦æ¯æ¼ç¤ºç¯å¢ |
| | | //var demoEnvFlag = App.GetService<ISysConfigService>().GetDemoEnvFlag().GetAwaiter().GetResult(); |
| | | //if (demoEnvFlag) |
| | | //{ |
| | | // var sysUser = entities.Find(u => u.Entity.GetType() == typeof(SysUser)); |
| | | // if (sysUser == null || string.IsNullOrEmpty((sysUser.Entity as SysUser).LastLoginTime.ToString())) // æé¤ç»å½ |
| | | // throw Oops.Oh(ErrorCode.D1200); |
| | | //} |
| | | |
| | | // å½åæä½è
ä¿¡æ¯ |
| | | var userId = App.User?.FindFirst(ClaimConst.CLAINM_USERID)?.Value; |
| | | //读åçç¨æ·åæ¹ä¸º æµç§°ï¼è䏿¯è´¦å· ãEditby shaocx,2024-04-20ã |
| | | //var userName = App.User?.FindFirst(ClaimConst.CLAINM_ACCOUNT)?.Value; |
| | | var userName = App.User?.FindFirst(ClaimConst.CLAINM_NAME)?.Value; |
| | | // å½åæä½è
æºæä¿¡æ¯ |
| | | var orgId = App.User?.FindFirst(ClaimConst.CLAINM_ORGID)?.Value; |
| | | var orgName = App.User?.FindFirst(ClaimConst.CLAINM_ORGNAME)?.Value; |
| | | |
| | | foreach (var entity in entities) |
| | | { |
| | | if (entity.Entity.GetType().IsSubclassOf(typeof(DEntityBase))) |
| | | { |
| | | var obj = entity.Entity as DEntityBase; |
| | | if (entity.State == EntityState.Added) |
| | | { |
| | | obj.Id = obj.Id == 0 ? YitIdHelper.NextId() : obj.Id; |
| | | obj.CreatedTime = DateTimeOffset.Now; |
| | | if (!string.IsNullOrEmpty(userId)) |
| | | { |
| | | obj.CreatedUserId = long.Parse(userId); |
| | | obj.CreatedUserName = userName; |
| | | if (entity.Entity.GetType().GetInterface(typeof(IDataPermissions).Name) != null) |
| | | { |
| | | ((IDataPermissions)obj).CreatedUserOrgId = long.Parse(orgId); |
| | | ((IDataPermissions)obj).CreatedUserOrgName = orgName; |
| | | } |
| | | } |
| | | } |
| | | else if (entity.State == EntityState.Modified) |
| | | { |
| | | // æé¤å建人 |
| | | entity.Property(nameof(DEntityBase.CreatedUserId)).IsModified = false; |
| | | entity.Property(nameof(DEntityBase.CreatedUserName)).IsModified = false; |
| | | // æé¤åå»ºæ¥æ |
| | | entity.Property(nameof(DEntityBase.CreatedTime)).IsModified = false; |
| | | |
| | | obj.UpdatedTime = DateTimeOffset.Now; |
| | | if (!string.IsNullOrEmpty(userId)) |
| | | { |
| | | obj.UpdatedUserId = long.Parse(userId); |
| | | obj.UpdatedUserName = userName; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æå»ºç§æ·Id以ååå é¤è¿æ»¤å¨ |
| | | /// </summary> |
| | | /// <param name="entityBuilder"></param> |
| | | /// <param name="dbContext"></param> |
| | | /// <param name="isDeletedKey"></param> |
| | | /// <param name="filterValue"></param> |
| | | /// <returns></returns> |
| | | protected static LambdaExpression FakeDeleteQueryFilterExpression(EntityTypeBuilder entityBuilder, DbContext dbContext, string onTableTenantId = null, string isDeletedKey = null, object filterValue = null) |
| | | { |
| | | //onTableTenantId ??= "TenantId"; |
| | | isDeletedKey ??= "IsDeleted"; |
| | | IMutableEntityType metadata = entityBuilder.Metadata; |
| | | //if (metadata.FindProperty(onTableTenantId) == null && metadata.FindProperty(isDeletedKey) == null) |
| | | //{ |
| | | // return null; |
| | | //} |
| | | //è§£å³å®ä½ç»§æ¿æ¥éé®é¢ï¼åºç±»è¡¨ææIsDeletedãTenantIdåæ®µ |
| | | if (metadata.BaseType != null) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | Expression finialExpression = Expression.Constant(true); |
| | | ParameterExpression parameterExpression = Expression.Parameter(metadata.ClrType, "u"); |
| | | |
| | | |
| | | // åå é¤è¿æ»¤å¨ |
| | | if (metadata.FindProperty(isDeletedKey) != null) |
| | | { |
| | | ConstantExpression constantExpression = Expression.Constant(isDeletedKey); |
| | | ConstantExpression right = Expression.Constant(filterValue ?? false); |
| | | var fakeDeleteQueryExpression = Expression.Equal(Expression.Call(typeof(EF), "Property", new Type[1] |
| | | { |
| | | typeof(bool) |
| | | }, parameterExpression, constantExpression), right); |
| | | finialExpression = Expression.AndAlso(finialExpression, fakeDeleteQueryExpression); |
| | | } |
| | | |
| | | return Expression.Lambda(finialExpression, parameterExpression); |
| | | } |
| | | |
| | | #region æ°æ®æé |
| | | |
| | | /// <summary> |
| | | /// è·åç¨æ·Id |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | public object GetUserId() |
| | | { |
| | | if (App.User == null) return null; |
| | | return App.User.FindFirst(ClaimConst.CLAINM_USERID)?.Value; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// è·åæ°æ®èå´ |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | public List<object> GetDataScopes() |
| | | { |
| | | var userId = this.GetUserId(); |
| | | if (userId == null) |
| | | { |
| | | return new List<object>(); |
| | | } |
| | | |
| | | var dataScopes = JsonUtil.FromJson<List<object>>(App.User.FindFirst(ClaimConst.DATA_SCOPES)?.Value); |
| | | if (dataScopes != null) |
| | | { |
| | | return dataScopes; |
| | | } |
| | | return new List<object>(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// æå»ºæ°æ®èå´è¿æ»¤å¨ |
| | | /// </summary> |
| | | /// <param name="entityBuilder"></param> |
| | | /// <param name="dbContext"></param> |
| | | /// <param name="onTableCreatedUserId"></param> |
| | | /// <param name="onTableCreatedUserOrgId"></param> |
| | | /// <param name="filterValue"></param> |
| | | /// <returns></returns> |
| | | protected LambdaExpression DataScopesFilterExpression(EntityTypeBuilder entityBuilder, DbContext dbContext, string onTableCreatedUserId = null, string onTableCreatedUserOrgId = null) |
| | | { |
| | | onTableCreatedUserId ??= nameof(IDataPermissions.CreatedUserId);//ç¨æ·idåæ®µ |
| | | onTableCreatedUserOrgId ??= nameof(IDataPermissions.CreatedUserOrgId);//ç¨æ·é¨é¨å段 |
| | | |
| | | IMutableEntityType metadata = entityBuilder.Metadata; |
| | | if (metadata.FindProperty(onTableCreatedUserId) == null || metadata.FindProperty(onTableCreatedUserOrgId) == null) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | Expression finialExpression = Expression.Constant(true); |
| | | ParameterExpression parameterExpression = Expression.Parameter(metadata.ClrType, "u"); |
| | | |
| | | // ä¸ªäººç¨æ·æ°æ®è¿æ»¤å¨ |
| | | if (metadata.FindProperty(onTableCreatedUserId) != null) |
| | | { |
| | | ConstantExpression constantExpression = Expression.Constant(onTableCreatedUserId); |
| | | MethodCallExpression right = Expression.Call(Expression.Constant(dbContext), dbContext.GetType().GetMethod("GetUserId")); |
| | | finialExpression = Expression.AndAlso(finialExpression, Expression.Equal(Expression.Call(typeof(EF), "Property", new Type[1] |
| | | { |
| | | typeof(object) |
| | | }, parameterExpression, constantExpression), right)); |
| | | } |
| | | |
| | | //æ°æ®æéè¿æ»¤å¨ |
| | | if (metadata.FindProperty(onTableCreatedUserOrgId) != null) |
| | | { |
| | | ConstantExpression constantExpression = Expression.Constant(onTableCreatedUserOrgId); |
| | | |
| | | MethodCallExpression dataScopesLeft = Expression.Call(Expression.Constant(dbContext), dbContext.GetType().GetMethod("GetDataScopes")); |
| | | var firstOrDefaultCall = Expression.Call(typeof(EF), "Property", new Type[1] |
| | | { |
| | | typeof(object) |
| | | }, parameterExpression, constantExpression); |
| | | |
| | | var createdUserOrgIdQueryExpression = Expression.Call(dataScopesLeft, typeof(List<object>).GetMethod("Contains"), firstOrDefaultCall); |
| | | |
| | | finialExpression = Expression.Or(finialExpression, createdUserOrgIdQueryExpression); |
| | | } |
| | | |
| | | return Expression.Lambda(finialExpression, parameterExpression); |
| | | } |
| | | |
| | | #endregion æ°æ®æé |
| | | } |
| | | } |