我是 ASP.Net MVC 和多租户 Web 应用程序的新手。我读了很多书,但作为一个初学者,我只是按照我理解的去做。所以我设法构建了一个示例场景 Web 应用程序,并且需要解决它的结尾部分。希望这个场景对其他一些初学者也有用,但欢迎任何其他方法。提前致谢
1) SQLServer 2008 中的数据库。
2)数据层:C#类库项目,名为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)。业务层:类库 MyApplication.Business 以下FilteredDbSet 类礼貌: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 层: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)控制器。这是问题所在
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();
}
}