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.
Related
I am receiving null exception error on my framework. I have tried to apply Repository and Unit of Work design patterns in my application. What I am trying to do is simply retreiving user titles from my data base with GetAll() method.
Here is my repository class:
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
this.Context = context;
}
public T Get(int id)
{
return Context.Set<T>().Find(id);
}
public IEnumerable<T> GetAll()
{
return Context.Set<T>().ToList();
}
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
{
return Context.Set<T>().Where(predicate);
}
public void Add(T entity)
{
Context.Set<T>().Add(entity);
}
public void AddRange(IEnumerable<T> entityList)
{
Context.Set<T>().AddRange(entityList);
}
public void Remove(T entity)
{
Context.Set<T>().Remove(entity);
}
public void RemoveRange(IEnumerable<T> entityList)
{
Context.Set<T>().RemoveRange(entityList);
}
}
This is IUserTitlesRepository:
public interface IUserTitlesRepository : IRepository<UserTitles>
{
}
And, the class where above interface implemented:
public UserTitlesRepository(XaPaDataContext context) : base(context)
{
}
public XaPaDataContext XaPaDataContext
{
get { return Context as XaPaDataContext; }
}
Before coming to Controller layer, I have two more layers, which are Operation and Manager layers. And, I think I have messed up on that part (on Base Manager class as shown below).
This is operation layer:
public class UserTitlesOperations
{
private readonly IUnitOfWork _uow;
public UserTitlesOperations(IUnitOfWork uow)
{
_uow = uow;
}
public List<UserTitles> GetAllUserTitles()
{
try
{
List<UserTitles> userTitleList = _uow.UserTitles.GetAll().ToList();
_uow.Complete();
return userTitleList;
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
}
Below is the BaseManager class which gives inheritance to all manager classes.
public abstract class BaseManager
{
private IUnitOfWork _iUow;
private readonly XaPaDataContext _context;
public IUnitOfWork IUOW
{
get
{
if (_iUow == null)
{
_iUow = new XaPaUnitOfWork(_context);
}
return _iUow;
}
}
}
This is the manager class:
public class UserTitlesManager : BaseManager
{
private readonly UserTitlesOperations _userTitlesOperations;
public UserTitlesManager()
{
_userTitlesOperations = new UserTitlesOperations(base.IUOW);
}
public List<UserTitlesWM> GetAllUserTitles()
{
try
{
return UserTitlesMapping.MaptoWM(_userTitlesOperations.GetAllUserTitles());
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
}
Finally, this is my API Controller:
[Route("api/LoginRequest")]
public class TitlesController : BaseController
{
UserTitlesManager _userTitlesManager;
public LoginController()
{
_userTitlesManager = new UserTitlesManager();
}
[Route("RetreiveTitles")]
public HttpResponseMessage GetTitles()
{
try
{
return Request.CreateResponse(HttpStatusCode.OK, _userTitlesManager.GetAllUserTitles());
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.ToString());
}
}
}
By the way BaseController is just another API controller which gives inheritance to all other API controllers, and houses a method which is used by all the other controllers.
So, I'm still trying to sharpen my self on this design patterns and would be glad if anyone could show my mistake on BaseManager class. As I said, I suppose the problem is caused by that private readonly XaPaDataContext _context; line. On the other hand,I can't figure out how to corrrect it as my operation classes' constructors are asking for IUnitOfWork.
Thank you in advance!
EDIT:
Just realized that I forgot to share my Unit of Work class:
public class XaPaUnitOfWork : IUnitOfWork
{
private readonly XaPaDataContext _context;
public XaPaUnitOfWork(XaPaDataContext context)
{
_context = context;
Categories = new CategoriesRepository(_context);
OrderDetails = new OrderDetailsRepository(_context);
Orders = new OrdersRepository(_context);
ProductImages = new ProductImagesRepository(_context);
Products = new ProductsRepository(_context);
Users = new UsersRepository(_context);
UserTitles = new UserTitlesRepository(_context);
UserTokens = new UserTokensRepository(_context);
}
public ICategoriesRepository Categories { get; private set; }
public IOrderDetailsRepository OrderDetails { get; private set; }
public IOrdersRepository Orders { get; private set; }
public IProductImagesRepository ProductImages { get; private set; }
public IProductsRepository Products { get; private set; }
public IUsersRepository Users { get; private set; }
public IUserTitlesRepository UserTitles { get; private set; }
public IUserTokensRepository UserTokens { get; private set; }
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
After I have changed my BaseManager class as below:
public abstract class BaseManager
{
private IUnitOfWork _iUow;
public IUnitOfWork IUOW
{
get
{
if (_iUow == null)
{
_iUow = new XaPaUnitOfWork(new XaPaDataContext());
}
return _iUow;
}
}
}
I have achived to receive HttpStatusCode.OK
But, honestly, I'm still unsure about the real reason. I make this correction mostly by heart.
So Im cant get it right and I`m trying from about 5 hours and nothing. If some can help I will be greatfull.
So here is my code
MyContextFile:
public class InventoryManagerContext : IdentityDbContext<ApplicationUser>, IInventoryManagerContext
{
public InventoryManagerContext()
: base("name=InventoryManager")
{
}
public virtual IDbSet<Cloth> Clothes { get; set; }
public static InventoryManagerContext Create()
{
return new InventoryManagerContext();
}
}
My Data file:
public class InventoryManagerData: IInventoryManagerData
{
private readonly DbContext context;
private readonly Dictionary<Type, object> repositories = new Dictionary<Type, object>();
public InventoryManagerData()
: this(new InventoryManagerContext())
{
}
public InventoryManagerData(DbContext context)
{
this.context = context;
}
private IRepository<T> GetRepository<T>() where T : class
{
if (!this.repositories.ContainsKey(typeof(T)))
{
var type = typeof(GenericRepository<T>);
this.repositories.Add(typeof(T), Activator.CreateInstance(type, this.context));
}
return (IRepository<T>)this.repositories[typeof(T)];
}
public int SaveChanges()
{
return this.context.SaveChanges();
}
public void Dispose()
{
this.context.Dispose();
}
public IRepository<Cloth> Clothes
{
get { return this.GetRepository<Cloth>(); }
}
}
and Generic Repository Fail:
public class GenericRepository<T> : IRepository<T> where T : class
{
//public GenericRepository()
// : this(new InventoryManagerContext())
//{
//}
public GenericRepository(DbContext context)
{
if (context == null)
{
throw new ArgumentException("An instance of DbContext is required to use this repository.", "context");
}
this.Context = context;
this.DbSet = this.Context.Set<T>();
}
protected IDbSet<T> DbSet { get; set; }
protected DbContext Context { get; set; }
public virtual IQueryable<T> All()
{
return this.DbSet.AsQueryable();
}
public virtual T GetById(int id)
{
return this.DbSet.Find(id);
}
public virtual void Add(T entity)
{
DbEntityEntry entry = this.Context.Entry(entity);
if (entry.State != EntityState.Detached)
{
entry.State = EntityState.Added;
}
else
{
this.DbSet.Add(entity);
}
}
public virtual void Update(T entity)
{
DbEntityEntry entry = this.Context.Entry(entity);
if (entry.State == EntityState.Detached)
{
this.DbSet.Attach(entity);
}
entry.State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
DbEntityEntry entry = this.Context.Entry(entity);
if (entry.State != EntityState.Deleted)
{
entry.State = EntityState.Deleted;
}
else
{
this.DbSet.Attach(entity);
this.DbSet.Remove(entity);
}
}
public virtual void Delete(int id)
{
var entity = this.GetById(id);
if (entity != null)
{
this.Delete(entity);
}
}
public virtual void Detach(T entity)
{
DbEntityEntry entry = this.Context.Entry(entity);
entry.State = EntityState.Detached;
}
}
So Im trying to make Fake Context and the pass it to the Data class, but without success. in the Test controller Im trying to use MOQ, in a example like this:
[TestMethod]
public void Index_ShouldPass()
{
var mockContext = new Mock<IInventoryManagerContext>();
foreach (var cloth in clothes)
{
mockContext.Object.Clothes.Add(cloth);
}
var mockMenuRepository = new Mock<IRepository<Cloth>>(mockContext.Object);
var mockUnitOfWork = new Mock<InventoryManagerData>(mockContext.Object);
mockUnitOfWork.Setup(e => e.Clothes).Returns(mockMenuRepository.Object);
var menuItems = mockUnitOfWork.Object.Clothes.All();
Assert.AreEqual(4, menuItems.Count());
}
So I have interfaces as well IInventoryManagerContext, IInventoryManagerData and IRepository with all Add, Remove signatures.
For data I have really simple List of for Cloth Objects. and tried to add them o the context with loop. But my latest error is:
Test error:
Message: Test method InventoryManager.Tests.Controllers.HomeControllerTests.Index_ShouldPass threw exception:
System.NullReferenceException: Object reference not set to an instance of an object.
Kind of desperate already..
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
}
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.
I'm have a Base Repository and all Entities repositories inherits from that.
In my testes i create a Fake DbContext and Fake DbSet to test my repositories, but when implementing some methods in my FakeDbContext I'm not able to implement the IDbContext.Entry method:
public class FakeDbContext : IDbContext
{
private IDbSet<Usuario> _usuario;
private IDbSet<Atividade> _atividade;
private IDbSet<Autor> _autor;
private IDbSet<CategoriaModulo> _categoriaModulo;
private IDbSet<CategoriaMateria> _categoriaMateria;
private IDbSet<Site> _site;
private IDbSet<Modulo> _modulo;
private IDbSet<Perfil> _perfil;
private IDbSet<CategoriaGaleriaImagem> _categoriaGaleriaImagem;
public IDbSet<Usuario> Usuario { get { return _usuario ?? (_usuario = new FakeDbSet<Usuario>()); } set { } }
public IDbSet<Atividade> Atividade { get { return _atividade ?? (_atividade = new FakeDbSet<Atividade>()); } set { } }
public IDbSet<Autor> Autor { get { return _autor ?? (_autor = new FakeDbSet<Autor>()); } set { } }
public IDbSet<CategoriaModulo> CategoriaModulo { get { return _categoriaModulo ?? (_categoriaModulo = new FakeDbSet<CategoriaModulo>()); } set { } }
public IDbSet<CategoriaMateria> CategoriaMateria { get { return _categoriaMateria ?? (_categoriaMateria = new FakeDbSet<CategoriaMateria>()); } set { } }
public IDbSet<Site> Site { get { return _site ?? (_site = new FakeDbSet<Site>()); } set { } }
public IDbSet<Modulo> Modulo { get { return _modulo ?? (_modulo = new FakeDbSet<Modulo>()); } set { } }
public IDbSet<Perfil> Perfil { get { return _perfil ?? (_perfil = new FakeDbSet<Perfil>()); } set { } }
public IDbSet<CategoriaGaleriaImagem> CategoriaGaleriaImagem { get { return _categoriaGaleriaImagem ?? (_categoriaGaleriaImagem = new FakeDbSet<CategoriaGaleriaImagem>()); } set { } }
public void SaveChanges()
{
//do nothing
}
public IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
foreach (PropertyInfo property in typeof(FakeDbContext).GetProperties())
{
if (property.PropertyType == typeof(IDbSet<TEntity>))
return property.GetValue(this, null) as IDbSet<TEntity>;
}
throw new Exception("Type collection not found");
}
public System.Data.Entity.Infrastructure.DbEntityEntry Entry<TEntity>(TEntity entity) where TEntity : class
{
}
}
The last method I'm not able to implementing, can you guys help me?
I'm using this Entry method to update a Entity in my base repository:
public abstract class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class
{
#region Fields
protected TEntity EntityType;
protected IDbSet<TEntity> DbSet;
#endregion
#region Properties
public IDbContext DbContext
{
get
{
return DbContextFactory.Instance.GetOrCreateContext();
}
}
#endregion
#region Constructors
protected BaseRepository()
{
this.EntityType = DependencyResolverFactory.Instance.Get<TEntity>();
this.DbSet = DbContext.Set<TEntity>();
}
#endregion
#region Methods
public virtual void Add(TEntity entity)
{
this.DbSet.Add(entity);
}
public virtual void Remove(TEntity entity)
{
this.DbSet.Remove(entity);
}
public virtual void RemoveById(object id)
{
TEntity entity = this.GetById(id);
this.DbSet.Remove(entity);
}
public virtual void Edit(TEntity entity)
{
this.DbContext.Entry(entity).State = EntityState.Modified;
}
public virtual TEntity GetById(object id)
{
return (TEntity)this.DbSet.Find(id);
}
public virtual IList<TEntity> GetAll()
{
return ((IEnumerable<TEntity>)this.DbSet).ToList();
}
#endregion
}
Read this and all linked questions before you continue. Unit testing anything returning EF related classes or working with linq-to-entities is dangerous.
Give up with unit testing your repositories and instead unit test your application logic by faking repositories themselves. If you want to test your repositories create integration tests talking to the real database.