代码之家  ›  专栏  ›  技术社区  ›  Marek Urbanowicz user7125929

如何向EF Core中DbContext中的每个查询添加参数以保护租户数据?

  •  3
  • Marek Urbanowicz user7125929  · 技术社区  · 7 年前

    我正在用共享表构建一种多租户应用程序。NET Core 2.0和EF Core。

    如果有必要的话,我还将通用存储库与工作单元一起使用。

    我想让它得到适当的保护,同时避免重复逻辑,所以我想如果有可能以某种方式修改DbContext,我正在使用它来为每个查找操作添加如下内容: entity => entity.tenantId == userContext.tenantId .

    我还必须确保在创建正确的租户时应用了正确的租户ID,并且不授权更新其他租户属性,但到目前为止,此逻辑包含在服务层中-如果我对此方法有错误,请更正我?

    IUserContext是在域抽象中定义的,应用层以不同的方式实现它(API或Web App),但我不确定当数据层执行这种逻辑时,它是否不是代码气味/反模式?(恐怕是的)。

    这个逻辑应该转到服务(然后必须重复多次,我认为这不是一个好主意)、DbContext还是应该以某种方式调整存储库?

    2 回复  |  直到 6 年前
        1
  •  3
  •   Harald Coppoolse    4 年前

    所以你想要的是如果有人写一些像这样的Linq语句

    var result = myDbcontext.myDbSet.SomeLinq(...)
    

    从内部来看

    var result = myDbContext.myDbSet
        .Where(entity => entity.tenantId == userContext.tenantId)
        .SomeLinq(...)
    

    所以你应该做的是,当用户认为他们可以访问 myDbContext.myDbSet ,它们实际上得到了 tenantId == userContext.tenantId

    我认为最好的解决方案是创建一个类,该类为DbContext中的每个DbSet公开IQueryable,并隐藏实际的DbContext。

    类似这样:

    class MyOriginalDbContext : DbContext
    {
        public DbSet<Student> Students {get; set;}
        public DbSet<Teacher> Teachers {get; set;}
        public DbSet<ClassRoom> ClassRooms {get; set;}
        ...
    }
    
    public MyLimitedContext : IDisposable
    {
        // to be filled in constructor
        private readonly MyOriginalDbcontext dbContext = ...
        private readonly int tenantId = ...
    
        IQueryable<Student> Students
        {
            get
            {
                return this.dbContext.Students
                    .Where(student => student.tenantId == tenantId);
            }
        }
    
        IQueryable<Student> Teachers
        {
            get
            {
                return this.dbContext.Teachers
                    .Where(teacher => teacher.tenantId == tenantId);
            }
        }
        ...
    

    用户不会注意到区别:

    using (var dbContext = new MyLimitedContext(...))
    {
         var TeachersWithTheirStudents = dbContext.Teachers
             .Join(dbContext.Student)
             .GroupBy(teacher => teacher.Id,
                ...
    }
    
        2
  •  0
  •   Jonatan Dragon    6 年前

    您可以使用全局查询筛选器。阅读更多信息 here .

    此类过滤器自动应用于涉及以下内容的任何LINQ查询 这些实体类型,包括间接引用的实体类型,例如 通过使用Include或direct navigation属性引用

    例如:

    public class Blog
    {
        private string _tenantId;
    
        public int BlogId { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
    
        public List<Post> Posts { get; set; }
    }
    
    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public bool IsDeleted { get; set; }
    
        public int BlogId { get; set; }
        public Blog Blog { get; set; }
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");
    
        // Configure entity filters
        modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
        modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
    }
    

    如果需要,您甚至可以禁用单个LINQ查询的筛选器:

    blogs = db.Blogs
        .Include(b => b.Posts)
        .IgnoreQueryFilters()
        .ToList();