过滤后的DbContext多租户Web应用程序 [英] Multi-tenancy web application with filtered dbContext

查看:577
本文介绍了过滤后的DbContext多租户Web应用程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是新来的ASP.Net MVC和多租户Web应用程序。我已经做了很多的阅读,但作为一个初学者我只是按照我的理解。所以我设法建立了一个示例场景的Web应用程序和需要解决它的结尾部分。希望此方案将是其他一些初学者有用的,但是欢迎任何其他的办法。在此先感谢

1)数据库2008年的SQLServer。

2)数据层:C#类库项目名为MyApplication.Data

 公共类APPUSER
{
    [键]
    公共虚拟INT AppUserID {搞定;组; }    [需要]
    公共虚拟INT TenantID {搞定;组; }    [需要]
    公共虚拟INT雇员{搞定;组; }    [需要]
    公共虚拟字符串登录{搞定;组; }    [需要]
    公共虚拟字符串密码{搞定;组; }
}公共类员工
{
    [键]
    公共虚拟INT雇员{搞定;组; }    [需要]
    公共虚拟INT TenantID {搞定;组; }    [需要]
    公共虚拟字符串全名{获得;组; }}公共类Tenant_SYS
{
    //这是从1开始的自动编号
    [键]
    公共虚拟INT TenantID {搞定;组; }    [需要]
    公共虚拟字符串TenantName {搞定;组; }
}

