EF中HasQueryFilter插入等效方案:限制WebsiteId并禁用默认Add方法
解决方案
1. 统一实体隔离接口
为所有需要网站隔离的实体定义通用接口,强制包含WebsiteId属性:
public interface IWebsiteScopedEntity { int WebsiteId { get; set; } }
让Post、Comment等业务实体实现该接口:
public class Post : IWebsiteScopedEntity { public int Id { get; set; } public string Title { get; set; } public int WebsiteId { get; set; } // 其他业务属性 } public class Comment : IWebsiteScopedEntity { public int Id { get; set; } public string Content { get; set; } public int WebsiteId { get; set; } // 其他业务属性 }
2. 在DbContext中维护当前网站ID
通过构造注入(如从IHttpContextAccessor获取当前请求的网站标识,或从配置/服务传入)在DbContext中保存当前网站ID:
public class AppDbContext : DbContext { public int CurrentWebsiteId { get; } public AppDbContext(DbContextOptions<AppDbContext> options, int currentWebsiteId) : base(options) { CurrentWebsiteId = currentWebsiteId; } // 定义业务DbSet public DbSet<Post> Posts { get; set; } public DbSet<Comment> Comments { get; set; } // 已有的查询过滤配置 protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Post>().HasQueryFilter(p => p.WebsiteId == CurrentWebsiteId); modelBuilder.Entity<Comment>().HasQueryFilter(c => c.WebsiteId == CurrentWebsiteId); } }
3. 禁用默认插入方法
使用new关键字隐藏基类的Add/AddRange方法,直接抛出异常阻止外部调用:
// 禁用单实体Add public new EntityEntry<TEntity> Add<TEntity>(TEntity entity) where TEntity : class { throw new InvalidOperationException("禁止使用默认Add方法,请调用AddScopedEntity或专属插入方法"); } // 禁用批量AddRange public new EntityEntry[] AddRange(params object[] entities) { throw new InvalidOperationException("禁止使用默认AddRange方法,请调用AddScopedRange"); } public new EntityEntry<TEntity> AddRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class { throw new InvalidOperationException("禁止使用默认AddRange方法,请调用AddScopedRange"); }
4. 实现安全的自定义插入方法
编写通用的插入方法,自动为实体设置当前网站ID,内部调用基类的Add方法(外部无法直接访问):
// 通用单实体插入 public EntityEntry<TEntity> AddScopedEntity<TEntity>(TEntity entity) where TEntity : class, IWebsiteScopedEntity { entity.WebsiteId = CurrentWebsiteId; return base.Add(entity); } // 通用批量插入 public EntityEntry[] AddScopedRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class, IWebsiteScopedEntity { foreach (var entity in entities) { entity.WebsiteId = CurrentWebsiteId; } return base.AddRange(entities); } // 保留原有专属方法(可选) public EntityEntry<Comment> AddComment(Comment comment) => AddScopedEntity(comment); public EntityEntry<Post> AddPost(Post post) => AddScopedEntity(post);
5. 最后一道防线:SaveChanges校验
为防止反射等绕过方式,在保存时校验所有新增实体的WebsiteId是否匹配当前网站:
public override int SaveChanges(bool acceptAllChangesOnSuccess) { ValidateScopedEntities(); return base.SaveChanges(acceptAllChangesOnSuccess); } public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { ValidateScopedEntities(); return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } private void ValidateScopedEntities() { var addedScopedEntities = ChangeTracker.Entries<IWebsiteScopedEntity>() .Where(e => e.State == EntityState.Added); foreach (var entry in addedScopedEntities) { if (entry.Entity.WebsiteId != CurrentWebsiteId) { throw new InvalidOperationException($"新增实体{entry.Entity.GetType().Name}的WebsiteId与当前网站不匹配"); } } }
内容的提问来源于stack exchange,提问作者Vaheed01
相关产品推荐
相关产品推荐

