I am trying to solve problem with updating entity from form. I have typed view and form for entity Post and post has collection of pictures.
public class Post : IEntity
{
public virtual int Id{ get; set; }
[Required(ErrorMessage = "Každý článek musí mít titulek")]
[MaxLength(250, ErrorMessage ="Nadpis může mít maximálně 250 znaků")]
public virtual string Title { get; set; }
public virtual string Annotation { get; set; }
[AllowHtml]
public virtual string Content { get; set; }
public virtual User Author { get; set; }
public virtual DateTime CreationDate { get; set; }
public virtual Rating Rating { get; set; }
public virtual string PreviewImageName { get; set; }
public virtual string ContentImageName { get; set; }
public virtual Category Category { get; set; }
public virtual IList<Tag> Tags { get; set; }
public virtual IList<BlogImage>Gallery { get; set; }
}
its mapped as one-to-many.
<bag name="Gallery" lazy="true" inverse="true"
batch-size="25" cascade="all-delete-orphan">
<key column="post_id" />
<one-to-many class="BlogImage" />
</bag>
Now i am trying to add a picture from input to this collection. (Updating of all other columns is working just fine). So I take picture from input, create Picture object, persist it and then use Add(method) on Gallery collection on Post entity. Then I use update on post. But its not working. It throws exception: HibernateException: Illegal attempt to associate a collection with two open sessions. Anyone can see a problem? Thank you very much.
public class DaoBase<T> : IDaoBase<T> where T : class, IEntity
{
protected ISession session;
protected DaoBase()
{
session = NHibernateHelper.Session;
}
public object Create(T entity)
{
object o;
using (ITransaction transaction = session.BeginTransaction())
{
o = session.Save(entity);
transaction.Commit();
}
return o;
}
public void Delete(T entity)
{
using (ITransaction transaction = session.BeginTransaction())
{
session.Delete(entity);
transaction.Commit();
}
}
public IList<T> GetAll()
{
return session.QueryOver<T>().List<T>();
}
public T GetById(int id)
{
return session.CreateCriteria<T>().Add(Restrictions.Eq("Id", id)).UniqueResult<T>();
}
public void Update(T entity)
{
using (ITransaction transaction = session.BeginTransaction())
{
session.Update(entity);
transaction.Commit();
}
}
}
}
And NHibernateHelper code as requested.
namespace DataAccess
{
public class NHibernateHelper
{
private static ISessionFactory _factory;
public static ISession Session
{
get
{
if (_factory == null)
{
var cfg = new Configuration();
_factory = cfg.Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hibernate.cfg.xml"))
.BuildSessionFactory();
}
return _factory.OpenSession();
}
}
}
}
So I'm going to guess that your create you you Picture object using 1 DAO and update the Post with a different DAO. Your NHibernate helper is just handing out new Sessions every time a Session is asked for. you want to share the same session per web request.
I would ditch the whole NHibernateHelper concept and just make the changes to you Global.asax.
See my answer here
But what it boils down to is
create you SessionFactory in Application_Start, storing it in a static property of Global.asax
Create a Current Session Property that maps to an item in HttpContext.
Application_BeginRequest to open the session
Application_EndRequest to cleanup
I'd also look at modifying you DAO to not always create a new transaction. You probably want all the db changes you make in a single request to be in a single transaction.
Here's code for the attribute
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TransactionAttribute : ActionFilterAttribute
{
private ITransaction Transaction { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Transaction = MvcApplication.CurrentSession.BeginTransaction(IsolationLevel.ReadCommitted);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (Transaction.IsActive)
{
if (filterContext.Exception == null)
{
Transaction.Commit();
}
else
{
Transaction.Rollback();
}
}
}
}
and I like to use a generic repository to show you how to use either a transaction started outside of the repo or create one if none exists.
public class Repository<T> : IRepository<T> where T : EntityBase
{
private readonly ISession _session;
#region constructor
public Repository(ISession session)
{
_session = session;
}
#endregion
#region Transact
protected virtual TResult Transact<TResult>(Func<TResult> func)
{
if (_session.Transaction.IsActive)
return func.Invoke();
TResult result;
using (var tx = _session.BeginTransaction(IsolationLevel.ReadCommitted))
{
result = func.Invoke();
tx.Commit();
}
return result;
}
protected virtual void Transact(System.Action action)
{
Transact(() =>
{
action.Invoke();
return false;
});
}
#endregion
#region IRepository<T> Members
public void Save(T item)
{
Transact(() => _session.Save(item));
}
public Boolean Contains(T item)
{
if (item.Id == default(Guid))
return false;
return Transact(() => _session.Get<T>(item.Id)) != null;
}
public Int32 Count
{
get
{
return Transact(() => _session.Query<T>().Count());
}
}
public bool Remove(T item)
{
Transact(() => _session.Delete(item));
return true;
}
public T Load(Guid id)
{
return Transact(() => _session.Load<T>(id));
}
public T Get(Guid id)
{
return Transact(() => _session.Get<T>(id));
}
public IQueryable<T> FindAll()
{
return Transact(() => _session.Query<T>());
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return Transact(() => _session.Query<T>().Take(1000).GetEnumerator());
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return Transact(() => GetEnumerator());
}
#endregion
}
Related
I am using the repository pattern with entity framework 5. When I add a new user to my users entity, it does not get saved to the database. Any ideas why??
I have the following structure -
DAL (contains Entity Framework model) -> Core -> Web
Form1.cs
UserService _userService = new UserService();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
listBox1.DataSource = _userService.GetUserList(10, 1).Users;
listBox1.DisplayMember = "FullName";
}
private void btnAdd_Click(object sender, EventArgs e)
{
UserModel newUser = new UserModel();
newUser.Username = tbUsername.Text;
newUser.FirstName = tbFirstname.Text;
newUser.Surname = tbLastname.Text;
newUser.Password = tbPassword.Text;
newUser.LoginEnabled = true;
newUser.UserStatus = UserStatus.Active;
_userService.Add(newUser);
}
UserService.cs
public void Add(UserModel entity)
{
User newUser = new User();
newUser.DateCreated = DateTime.Now;
AutoMapper.Mapper.CreateMap<UserModel, User>();
try
{
_userRepository.Add(AutoMapper.Mapper.Map(entity, newUser));
}
catch (Exception ex)
{
}
}
RepositoryBase.cs
public abstract class RepositoryBase<T> : IRepository<T>
where T : class
{
public RepositoryBase()
: this(new AcRepositoryContext())
{
}
public RepositoryBase(IRepositoryContext repositoryContext)
{
repositoryContext = repositoryContext ?? new AcRepositoryContext();
_objectSet = repositoryContext.GetObjectSet<T>();
}
private IObjectSet<T> _objectSet;
public IObjectSet<T> ObjectSet
{
get
{
return _objectSet;
}
}
#region IRepository Members
public void Add(T entity)
{
this.ObjectSet.AddObject(entity);
}
public void Delete(T entity)
{
this.ObjectSet.DeleteObject(entity);
}
public IList<T> GetAll()
{
return this.ObjectSet.ToList<T>();
}
public IList<T> GetAll(Expression<Func<T, bool>> whereCondition)
{
return this.ObjectSet.Where(whereCondition).ToList<T>();
}
public T GetSingle(Expression<Func<T, bool>> whereCondition)
{
return this.ObjectSet.Where(whereCondition).FirstOrDefault<T>();
}
public void Attach(T entity)
{
this.ObjectSet.Attach(entity);
}
public IQueryable<T> GetQueryable()
{
return this.ObjectSet.AsQueryable<T>();
}
public long Count()
{
return this.ObjectSet.LongCount<T>();
}
public long Count(Expression<Func<T, bool>> whereCondition)
{
return this.ObjectSet.Where(whereCondition).LongCount<T>();
}
#endregion
}
AcRepositoryContext.cs
public class AcRepositoryContext : IRepositoryContext
{
private const string OBJECT_CONTEXT_KEY = "AC.DAL.AccessControlDBEntities";
public IObjectSet<T> GetObjectSet<T>()
where T : class
{
try
{
return ContextManager.GetObjectContext(OBJECT_CONTEXT_KEY).CreateObjectSet<T>();
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Returns the active object context
/// </summary>
public ObjectContext ObjectContext
{
get
{
return ContextManager.GetObjectContext(OBJECT_CONTEXT_KEY);
}
}
public int SaveChanges()
{
return this.ObjectContext.SaveChanges();
}
public void Terminate()
{
ContextManager.SetRepositoryContext(null, OBJECT_CONTEXT_KEY);
}
public DbSet<User> Users { get; set; }
public DbSet<Door> Doors { get; set; }
public DbSet<Event> Events { get; set; }
}
In your Repository implementation class (RepositoryBase or its children), you should have a call to the context's SaveChanges.
For instance, in RepositoryBase<T>, I would add this method:
public void Commit()
{
repositoryContext.SaveChanges()
}
After you perform your call to Add() in your btnAdd_Click event handler, you should call this Commit method to save changes to the database.
For more information, take a look at this blog:
Using the Repository Pattern in Entity Framework
Hope this helps!
I can't see in your repository where you call SaveChanges on your AcRepositoryContext I'm assuming this inherits from dbcontext or more likely from your naming convention objectcontext
So after you've added a new entity or updated or deleted then you need to save changes to have those changes persisted to the database.
I can see in your userservice class where you add an entity to the repository but you don't appear to call SaveChanges on the context afterwards.
I have create a Generic Repository (Using EF 6.1.1), which I am using in several projects and it works very well.
I have implemented a 'soft' delete feature, where we mark data as deleted, but do not actually remove it from the database.
I can then filter all my queries as all entities inherit from a base entity which has the IsDeleted property. This is all works nicely, but it obviously does not filter out any of the 'soft deleted' child entities.
I am unsure how to go about doing this in a generic way, as I dont want to have to over code a solution into every respoitory, that really defeats the reason for having a generic repo.
this is an example of my current Generic Repo
public sealed class MyRepository<T> : IRepository<T> where T : BaseEntity
{
public String CurrentUser { get; set; }
private readonly MyObjectContext context;
private readonly Configuration configuration = ConfigurationManager.GetConfiguration();
private IDbSet<T> entities;
private IDbSet<T> Entities
{
get { return entities ?? (entities = context.Set<T>()); }
}
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
var providerManager = new DataProviderManager(configuration);
var dataProvider = (IDataProvider)providerManager.LoadDataProvider();
dataProvider.InitDatabase();
CurrentUser = userName;
}
public void Dispose()
{
//do nothing at the moment
}
public T GetById(Guid id)
{
return Entities.FirstOrDefault(x => x.Id == id && !x.IsDeleted);
}
public IQueryable<T> GetAll()
{
return Entities.Where(x => !x.IsDeleted);
}
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter).Where(x => !x.IsDeleted);
}
public void Delete(T entity)
{
if (configuration.HardDelete)
{
HardDelete(entity);
}
else
{
SoftDelete(entity);
}
}
private void HardDelete(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
Entities.Attach(entity);
Entities.Remove(entity);
}
catch (DbEntityValidationException ex)
{
var msg = string.Empty;
foreach (var validationErrors in ex.EntityValidationErrors)
foreach (var validationError in validationErrors.ValidationErrors)
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
var fail = new Exception(msg, ex);
throw fail;
}
}
private void SoftDelete(T entity)
{
entity.IsDeleted = true;
Update(entity);
}
}
Any help on this would be great.
Thanks
Someone has built a global filter, you can try it and install it from nuget EntityFramework.Filters.
https://github.com/jbogard/EntityFramework.Filters
Here is an example how to use it.
public abstract class BaseEntity
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
}
public class Foo : BaseEntity
{
public string Name { get; set; }
public ICollection<Bar> Bars { get; set; }
}
public class Bar : BaseEntity
{
public string Name { get; set; }
public int FooId { get; set; }
public Foo Foo { get; set; }
}
public class AppContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Registers and configures it first.
DbInterception.Add(new FilterInterceptor());
var softDeleteFilter = FilterConvention.Create<BaseEntity>("SoftDelete",
e => e.IsDeleted == false); // don't change it into e => !e.IsDeleted
modelBuilder.Conventions.Add(softDeleteFilter);
}
}
Then you can enable it in your repository constructor or somewhere after db context instance is created because the filters are disabled by default.
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.Include(f => f.Bars).ToArray(); // works on Include
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
var bars = foo.Bars; // works on lazy loading
}
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
db.Entry(foo).Collection(f => f.Bars).Load(); // works on manual loading
}
}
This filter is not needed anymore.
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter);//.Where(x => !x.IsDeleted);
}
As long as you have enabled it.
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
if (!configuration.HardDelete)
{
this.context.EnableFilter("SoftDelete");
}
}
I was having the same problem but my method to solve was little different i used a genric base interface IGenricInterface with IsDeleted as property
public int DeletebyId(string Id)
{
var Ent = (IGenricInterface)_sitecontext.Set<TEntity>().Find(Id);
Ent.IsDeleted = 1;
}
and for hardDelete
_sitecontext.Set<TEntity>().Remove(Ent);
This is on ID but offcourse you can do it on EnTity as well
i having this error as title mention , what causes this anyone?? heres my code
i just want to display the files from SQL Database but it does have error any help? thanks!
namespace a.Models
public interface ICatRepository
{
IEnumerable<Category> GetAll();
Category Get(int id);
Category Add(Category item);
void Remove(int id);
bool Update(Category item);
}
another repository
namespace a.Models
public class CatRepository : ICatRepository
{
private istellarEntities db = new istellarEntities();
public CatRepository()
{
}
public IEnumerable<Category> GetAll()
{
return db.Categories;
}
public Category Get(int id)
{
return db.Categories.Find(id);
}
public Category Add(Category category)
{
db.Categories.Add(category);
db.SaveChanges();
return category;
}
public void Remove(int id)
{
Category category = db.Categories.Find(id);
db.Categories.Remove(category);
db.SaveChanges();
}
public bool Update(Category category)
{
db.Entry(category).State = EntityState.Modified;
db.SaveChanges();
return true;
}
}
controller
namespace a.Controllers
public class APICategoryController : ApiController
{
// static readonly ICatRepository repository = new CatRepository();
private readonly ICatRepository repository;
public APICategoryController(ICatRepository repository)
{
if (repository == null)
{
throw new ArgumentNullException("repository");
}
this.repository = repository;
}
public IEnumerable<Category> GetAllCategories()
{
return repository.GetAll();
}
Lastly my class file
namespace a.Models
using System;
using System.Collections.Generic;
public partial class Category
{
public Category()
{
this.IQuestions = new HashSet<IQuestion>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<IQuestion> IQuestions { get; set; }
}
You've got one of the more helpful error messages there - APICategoryController does not have a default constructor, ie: a parameterless constructor.
You either need to use some kind of Dependency Injector for your code to know how to instantiate a concrete class for ICatRepository, or provide a default constructor.
eg:
//a default constructor instantiating a concrete type. Simple, but no good for testing etc.
public APICategoryController()
{
ICatRepository repository = new ConcreteRepository;
this.repository = repository;
}
you need to add default constructor. you should add this to APICategoryController (this is default constructor)
public APICategoryController()
{
}
I have an object A that has another object B inside. I am loading the using EntityFramework and modifying the object B but when I set the State to EntityState.Modified I have this error{"An entity object cannot be referenced by multiple instances of IEntityChangeTracker."}
This are my objects
public class Patient : IEntity, IObjectWithState
{
public virtual int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsMobileActived { get; set; }
public virtual MobilePatient MobilePatient { get; set; }
[NotMapped]
public State State { get; set; }
}
public class MobilePatient : IObjectWithState
{
public virtual int Id { get; set; }
public virtual int PatientPin { get; set; }
public virtual string SecurityAnswer { get; set; }
public virtual bool IsPinRemembered { get; set; }
public virtual PhysiologicalData PhysiologicalData { get; set; }
[NotMapped]
public State State { get; set; }
}
I have a repository and a unit of work and bascally using it I am loading the Patient object in this way.
... Patient patient = context.Patients.Find(id); ...
Then with the patient I am updating the MobilePatient
... patient.MobilePatient = NEWmobilePatient; ...
and then updating it with
PatientContext patientContext = (PatientContext)context;
patientContext.Entry(patient).State = EntityState.Modified;
My context only has the Patient dbset
public class PatientContext:BaseContext<PatientContext>, IPatientContext
{
public IDbSet<Patient> Patients { get; set; }
public void SetModified(object entity)
{
Entry(entity).State = EntityState.Modified;
}
public void SetAdd(object entity)
{
Entry(entity).State = EntityState.Added;
}
}
So I dont know what I am missing to update. When I am loading the Patient I am using the default lazy-load but how I have data in MobilePatient I am getting that object too.
Something that I think could be a good information is that I am using unit of work and repository and my application a disconnect application.
This my repository:
public class PatientRepository : IRepository<Patient>
{
private IPatientContext context=new PatientContext();
public PatientRepository(PatientContext context)
{
this.context = context;
}
public void Add(Patient patient)
{
context.SetAdd(patient);
}
public void Update(Patient patient)
{
context.SetModified(patient);
}
public void Delete(Patient entity)
{
throw new NotImplementedException();
}
public Patient FindById(int id)
{
Patient patient = context.Patients.Find(id);
return patient;
}
public IQueryable<Patient> Find(Expression<Func<Patient, bool>> predicate)
{
PatientContext patientContext = (PatientContext)context;
return patientContext.Set<Patient>().Where(predicate).AsQueryable<Patient>();
}
public IQueryable<Patient> FindAll()
{
return context.Patients;
}
}
I an this how I am using it in my services :
Patient patient = new Patient();
using (IPatientLoaderService patientLoaderService = AppManager.Instance.Database.CreatePatientLoaderService())
{
patient = patientLoaderService.LoadPatientById(patientId);
}
patient.MobilePatient =New_mobilePatient;
patient.State = State.Modified;
patient.Age = 40;
using (IPatientUpdaterService patientUpdaterService = AppManager.Instance.Database.CreatePatientUpdaterService())
{
patientUpdaterService.UpdatePatient(patient);
}
In my services I use the unit of work and the repositories
this is one of my services used in the code:
public class EntityFrameworkPatientUpdaterService: IPatientUpdaterService
{
private PatientRepository patientsRepository;
private EntityFrameworkUnitOfWork<PatientContext> unitOfWork;
public EntityFrameworkPatientUpdaterService()
{
unitOfWork = new EntityFrameworkUnitOfWork<PatientContext>();
PatientContext patientContent = new PatientContext();
patientsRepository = new PatientRepository(patientContent);
}
public void UpdatePatient(Patient patient)
{ try
{
patientsRepository.Update(patient);
unitOfWork.Commit();
}
catch (Exception e)
{
//TODO: Log the error and evoid to throw another exception-DOR
unitOfWork.Dispose();
throw new Exception("Error on EntityFrameworkPatientUpdaterService.UpdatePatient. " +
e.Message);
}
finally
{
unitOfWork.Dispose();
unitOfWork = new EntityFrameworkUnitOfWork<PatientContext>();
PatientContext patientContent = new PatientContext();
patientsRepository = new PatientRepository(patientContent);
}
}
public void Dispose()
{
unitOfWork.Dispose();
}
}
Thank you for read this post
I am going to be adding more detail on how I am currently using my service. I think the problem is that I am trying to use tghe Onion Architecture and I am missing something.
public class PatientContext:BaseContext<PatientContext>, IPatientContext
{
public IDbSet<Patient> Patients { get; set; }
public void SetModified(object entity)
{
Entry(entity).State = EntityState.Modified;
}
}
public class PatientRepository : IRepository<Patient>
{
private readonly IPatientContext context;
public PatientRepository(PatientContext context)
{
this.context = context;
}
public void Update(Patient patient)
{
context.SetModified(_patient);
}
public Patient FindById(int id)
{
Patient patient = context.Patients.Find(id);
return patient;
}
}
public class EntityFrameworkPatientUpdaterService
{
private PatientRepository patientsRepository;
private EntityFrameworkUnitOfWork<PatientContext> unitOfWork;
public EntityFrameworkPatientUpdaterService()
{
unitOfWork = new EntityFrameworkUnitOfWork<PatientContext>();
PatientContext patientContent = new PatientContext();
patientsRepository = new PatientRepository(patientContent);
}
public void UpdatePatient(Patient patient)
{ try
{
patientsRepository.Update(patient);
unitOfWork.Commit();
}
catch (Exception e)
{
//TODO: Log the error and evoid to throw another exception-DOR
unitOfWork.Dispose();
throw new Exception("Error on EntityFrameworkPatientUpdaterService.UpdatePatient. " +
e.Message);
}
finally
{
unitOfWork.Dispose();
}
}
public void Dispose()
{
unitOfWork.Dispose();
}
}
//GEtting the patient and dispose everything,
Patient patient = new Patient();
using (IPatientLoaderService patientLoaderService = AppManager.Instance.Database.CreatePatientLoaderService())
{
patient = patientLoaderService.LoadPatientById(patientId);
}
//THEN Calling my services to update
using (IPatientUpdaterService patientUpdaterService = AppManager.Instance.Database.CreatePatientUpdaterService())
{
patientUpdaterService.UpdatePatient(patient);
}
If I interpret the exception message correctly, it means that you have two contexts, and both want the entry of your entity. This is not possible, only one context at the time can deal with a data row at a time.
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"]);
}
The way you have designed your repository follows a very clear design, but the parameter that you are passing in the constructor makes things a bit more complicated when using dependency injection.
What I propose here below, is perhaps not the best design, but it will allow you to progress without doing too much changes to your existing code.
The catch in this solution is that you have to call the "Initialise" method when creating the controller, which potentially you might not like, but it is quite effective.
Here are the steps:
Create a new method in your IEmployeeRepository
public interface IEmployeeRepository
{
//leave everything else as it is
void Initialise(int tenantId);
}
Implement that method in the EFEmployeeRepository
public class EFEmployeeRepository
{
//leave everything else as it is
public void Initialise(int tenantID = 0)
{
context = new EFDbContext(tenantID);
}
}
In the HomeController, you would need to call "Initialise" in the constructor
public HomeController(IEmployeeRepository empRepository)
{
repoEmployee = empRepository;
repoEmployee.Initialise(/* use your method to pass the Tenant ID here*/);
}
An alternative to this approach could be to create a RepositoryFactory that would return the Repository filled out with all the filters you need. In that case you will inject the Factory rather than the Repository to the Controller.