3)。业务层:类库MyApplication.Business
继<一个href=\"http://www.agile-$c$c.com/blog/entity-framework-$c$c-first-applying-global-filters/\">FilteredDbSet类礼遇:卓然Maksimovic

 公共类FilteredDbSet&LT; TEntity&GT; :IDbSet&LT; TEntity&gt;中IOrderedQueryable&LT; TEntity&gt;中IOrderedQueryable,IQueryable的&LT; TEntity&gt;中的IQueryable,IEnumerable的&LT; TEntity&gt;中的IEnumerable,IListSource
    其中,TEntity:类
    {
        私人只读DbSet&LT; TEntity&GT; _组;
        私人只读动作&LT; TEntity&GT; _initializeEntity;
        私人只读防爆pression&LT;&Func键LT; TEntity,布尔&GT;&GT; _过滤;        公共FilteredDbSet(的DbContext上下文)
            :这(context.Set&LT; TEntity&GT;(),I =&GT;真,NULL)
        {
        }        公共FilteredDbSet(的DbContext的背景下,前pression&LT;&Func键LT; TEntity,布尔&GT;&GT;过滤器)
            :这(context.Set&LT; TEntity&GT;(),过滤器,空)
        {
        }        公共FilteredDbSet(的DbContext的背景下,前pression&LT;&Func键LT; TEntity,布尔&GT;&GT;过滤器,动作&LT; TEntity&GT; initializeEntity)
            :这(context.Set&LT; TEntity&GT;(),过滤器,initializeEntity)
        {
        }        公共防爆pression&LT;&Func键LT; TEntity,布尔&GT;&GT;过滤
        {
            {返回_filter; }
        }        公众的IQueryable&LT; TEntity&GT;包括(字符串路径)
        {
            返回_set.Include(路径)。凡(_filter).AsQueryable();
        }        私人FilteredDbSet(DbSet&LT; TEntity&GT;设置,防爆pression&LT;&Func键LT; TEntity,布尔&GT;&GT;过滤器,动作&LT; TEntity&GT; initializeEntity)
        {
            _set =设置;
            _filter =滤波器;
            MatchesFilter = filter.Compile();
            _initializeEntity = initializeEntity;
        }        公共Func键&LT; TEntity,布尔&GT; MatchesFilter
        {
            得到;
            私人集;
        }        公众的IQueryable&LT; TEntity&GT;未过滤()
        {
            返回_set;
        }        公共无效ThrowIfEntityDoesNotMatchFilter(TEntity实体)
        {
            如果(!MatchesFilter(实体))
                抛出新ArgumentOutOfRangeException();
        }        公共TEntity添加(TEntity实体)
        {
            DoInitializeEntity(实体);
            ThrowIfEntityDoesNotMatchFilter(实体);
            返回_set.Add(实体);
        }        公共TEntity连接(TEntity实体)
        {
            ThrowIfEntityDoesNotMatchFilter(实体);
            返回_set.Attach(实体);
        }        公共TDerivedEntity创建&LT; TDerivedEntity&GT;()其中TDerivedEntity:类,TEntity
        {
            VAR实体= _set.Create&LT; TDerivedEntity&GT;();
            DoInitializeEntity(实体);
            返回(TDerivedEntity)实体;
        }        公共TEntity的Create()
        {
            变种实体= _set.Create();
            DoInitializeEntity(实体);
            返回实体;
        }        公共TEntity查找(params对象[]键值)
        {
            VAR实体= _set.Find(键值);
            如果(实体== NULL)
                返回null;
            //如果用户查询的过滤器以外的项目,然后我们抛出一个错误。
            //如果IDbSet了,我们将使用它的方法分离......可悲的是,我们必须确定在集合的项目之中。
            ThrowIfEntityDoesNotMatchFilter(实体);
            返回实体;
        }        公共TEntity删除(TEntity实体)
        {
            ThrowIfEntityDoesNotMatchFilter(实体);
            返回_set.Remove(实体);
        }        ///&LT;总结&gt;
        ///返回在本地缓存中的项目
        ///&LT; /总结&gt;
        ///&LT;&言论GT;
        ///它可以通过这个属性不匹配过滤器的添加/删除实体。
        ///使用的&lt;见CREF =ThrowIfEntityDoesNotMatchFilter/&GT;添加/从此集合删除项目之前的方法。
        ///&LT; /言论&GT;
        公众的ObservableCollection&LT; TEntity&GT;本地
        {
            {返回_set.Local; }
        }        IEnumerator的&LT; TEntity&GT; IEnumerable的&LT; TEntity&GT; .GetEnumerator()
        {            返回_set.Where(_filter).GetEnumerator();
        }        的IEnumerator IEnumerable.GetEnumerator()
        {
            返回_set.Where(_filter).GetEnumerator();
        }        类型IQueryable.ElementType
        {
            得到{typeof运算(TEntity)已; }
        }        防爆pression IQueryable.Ex pression
        {
            得到
            {
                返回_set.Where(_filter).EX pression;
            }
        }        IQueryProvider IQueryable.Provider
        {
            得到
            {
                返回_set.AsQueryable()提供。
            }
        }        布尔IListSource.ContainsListCollection
        {
            获得{返回false; }
        }        IList的IListSource.GetList()
        {
            抛出新的InvalidOperationException异常();
        }        无效DoInitializeEntity(TEntity实体)
        {
            如果(_initializeEntity!= NULL)
                _initializeEntity(实体);
        }       公共DbSqlQuery&LT; TEntity&GT; SqlQuery类(字符串SQL,params对象[]参数)
       {
            返回_set.SqlQuery(SQL,参数);
       }
    }公共类EFDbContext:的DbContext
{
    公共IDbSet&LT;&APPUSER GT; APPUSER {搞定;组; }
    公共IDbSet&LT; Tenant_SYS&GT;租户{搞定;组; }
    公共IDbSet&LT;员工&GT;员工{搞定;组; }    ///这确保命名约定不必是多个
    ///表可以是任何东西,我们为它们命名为
    保护覆盖无效OnModelCreating(DbModelBuilder模型构建器)
    {
        modelBuilder.Conventions.Remove&LT; PluralizingTableNameConvention&GT;();
    }    公共EFDbContext(INT tenantID = 0)//类的构造函数总是期待一个tenantID
    {
        //这里,Dbset可以公开的未经过滤的数据
        APPUSER =新FilteredDbSet&LT;&APPUSER GT;(本);
        租客=新FilteredDbSet&LT; Tenant_SYS&GT;(本);        //从这里,添加所有的多租户dbsets用过滤数据
        员工=新FilteredDbSet&LT;员工&GT;(这一点,D =&GT; d.TenantID == tenantID);
    }
}公共接口IEmployeeRepository
{
    IQueryable的&LT;员工&GT;员工{搞定; }
    无效SaveEmployee(员工员工);
    无效DeleteEmployee(员工员工);
    清单&LT;员工&GT; GetEmployeesSorted();
}公共类EFEmployeeRepository:IEmployeeRepository
{
    私人EFDbContext语境;    公共EFEmployeeRepository(中间体tenantID = 0)
    {
        上下文=新EFDbContext(tenantID);
    }    IQueryable的&LT;员工&GT; IEmployeeRepository.Employees
    {
        得到
        {
            返回context.Employee;
        }
    }    公共无效SaveEmployee(雇员雇员)
    {
        如果(Employee.EmployeeID == 0)
        {
            context.Employee.Add(员工);
        }        context.SaveChanges();
    }    公共无效DeleteEmployee(雇员雇员)
    {
        context.Employee.Remove(员工);
        context.SaveChanges();
    }    公开名单&LT;员工&GT; GetEmployeesSorted()
    {
        //这是刚才看到的结果是如何获取的功能。
        返回context.Employee.OrderBy(M =&GT; m.FullName)
                                    .ToList();
        //我没有用where条件的员工,因为过滤它应该被过滤的情况下进行处理
    }
}

4)WEB层:与Ninject DI ASP.NET MVC 4互联网应用程序

 公共类NinjectControllerFactory:DefaultControllerFactory
{
    私人的iKernel ninjectKernel;
    公共NinjectControllerFactory()
    {
        ninjectKernel =新StandardKernel();
        AddBindings();
    }
    保护覆盖一个IController GetControllerInstance(RequestContext的RequestContext的,
    键入controllerType)
    {
        返回controllerType == NULL
        ?空值
        (一个IController)ninjectKernel.Get(controllerType);
    }
    私人无效AddBindings()
    {
        ninjectKernel.Bind&所述; IAppUserRepository方式&gt;()到&lt; EFAppUserRepository&GT;();
        ninjectKernel.Bind&所述; IEmployeeRepository方式&gt;()到&lt; EFEmployeeRepository&GT;();    }
}

5)的控制器。这里的问题是

 公共类HomeController的:控制器
{
   IEmployeeRepository repoEmployee;   公众的HomeController(IEmployeeRepository EM prepository)
   {
       //我怎样才能确保员工是通过提供tenantID的会话变量过滤全局
       //请假设会话变量已经从登录模块验证后初始化。
       //将有很多应用程序中的这样控制器这就需要用到这些全球过滤的对象
        repoEmployee = EM prepository;
    }    公众的ActionResult指数()
    {
        //员工获取列表必须属于会话变量提供的tenantID
        //为什么这是需要的是确保一个租户的数据暴露给另一个租户意外类的,如果程序员未能将where条件        清单&LT;员工&GT;员工= repoEmployee.Employees.ToList();
        返回查看();
    }
}


解决方案

NInject DI可以做的魔力!只要你将拥有它创建会话变量thisTenantID登录程序。

在网络层:

 私人无效AddBindings()
{
    //修改来注入会话变量
    ninjectKernel.Bind&LT; EFDbContext方式&gt;()ToMethod(C =&gt;新建EFDbContext((int)的HttpContext.Current.Session [thisTenantID]));    ninjectKernel.Bind&所述; IAppUserRepository方式&gt;()到&lt; EFAppUserRepository&GT;();
    ninjectKernel.Bind<IEmployeeRepository>().To<EFEmployeeRepository>().WithConstructorArgument(\"tenantID\", C =&GT; (INT)HttpContext.Current.Session [thisTenantID]);
}

I am new to ASP.Net MVC and multi-tenancy web application. I have done lots of reading, but being a beginner I just follow what I understand. So I managed to built a sample scenario web application and need to solve the ending part of it. Hope this scenario will be useful for some other beginners as well, but would welcome any other approach. Thanks in advance

1) Database in SQLServer 2008.

2) Data layer: C# class library project called MyApplication.Data

public class AppUser
{
    [Key]
    public virtual int AppUserID { get; set; }

    [Required]
    public virtual int TenantID { get; set; }

    [Required]
    public virtual int EmployeeID { get; set; }

    [Required]
    public virtual string Login { get; set; }

    [Required]
    public virtual string Password { get; set; }
}

public class Employee
{
    [Key]
    public virtual int EmployeeID { get; set; }

    [Required]
    public virtual int TenantID { get; set; }

    [Required]
    public virtual string FullName { get; set; }

}

public class Tenant_SYS
{
    //this is an autonumber starting from 1
    [Key]
    public virtual int TenantID { get; set; }

    [Required]
    public virtual string TenantName { get; set; }
}

3). Business Layer: class library MyApplication.Business Following FilteredDbSet Class courtesy: Zoran Maksimovic

public class FilteredDbSet<TEntity> : IDbSet<TEntity>, IOrderedQueryable<TEntity>, IOrderedQueryable, IQueryable<TEntity>, IQueryable, IEnumerable<TEntity>, IEnumerable, IListSource
    where TEntity : class
    {
        private readonly DbSet<TEntity> _set;
        private readonly Action<TEntity> _initializeEntity;
        private readonly Expression<Func<TEntity, bool>> _filter;

        public FilteredDbSet(DbContext context)
            : this(context.Set<TEntity>(), i => true, null)
        {
        }

        public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter)
            : this(context.Set<TEntity>(), filter, null)
        {
        }

        public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
            : this(context.Set<TEntity>(), filter, initializeEntity)
        {
        }

        public Expression<Func<TEntity, bool>> Filter
        {
            get { return _filter; }
        }

        public IQueryable<TEntity> Include(string path)
        {
            return _set.Include(path).Where(_filter).AsQueryable();
        }

        private FilteredDbSet(DbSet<TEntity> set, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
        {
            _set = set;
            _filter = filter;
            MatchesFilter = filter.Compile();
            _initializeEntity = initializeEntity;
        }

        public Func<TEntity, bool> MatchesFilter
        {
            get;
            private set;
        }

        public IQueryable<TEntity> Unfiltered()
        {
            return _set;
        }

        public void ThrowIfEntityDoesNotMatchFilter(TEntity entity)
        {
            if (!MatchesFilter(entity))
                throw new ArgumentOutOfRangeException();
        }

        public TEntity Add(TEntity entity)
        {
            DoInitializeEntity(entity);
            ThrowIfEntityDoesNotMatchFilter(entity);
            return _set.Add(entity);
        }

        public TEntity Attach(TEntity entity)
        {
            ThrowIfEntityDoesNotMatchFilter(entity);
            return _set.Attach(entity);
        }

        public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
        {
            var entity = _set.Create<TDerivedEntity>();
            DoInitializeEntity(entity);
            return (TDerivedEntity)entity;
        }

        public TEntity Create()
        {
            var entity = _set.Create();
            DoInitializeEntity(entity);
            return entity;
        }

        public TEntity Find(params object[] keyValues)
        {
            var entity = _set.Find(keyValues);
            if (entity == null)
                return null;
            // If the user queried an item outside the filter, then we throw an error.
            // If IDbSet had a Detach method we would use it...sadly, we have to be ok with the item being in the Set.
            ThrowIfEntityDoesNotMatchFilter(entity);
            return entity;
        }

        public TEntity Remove(TEntity entity)
        {
            ThrowIfEntityDoesNotMatchFilter(entity);
            return _set.Remove(entity);
        }

        /// <summary>
        /// Returns the items in the local cache
        /// </summary>
        /// <remarks>
        /// It is possible to add/remove entities via this property that do NOT match the filter.
        /// Use the <see cref="ThrowIfEntityDoesNotMatchFilter"/> method before adding/removing an item from this collection.
        /// </remarks>
        public ObservableCollection<TEntity> Local
        {
            get { return _set.Local; }
        }

        IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
        {

            return _set.Where(_filter).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _set.Where(_filter).GetEnumerator();
        }

        Type IQueryable.ElementType
        {
            get { return typeof(TEntity); }
        }

        Expression IQueryable.Expression
        {
            get
            {
                return _set.Where(_filter).Expression;
            }
        }

        IQueryProvider IQueryable.Provider
        {
            get
            {
                return _set.AsQueryable().Provider;
            }
        }

        bool IListSource.ContainsListCollection
        {
            get { return false; }
        }

        IList IListSource.GetList()
        {
            throw new InvalidOperationException();
        }

        void DoInitializeEntity(TEntity entity)
        {
            if (_initializeEntity != null)
                _initializeEntity(entity);
        }

       public DbSqlQuery<TEntity> SqlQuery(string sql, params object[] parameters)
       {
            return _set.SqlQuery(sql, parameters);
       }
    }

public class EFDbContext : DbContext
{
    public IDbSet<AppUser> AppUser { get; set; }
    public IDbSet<Tenant_SYS> Tenant { get; set; }
    public IDbSet<Employee> Employee { get; set; }

    ///this makes sure the naming convention does not have to be plural
    ///tables can be anything we name them to be
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }

    public EFDbContext(int tenantID = 0)    //Constructor of the class always expect a tenantID
    {
        //Here, the Dbset can expose the unfiltered data            
        AppUser = new FilteredDbSet<AppUser>(this);
        Tenant = new FilteredDbSet<Tenant_SYS>(this);

        //From here, add all the multitenant dbsets with filtered data
        Employee = new FilteredDbSet<Employee>(this, d => d.TenantID == tenantID);
    }
}

public interface IEmployeeRepository
{
    IQueryable<Employee> Employees { get; }
    void SaveEmployee(Employee Employee);
    void DeleteEmployee(Employee Employee);
    List<Employee> GetEmployeesSorted();
}

public class EFEmployeeRepository : IEmployeeRepository
{
    private EFDbContext context;

    public EFEmployeeRepository(int tenantID = 0)  
    {
        context = new EFDbContext(tenantID);
    }

    IQueryable<Employee> IEmployeeRepository.Employees
    {
        get
        {
            return context.Employee;
        }
    }

    public void SaveEmployee(Employee Employee)
    {
        if (Employee.EmployeeID == 0)
        {
            context.Employee.Add(Employee);
        }

        context.SaveChanges();
    }

    public void DeleteEmployee(Employee Employee)
    {
        context.Employee.Remove(Employee);
        context.SaveChanges();
    }

    public List<Employee> GetEmployeesSorted()
    {
        //This is just a function to see the how the results are fetched. 
        return context.Employee.OrderBy(m => m.FullName)
                                    .ToList();
        //I haven't used where condition to filter the employees since it should be handled by the filtered context
    }
}

4) WEB Layer: ASP.NET MVC 4 Internet Application with Ninject DI

public class NinjectControllerFactory : DefaultControllerFactory
{
    private IKernel ninjectKernel;
    public NinjectControllerFactory()
    {
        ninjectKernel = new StandardKernel();
        AddBindings();
    }
    protected override IController GetControllerInstance(RequestContext requestContext,
    Type controllerType)
    {
        return controllerType == null
        ? null
        : (IController)ninjectKernel.Get(controllerType);
    }
    private void AddBindings()
    {
        ninjectKernel.Bind<IAppUserRepository>().To<EFAppUserRepository>();
        ninjectKernel.Bind<IEmployeeRepository>().To<EFEmployeeRepository>();

    }
}

5) Controller. Here is the Problem

public class HomeController : Controller
{
   IEmployeeRepository repoEmployee;

   public HomeController(IEmployeeRepository empRepository)
   {
       //How can I make sure that the employee is filtered globally by supplying a session variable of tenantID
       //Please assume that session variable has been initialized from Login modules after authentication.
       //There will be lots of Controllers like this in the application which need to use these globally filtered object
        repoEmployee = empRepository;
    }

    public ActionResult Index()
    {
        //The list of employees fetched must belong to the tenantID supplied by session variable
        //Why this is needed is to secure one tenant's data being exposed to another tenants accidently like,  if programmer fails to put where condition

        List<Employee> Employees = repoEmployee.Employees.ToList();
        return View();
    }
}

解决方案

NInject DI can do the magic !! Provided you will have a login routine which creates the session variable "thisTenantID".

In the Web Layer:

private void AddBindings()
{
    //Modified to inject session variable
    ninjectKernel.Bind<EFDbContext>().ToMethod(c => new EFDbContext((int)HttpContext.Current.Session["thisTenantID"]));

    ninjectKernel.Bind<IAppUserRepository>().To<EFAppUserRepository>();
    ninjectKernel.Bind<IEmployeeRepository>().To<EFEmployeeRepository>().WithConstructorArgument("tenantID", c => (int)HttpContext.Current.Session["thisTenantID"]);
}

这篇关于过滤后的DbContext多租户Web应用程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