I have build a self hosted WCF service which consumes a unit of work with all my repositories in it. The repositories use code first EF to connect to the database. I am using the Ninject.Extensions.Wcf.SelfHost package to start the service and get the injection working.
Everything works just fine until i want to commit something to the database. I can read records from the database, but writing does not work. After digging and debugging i found that my db context is not shared between the unit of work and the repositories. So when i commit in my unit of work the context has no changes to commit.
any advice?
And here the code:
Startup code for the service
private static void StartNinjectSelfHosted(string address)
{
var service =
NinjectWcfConfiguration.Create<SecurityService, NinjectServiceSelfHostFactory>(
serviceHost =>
serviceHost.AddServiceEndpoint(typeof(ISecurityService), new BasicHttpBinding(), address));
selfHosted = new NinjectSelfHostBootstrapper(CreateKernel, service);
selfHosted.Start();
serviceAddress = address;
}
private static StandardKernel CreateKernel()
{
var kernel = new StandardKernel();
ConfigurationAction scope = bind => bind.InRequestScope();
kernel.Load((new NinjectModule[]
{
new ContextBinder(scope),
new ServiceBinder(scope) ,
new UnitOfWorkBinder(scope),
new RepositoryBinder(scope),
}));
return kernel;
}
Binders
public class ContextBinder : NinjectModule
{
private readonly ConfigurationAction _bindInScope;
public ContextBinder(ConfigurationAction bindInScope)
{
_bindInScope = bindInScope;
}
public override void Load()
{
Kernel.Bind(typeof(SecurityContext)).ToSelf().InSingletonScope();
}
}
public class ServiceBinder : NinjectModule
{
private readonly ConfigurationAction _configurationAction;
public ServiceBinder(ConfigurationAction configurationAction)
{
_configurationAction = configurationAction;
}
public override void Load()
{
Kernel.Bind(
x => x.FromAssembliesMatching("WcfInterfaces*")
.SelectAllInterfaces()
.Join.FromAssembliesMatching("*Facade*")
.SelectAllClasses()
.BindDefaultInterface()
.Configure(_configurationAction));
}
}
public class UnitOfWorkBinder : NinjectModule
{
private readonly ConfigurationAction _configurationAction;
public UnitOfWorkBinder(ConfigurationAction configurationAction)
{
_configurationAction = configurationAction;
}
public override void Load()
{
Kernel.Bind(x => x
/** Select all unit of work interfaces */
.FromAssembliesMatching("SecurityDomain*")
.SelectAllUnitOfWorkInterfaces()
/** Select all unit of work implementations */
.Join.FromAssembliesMatching("SecurityImplementation*")
.SelectAllUnitOfWorkImplementations()
/** Bind interfaces to implementations */
.BindDefaultInterface()
/** Configure the scope */
.Configure(_configurationAction));
}
}
public class RepositoryBinder : NinjectModule
{
private readonly ConfigurationAction _configurationAction;
public RepositoryBinder(ConfigurationAction configurationAction)
{
_configurationAction = configurationAction;
}
public override void Load()
{
Kernel.Bind(x => x
/** Select all default repository interfaces */
.FromAssembliesMatching("SecurityDomain*")
.SelectAllRepositoryInterfaces()
/** Select all repository implementations */
.Join.FromAssembliesMatching("SecurityImplementation*")
.SelectAllRepositoryImplementations()
/** Bind interfaces to implementations */
.BindDefaultInterface()
/** Configure the scope */
.Configure(_configurationAction));
}
}
Unit of work
public class UnitOfWork : IUnitOfWork
{
private readonly SecurityContext _context;
public UnitOfWork(SecurityContext context, ISecurityUnitOfWork security)
{
Console.WriteLine("*** Unit Of Work ContextHash: {0}***", context.Hash);
_context = context;
Security = security;
}
public void Commit(int userId)
{
Console.WriteLine("Context hash {0}", _context.Hash);
using (var transaction = _context.Database.BeginTransaction())
{
try
{
DateTime now = DateTime.Now;
foreach (var entry in _context.ChangeTracker.Entries<Entity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreationDate = now;
entry.Entity.CreationUserId = userId;
break;
case EntityState.Modified:
entry.Entity.ModificationDate = now;
entry.Entity.ModificationUserId = userId;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.Entity.Deleted = true;
break;
}
}
_context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
}
public ISecurityUnitOfWork Security { get; private set; }
}
Security Unit of work
public class SecurityUnitOfWork : ISecurityUnitOfWork
{
public SecurityUnitOfWork(IAccountRepository accounts, IRoleRepository roles, IRightRepository rights, IUserRepository users, IApplicationRepository applications)
{
Applications = applications;
Users = users;
Rights = rights;
Roles = roles;
Accounts = accounts;
}
public IAccountRepository Accounts { get; private set; }
public IRoleRepository Roles { get; private set; }
public IRightRepository Rights { get; private set; }
public IUserRepository Users { get; private set; }
public IApplicationRepository Applications { get; private set; }
}
Repositories
public class AccountRepository : GenericRepository<SecurityContext, Account>, IAccountRepository
{
public AccountRepository(SecurityContext context)
: base(context)
{
}
}
public class GenericRepository<TContext, TEntity> : IGenericRepository<TEntity>
where TContext : DbContext
where TEntity : class, IDeletable, IIdentifiable
{
private readonly TContext _context;
private readonly DbSet<TEntity> _entitySet;
private IQueryable<TEntity> _entities;
public GenericRepository(TContext context)
{
_context = context;
_entitySet = context.Set<TEntity>();
_entities = _entitySet;
}
/// <summary>
/// Gets the DbContext
/// </summary>
protected virtual TContext Context
{
get { return _context; }
}
/// <summary>
/// Gets the entities
/// </summary>
protected virtual IQueryable<TEntity> Entities
{
get { return _entities; }
set { _entities = value; }
}
/// <summary>
/// Gets the editable dbset
/// </summary>
public virtual IDbSet<TEntity> EntitySet
{
get { return _entitySet; }
}
/// <summary>
/// Gets the entities
/// </summary>
protected virtual IQueryable<TEntity> Process(IEntityFilter<TEntity> filter = null, IEntitySorter<TEntity> sorter = null, IEntityIncluder<TEntity> includer = null)
{
var entities = _entities.Where(x => !x.Deleted);
if (includer != null)
entities = includer.AddInclusions(entities);
if (filter != null)
entities = filter.Filter(entities);
if (sorter != null)
entities = sorter.Sort(entities);
return entities;
}
public virtual IQueryable<TEntity> List(IEntitySorter<TEntity> sorter = null, IEntityFilter<TEntity> filter = null, int? page = null, int? pageSize = null, IEntityIncluder<TEntity> includer = null)
{
if ((page.HasValue || pageSize.HasValue) && sorter == null)
{
throw new ArgumentException("You have to define a sorting order if you specify a page or pageSize! (IEntitySorter was null)");
}
if (page.HasValue && !pageSize.HasValue)
{
throw new ArgumentException("You have to define a pageSize if you specify a page!");
}
var entities = Process(filter, sorter, includer);
if (page != null)
entities = entities.Skip(pageSize.Value * page.Value);
if (pageSize != null)
entities = entities.Take(pageSize.Value);
return entities;
}
public virtual int Count(IEntityFilter<TEntity> filter = null)
{
return Process(filter).Count();
}
public bool Any(IEntityFilter<TEntity> filter = null)
{
return Process(filter).Any();
}
public TEntity SingleOrDefault(IEntityFilter<TEntity> filter = null, IEntityIncluder<TEntity> includer = null)
{
return Process(filter, includer: includer).SingleOrDefault();
}
public TEntity Single(IEntityFilter<TEntity> filter = null, IEntityIncluder<TEntity> includer = null)
{
return Process(filter, includer: includer).Single();
}
public TEntity FirstOrDefault(IEntityFilter<TEntity> filter = null, IEntitySorter<TEntity> sorter = null, IEntityIncluder<TEntity> includer = null)
{
return Process(filter, sorter, includer).FirstOrDefault();
}
public TEntity First(IEntityFilter<TEntity> filter = null, IEntitySorter<TEntity> sorter = null, IEntityIncluder<TEntity> includer = null)
{
return Process(filter, sorter, includer).First();
}
public virtual TEntity Find(int id)
{
var entity = EntitySet.FirstOrDefault(x => x.Id == id);
if (entity != null && entity.Deleted)
{
return null;
}
return entity;
}
public virtual void AddOrUpdate(TEntity entity)
{
if (entity.Id == 0)
{
Add(entity);
}
else
{
Update(entity);
}
}
public virtual void Delete(TEntity entity)
{
entity.Deleted = true;
Update(entity);
}
public virtual void Delete(IEnumerable<TEntity> entities)
{
foreach (TEntity entity in entities)
{
Delete(entity);
}
}
public virtual void Delete(int id)
{
TEntity entity = Find(id);
if (entity != null)
Delete(entity);
}
public virtual void HardDelete(TEntity entity)
{
DbEntityEntry entry = Context.Entry(entity);
if (entry.State != EntityState.Deleted)
{
entry.State = EntityState.Deleted;
}
else
{
EntitySet.Attach(entity);
}
}
public virtual void HardDelete(int id)
{
TEntity entity = Find(id);
if (entity != null)
HardDelete(entity);
}
public TResult Query<TResult>(Func<IQueryable<TEntity>, TResult> query)
{
return query(Entities);
}
/// <summary>
/// Gets the queryable entities
/// </summary>
public IQueryable<TEntity> QueryableEntities
{
get
{
return _entitySet;
}
}
protected virtual void Add(TEntity entity)
{
DbEntityEntry entry = Context.Entry(entity);
if (entry.State != EntityState.Detached)
{
entry.State = EntityState.Added;
}
else
{
EntitySet.Add(entity);
}
}
protected virtual void Update(TEntity entity)
{
DbEntityEntry entry = Context.Entry(entity);
if (entry.State == EntityState.Detached)
{
EntitySet.Attach(entity);
}
entry.State = EntityState.Modified;
}
}
when i start the service this is the output
Starting service
**** CONTEXT CONSTRUCTED, HASH:63174400 ****
**** CONTEXT CONSTRUCTED, HASH:24275713 ****
**** CONTEXT CONSTRUCTED, HASH:34631232 ****
**** CONTEXT CONSTRUCTED, HASH:66590816 ****
**** CONTEXT CONSTRUCTED, HASH:24695352 ****
**** CONTEXT CONSTRUCTED, HASH:11985038 ****
*** Unit Of Work ContextHash: 63174400***
--------------------------------
Security service is running # http://localhost/security
So after some more debugging and testing i managed to solve this myself. here's a what i did and what i found:
I started looking at the ninject scope and tried all the available options, none of them worked. next step was skip the binderClasses and manually link all my Interfaces and implementations. At first this was also no go, so i started playing with the scope setting again.
I got the whole thing working with the manual binding an in RequestScope. Of course manual binding was not what i wanted.
after some more testing i have this
private static StandardKernel CreateKernel()
{
var kernel = new StandardKernel();
ConfigurationAction scope = bind => bind.InRequestScope();
/* this works*/
scope(
kernel.Bind(typeof(SecurityContext))
.ToSelf());
/*
* This works
*
* kernel.Bind(typeof(SecurityContext))
.ToSelf()
.InRequestScope();*/
/*
* This does not work
kernel.Load(new ContextBinder(scope));
*/
kernel.Load(new UnitOfWorkBinder(scope));
kernel.Load(new RepositoryBinder(scope));
kernel.Load(new ServiceBinder(scope));
return kernel;
}
I have no Idea why binding the context in the contextbinder create a separate context for every instance it needs. So if anyone could clarify.
I marked this as resolved because the code above is working for me.
Related
I am wondering, how we can handle a situation, and if I'm going about this the wrong way. We wrote a web application based on one ERP's database schema model. We are using Entity Framework for the system, with the regular dependency injection method.
Now that we have purchased multiple ERP's from other labs, we are trying to allow their data be used on our first original web portal built for our original ERP. As you can imagine, this is rough as the database models and design will not line up with ours. For example something like GetAllAssets() stored procedure returns the complex type and then is bound to the repo level, the service level and onto the view.
What I was thinking is that we could maybe add multiple EDMX (context for databases), then keep everything from the Views to the controllers to the service layer the same. At the service layer, and type params to our classes, and so we could pass the context the current user logged in as, and then in auto mapper add new entries for the new database context calls from Entity Framework, to map to our original code from the service back down to the view.
Is this possible, and or a good idea or bad idea?
Here is a example of a current basic controller we are using with DI style, and I have removed a lot of code for this question's example:
[AuthorizeWithSession]
public class LocationController : Controller
{
private readonly IMProAssetLocationService _mProAssetLocationService;
private readonly IUIDataService _uiDataService;
public LocationController(IMProAssetLocationService mProAssetLocationService,
IUIDataService uiDataService)
{
_mProAssetLocationService = mProAssetLocationService;
}
public ActionResult List()
{
return View();
}
public ActionResult List2()
{
return View();
}
public ActionResult GetLocationList([DataSourceRequest]DataSourceRequest request)
{
//var result = DepartmentService.GetDepartmentList(SessionHelper.GetCustId());
var result = _mProAssetLocationService.MProAssetLocationGetLocationByCustID(SessionHelper.GetCustId(), null);
if (result != null && result.Any())
{
return Json(result.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}
}
}
The interface service layer:
public interface IMProAssetLocationService
{
IEnumerable<LocationVm> MProAssetLocationGetLocationByCustID(string custId,string id);
string MProAssetLocationInsertLocation(LocationVm vm);
void MProAssetLocationDeleteLocationByCustIDAndLocationID(string custId, string locationId);
void MProAssetLocationUpdateLocationByCustIDAndLocationID(LocationVm vm);
}
The service layer:
public class MProAssetLocationService : LogManager, IMProAssetLocationService
{
private readonly IMProAssetLocationRepo _mProAssetLocationRepo;
public MProAssetLocationService(IMProAssetLocationRepo mProAssetLocationRepo)
{
_mProAssetLocationRepo = mProAssetLocationRepo;
}
protected override Type LogPrefix
{
get { return this.GetType(); }
}
public IEnumerable<LocationVm> MProAssetLocationGetLocationByCustID(string custId, string id)
{
List<LocationVm> listlocationVm = new List<LocationVm>();
try
{
var records = _mProAssetLocationRepo.MProAssetLocationGetLocationByCustID(custId,id);
}
}
The Interface repo layer:
public interface IMProAssetLocationRepo : IRepository<MProAssetLocation>
{
IEnumerable<string> GetMProAssetLocatonByCustId(string custId);
IEnumerable<string> GetMProAssetLocatonDescriptionByCustId(string custId);
IEnumerable<LocationView> GetMProAssetLocatonListByCustId(string search, string locationID, string custId);
IEnumerable<LocationView> MProAssetLocationGetLocationByCustID(string custId, string id);
string MProAssetLocationInsertLocation(LocationView lv);
void MProAssetLocationDeleteLocationByCustIDAndLocationID(string custId, string locationId);
void MProAssetLocationUpdateLocationByCustIDAndLoacationID(LocationView lv);
}
The repo layer:
public class CalLocationsRepo : RepositoryBase<CalLocaton>, ICalLocationsRepo
{
public CalLocationsRepo(IDbFactory dbFactory)
: base(dbFactory)
{
}
//WHERE CalCodeActive=1 AND CalCodeGroup='OSS' ORDER BY CalCode
public IEnumerable<string> GetCalLocations(string empID)
{
return DbContext.TAM_GetCalLocationsList(empID).ToList();
}
}
I was thinking of something like using a context type in our system. I know the entity models calls would be named differently based on our different databases using their own stored procedures, but thought at the level prior to the call i check the context to use, then make the call accordingly like so:
public class DBContextRepo<T> : RepositoryBase, IDBContextRepo<T>
{
DBContextRepo<T> _typeParameterClass;
public DBContextRepo(IDbFactory dbFactory, DBContextRepo<T> typeParameterClass)
: base(dbFactory)
{
_typeParameterClass = typeParameterClass;
}
public List<string> GetAllModelsByManufcaturer(string manufacturerName)
{
List<string> results = new List<string>();
if (_typeParameterClass.GetType() == typeof(TAM.DataLayer.EntityModels.QuoteWerks1Entities))
{
using(var dbContext = DbContextQw)
{
var items = dbContext.Products_OurProducts_Products.Where(p => p.Manufacturer == manufacturerName).ToList();
results = items.Select(p => p.ManufacturerPartNumber).ToList();
}
}
else
{
using (var dbContext = DbContext)
{
var items = dbContext.Models.Where(a => a.Manufacturer.MfrName == manufacturerName);
results = items.Select(m => m.ModelNumber).ToList();
}
}
return results;
}
}
This causes errors and is where im not sure how to handle two DBContext:
public class DbFactory : Disposable, IDbFactory
{
private TAMModel _dbContext;
private QuoteWerks1Entities _dbContextQW;
public TAMModel Init()
{
return _dbContext ?? (_dbContext = new TAMModel());
}
public QuoteWerks1Entities InitQW()
{
return _dbContextQW ?? (_dbContextQW = new QuoteWerks1Entities());
}
protected override void DisposeCore()
{
if (_dbContext != null)
{
_dbContext.Dispose();
}
if (_dbContextQW != null)
{
_dbContextQW.Dispose();
}
}
}
Once i added the second context, all of my regular code say they do not have a corresponding type in constructors such as this one:
public class ContractRepo : RepositoryBase<Contract>, IContractRepo
{
public ContractRepo(IDbFactory dbFactory)
: base(dbFactory)
{
}
public string GetContractIdentifyByCustId(string custId)
{
return DbContext.TAM_GetContractIdentifyByCustId(custId).SingleOrDefault();
}
}
Here is the BaseRepo class:
public class RepositoryBase
{
private readonly TAMModel _dataContext;
private readonly QuoteWerks1Entities _dataContextQW;
protected IDbFactory DbFactory { get; private set; }
protected TAMModel DbContext
{
get
{
return _dataContext ?? DbFactory.Init();
}
}
protected QuoteWerks1Entities DbContextQw
{
get
{
return _dataContextQW ?? DbFactory.InitQW();
}
}
protected RepositoryBase(IDbFactory dbFactory)
{
DbFactory = dbFactory;
}
}
public abstract class RepositoryBase<T> where T : class
{
private readonly TAMModel _dataContext;
private readonly IDbSet<T> _dbSet;
private readonly IDbSet<T> _dbSetQW;
private readonly QuoteWerks1Entities _dataContextQW;
protected IDbFactory DbFactory { get; private set; }
protected TAMModel DbContext
{
get
{
return _dataContext ?? DbFactory.Init();
}
}
protected QuoteWerks1Entities DbContextQW
{
get
{
return _dataContextQW ?? DbFactory.InitQW();
}
}
protected RepositoryBase(IDbFactory dbFactory, T type)
{
DbFactory = dbFactory;
_dbSet = DbContext.Set<T>();
_dbSetQW = DbContextQW.Set<T>();
}
public virtual void Add(T entity)
{
_dbSet.Add(entity);
}
public virtual void Update(T entity)
{
_dbSet.Attach(entity);
DbContext.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
_dbSet.Remove(entity);
}
public virtual void Delete(Expression<Func<T, bool>> where)
{
IEnumerable<T> objects = _dbSet.Where<T>(where);
foreach (T obj in objects)
{
_dbSet.Remove(obj);
}
}
public virtual T GetById(int id)
{
return _dbSet.Find(id);
}
public virtual T GetById(string id)
{
return _dbSet.Find(id);
}
public virtual IEnumerable<T> GetAll()
{
return _dbSet.ToList();
}
public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
{
return _dbSet.Where(where).ToList();
}
public T Get(Expression<Func<T, bool>> where)
{
return _dbSet.Where(where).SingleOrDefault();
}
public virtual IQueryable<T> Query()
{
return _dbSet;
}
public virtual IQueryable<T> Query(Expression<Func<T, bool>> where)
{
return _dbSet.Where(where);
}
public virtual ObjectQuery<U> CreateQuery<U>(string query, ObjectParameter[] parameters)
{
return CastAsObjectContext().CreateQuery<U>(query, parameters);
}
public virtual ObjectQuery<U> CreateQuery<U>(string query)
{
return CreateQuery<U>(query, new ObjectParameter[0] { });
}
public virtual ObjectQuery<DbDataRecord> CreateQuery(string query, ObjectParameter[] parameters)
{
return CreateQuery<DbDataRecord>(query, parameters);
}
public virtual ObjectQuery<DbDataRecord> CreateQuery(string query)
{
return CreateQuery<DbDataRecord>(query);
}
private ObjectContext CastAsObjectContext()
{
var oContext = (DbContext as IObjectContextAdapter).ObjectContext;
return oContext;
}
}
The only problem is, how do I set the context on a login when using dependency injection? Will this work, as at the moment I'm still in the process, or am I over complicating it and going about it the wrong way? Thanks in advance.
I attempting to implement the Unit of Work and Repository Pattern in my ASP.NET MVC app as described here.
I was receiving the following error:
Value cannot be null.
Parameter name: entitySet
during a query. After doing some debugging, I noticed that my DBSet<T> classes were throwing the following error:
{
"The context cannot be used while the model is being created. This exception may be
thrown if the context is used inside the OnModelCreating method or if the same context
instance is accessed by multiple threads concurrently. Note that instance members of
DbContext and related classes are not guaranteed to be thread safe."
}
System.SystemException { System.InvalidOperationException }
I have looked around Stack Overflow but cannot find a solution. I used the "Code First from Database Approach." I checked my connection string and it seems right. My version of Entity, as defined in the packages.config file, is 6.1.3. I have tried commenting out some of the relationships within the DbContext class. The definition of which is as follows:
public partial class BKTrainerContext : DbContext
{
public BKTrainerContext()
: base("name=BKTrainerContext")
{
}
public virtual DbSet<AccessLevel> AccessLevels { get; set; }
public virtual DbSet<BuildCardCategory> BuildCardCategories { get; set; }
public virtual DbSet<BuildCard> BuildCards { get; set; }
public virtual DbSet<Manager> Managers { get; set; }
public virtual DbSet<SliderImage> SliderImages { get; set; }
public virtual DbSet<StoreMessage> StoreMessages { get; set; }
public virtual DbSet<Store> Stores { get; set; }
public virtual DbSet<Video> Videos { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AccessLevel>()
.HasMany(e => e.Managers)
.WithRequired(e => e.AccessLevel1)
.HasForeignKey(e => e.AccessLevel)
.WillCascadeOnDelete(false);
modelBuilder.Entity<BuildCardCategory>()
.HasMany(e => e.BuildCards)
.WithRequired(e => e.BuildCardCategory1)
.HasForeignKey(e => e.BuildCardCategory)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Manager>()
.HasMany(e => e.StoreMessages)
.WithRequired(e => e.Manager)
.HasForeignKey(e => e.MessageAuthor)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Manager>()
.HasMany(e => e.Stores)
.WithRequired(e => e.Manager)
.WillCascadeOnDelete(false);
modelBuilder.Entity<StoreMessage>()
.Property(e => e.MessageBody)
.IsUnicode(false);
modelBuilder.Entity<StoreMessage>()
.HasMany(e => e.Stores)
.WithMany(e => e.StoreMessages)
.Map(m => m.ToTable("MessagesStores").MapLeftKey("MessageID").MapRightKey("StoreID"));
}
}
This is my GenericRepo class
public class GenericRepo<T> : IGenericRepo<T> where T : class
{
internal BKTrainerContext dbContext;
internal DbSet<T> db;
public GenericRepo(BKTrainerContext context)
{
this.dbContext = context;
this.db = context.Set<T>();
}
public virtual IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
{
IQueryable<T> query = db;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual T GetEntity(object id)
{
return db.Find(id);
}
public virtual void Insert(T newEntity)
{
db.Add(newEntity);
}
public virtual void Delete(object id)
{
T enity = db.Find(id);
Delete(enity);
}
public virtual void Delete(T entity)
{
if (dbContext.Entry(entity).State == EntityState.Detached)
{
db.Attach(entity);
}
db.Remove(entity);
}
public virtual void Update(T entity)
{
db.Attach(entity);
dbContext.Entry(entity).State = EntityState.Modified;
}
}
The original error is thrown by the lines within Get(); those that call a query.xx method.
My Unit of Work class is as follows:
public class UnitOfWork : IUnitOfWork
{
private BKTrainerContext dbContext = new BKTrainerContext();
private GenericRepo<BuildCard> buildCardRepo;
private GenericRepo<BuildCardCategory> buildCardCategoryRepo;
private GenericRepo<Manager> managerRepo;
private GenericRepo<StoreMessage> messageRepo;
private GenericRepo<SliderImage> sliderRepo;
private GenericRepo<Store> storeRepo;
private GenericRepo<Video> videoRepo;
private bool disposed = false;
public GenericRepo<BuildCard> BuildCardRepo
{
get
{
if (this.buildCardRepo == null) { buildCardRepo = new GenericRepo<BuildCard>(dbContext); }
return buildCardRepo;
}
}
public GenericRepo<BuildCardCategory> BuildCardCategoriesRepo
{
get
{
if (this.buildCardCategoryRepo == null) { buildCardCategoryRepo = new GenericRepo<BuildCardCategory>(dbContext); }
return buildCardCategoryRepo;
}
}
public GenericRepo<Manager> ManagerRepo
{
get
{
if (this.managerRepo == null) { managerRepo = new GenericRepo<Manager>(dbContext); }
return managerRepo;
}
}
public GenericRepo<StoreMessage> MessageRepo
{
get
{
if (this.messageRepo == null) { messageRepo = new GenericRepo<StoreMessage>(dbContext); }
return messageRepo;
}
}
public GenericRepo<SliderImage> SliderRepo
{
get
{
if (this.sliderRepo == null) { sliderRepo= new GenericRepo<SliderImage>(dbContext); }
return sliderRepo;
}
}
public GenericRepo<Store> StoreRepo
{
get
{
if (this.storeRepo == null) { storeRepo= new GenericRepo<Store>(dbContext); }
return storeRepo;
}
}
public GenericRepo<Video> VideoRepo
{
get
{
if (this.videoRepo == null) {videoRepo = new GenericRepo<Video>(dbContext); }
return videoRepo;
}
}
public void Save()
{
dbContext.SaveChanges();
}
protected virtual void Dispose(bool disposing) {
if (!this.disposed)
{
if (disposing)
{
dbContext.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
I am using Unity to inject it into my Controller.
public class HomeController : Controller
{
private IUnitOfWork worker;
public HomeController(IUnitOfWork unit)
{
this.worker = unit;
}
public ActionResult Index(bool failedLogin = false)
{
IndexViewModel vm = new IndexViewModel();
vm.invalidLogin = failedLogin;
return View(vm);
}
[HttpPost]
public ActionResult AttemptLogin(string userName, string password)
{
List<Store> stores = worker.StoreRepo.Get().ToList();
Store store = worker.StoreRepo.Get(u => u.Username == userName && u.Password == password).FirstOrDefault();
if (stores != null)
{
Session["UserLevel"] = 0;
Session["UserID"] = store.ID;
return RedirectToAction("Index", "User");
}
else
{
Manager manager = worker.ManagerRepo.Get(m => m.Username == userName && m.Password == password).FirstOrDefault();
if (manager != null)
{
Session["UserLevel"] = manager.AccessLevel;
Session["UserID"] = manager.ID;
return RedirectToAction("Index", "Admin");
}
else
{
return RedirectToAction("Index", new { failedLogin = true });
}
}
}
}
I would really appreciate any help. Thanks!
EDIT:
Injection configuration:
public class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
/// <summary>
/// Gets the configured Unity container.
/// </summary>
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
#endregion
/// <summary>Registers the type mappings with the Unity container.</summary>
/// <param name="container">The unity container to configure.</param>
/// <remarks>There is no need to register concrete types such as controllers or API controllers (unless you want to
/// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered.</remarks>
public static void RegisterTypes(IUnityContainer container)
{
// NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
// container.LoadConfiguration();
container.RegisterType<IUnitOfWork, UnitOfWork>();
// container.RegisterType<IProductRepository, ProductRepository>();
}
}
}
The UnityMVCActivator class
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(BKBuildCard.App_Start.UnityWebActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(BKBuildCard.App_Start.UnityWebActivator), "Shutdown")]
namespace BKBuildCard.App_Start
{
/// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary>
public static class UnityWebActivator
{
/// <summary>Integrates Unity when the application starts.</summary>
public static void Start()
{
var container = UnityConfig.GetConfiguredContainer();
FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
/// <summary>Disposes the Unity container when the application is shut down.</summary>
public static void Shutdown()
{
var container = UnityConfig.GetConfiguredContainer();
container.Dispose();
}
}
}
EDIT
I attempted to run the application by using a concrete implementation of the class, but the problem persists.
private UnitOfWork worker;
public HomeController() {
this.worker = new UnitOfWork();
}
Just change one line in your RegisterTypes method:
container.RegisterType<IUnitOfWork, UnitOfWork>(new TransientLifetimeManager());
or try changing to
container.RegisterType<IUnitOfWork, UnitOfWork>(new PerThreadLifetimeManager());
What is happening here is that multiple threads are trying to share the same DbContext. The second one tries querying the model while the model is still being built on first thread. The solution would be to make sure that no 2 threads share the same DbContext. If the DbContext would be different on different threads, each one would build and query model on its own.
Finally figured it out. I neglected to mark a customer property on my class as [NotMapped].
public HttpPostedFileBase cardImg { get; set; }
The correction simply involved adding the attribute.
[NotMapped]
public HttpPostedFileBase cardImg { get; set; }
I was under the impression that the following error:
Value cannot be null.
Parameter name: entitySet
was caused by the context issues. Evidently, it was the reverse; the null parameter issue was causing "Context cannot be used" error. This is a good summary of resolving issues related to null entitySet parameters (which can cause the context issues noted above).
The solution was simple, but, it would not have been my initial guess based upon the error message. As such, I hope somebody may be able to gain some use insight from my foolish mistake.
I get a weird behavior with NHibernate with Fluent Configuration.
Whenever a generic exception unrelated to the NHibernate occurs i.e. in the view a DivideByZeroException every request after the exception throws.
An exception of type 'NHibernate.LazyInitializationException' occurred in NHibernate.dll but was not handled in user code. Additional information: Initializing[Entity]-Could not initialize proxy - no Session.
Due to nature of the bug the bug is critical due to the fact that 1 user can make the whole website dead if he generates an exception
Following it is my HttpModule for Nhibernate with Asp.Net MVC 5 that takes care of sessions.
NHibernateSessionPerRequest.cs
public class NHibernateSessionPerRequest : IHttpModule
{
private static readonly ISessionFactory SessionFactory;
// Constructs our HTTP module
static NHibernateSessionPerRequest()
{
SessionFactory = CreateSessionFactory();
}
// Initializes the HTTP module
public void Init(HttpApplication context)
{
context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
}
// Disposes the HTTP module
public void Dispose() { }
// Returns the current session
public static ISession GetCurrentSession()
{
return SessionFactory.GetCurrentSession();
}
// Opens the session, begins the transaction, and binds the session
private static void BeginRequest(object sender, EventArgs e)
{
ISession session = SessionFactory.OpenSession();
session.BeginTransaction();
CurrentSessionContext.Bind(session);
}
// Unbinds the session, commits the transaction, and closes the session
private static void EndRequest(object sender, EventArgs e)
{
ISession session = CurrentSessionContext.Unbind(SessionFactory);
if (session == null) return;
try
{
session.Transaction.Commit();
}
catch (Exception)
{
session.Transaction.Rollback();
throw;
}
finally
{
session.Close();
session.Dispose();
}
}
// Returns our session factory
private static ISessionFactory CreateSessionFactory()
{
if (HttpContext.Current != null) //for the web apps
_configFile = HttpContext.Current.Server.MapPath(
string.Format("~/App_Data/{0}", CacheFile)
);
_configuration = LoadConfigurationFromFile();
if (_configuration == null)
{
FluentlyConfigure();
SaveConfigurationToFile(_configuration);
}
if (_configuration != null) return _configuration.BuildSessionFactory();
return null;
}
// Returns our database configuration
private static MsSqlConfiguration CreateDbConfigDebug2()
{
return MsSqlConfiguration
.MsSql2008
.ConnectionString(c => c.FromConnectionStringWithKey("MyConnection"));
}
// Updates the database schema if there are any changes to the model,
// or drops and creates it if it doesn't exist
private static void UpdateSchema(Configuration cfg)
{
new SchemaUpdate(cfg)
.Execute(false, true);
}
private static void SaveConfigurationToFile(Configuration configuration)
{
using (var file = File.Open(_configFile, FileMode.Create))
{
var bf = new BinaryFormatter();
bf.Serialize(file, configuration);
}
}
private static Configuration LoadConfigurationFromFile()
{
if (IsConfigurationFileValid == false)
return null;
try
{
using (var file = File.Open(_configFile, FileMode.Open))
{
var bf = new BinaryFormatter();
return bf.Deserialize(file) as Configuration;
}
}
catch (Exception)
{
return null;
}
}
private static void FluentlyConfigure()
{
if (_configuration == null)
{
_configuration = Fluently.Configure()
.Database(CreateDbConfigDebug2)
.CurrentSessionContext<WebSessionContext>()
.Cache(c => c.ProviderClass<SysCacheProvider>().UseQueryCache())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<EntityMap>()
.Conventions.Add(DefaultCascade.All(), DefaultLazy.Always()))
.ExposeConfiguration(UpdateSchema)
.ExposeConfiguration(c => c.Properties.Add("cache.use_second_level_cache", "true"))
.BuildConfiguration();
}
}
private static bool IsConfigurationFileValid
{
get
{
var ass = Assembly.GetAssembly(typeof(EntityMap));
var configInfo = new FileInfo(_configFile);
var assInfo = new FileInfo(ass.Location);
return configInfo.LastWriteTime >= assInfo.LastWriteTime;
}
}
private static Configuration _configuration;
private static string _configFile;
private const string CacheFile = "hibernate.cfg.xml";
}
Edit
The Repository Implementation i use
public class Repository<T> : IIntKeyedRepository<T> where T : class
{
private readonly ISession _session;
public Repository()
{
_session = NHibernateSessionPerRequest.GetCurrentSession();
}
#region IRepository<T> Members
public bool Add(T entity)
{
_session.Save(entity);
return true;
}
public bool Add(System.Collections.Generic.IEnumerable<T> items)
{
foreach (T item in items)
{
_session.Save(item);
}
return true;
}
public bool Update(T entity)
{
_session.Update(entity);
return true;
}
public bool Delete(T entity)
{
_session.Delete(entity);
return true;
}
public bool Delete(System.Collections.Generic.IEnumerable<T> entities)
{
foreach (T entity in entities)
{
_session.Delete(entity);
}
return true;
}
#endregion
#region IIntKeyedRepository<T> Members
public T FindBy(int id)
{
return _session.Get<T>(id);
}
#endregion
#region IReadOnlyRepository<T> Members
public IQueryable<T> All()
{
return _session.Query<T>();
}
public T FindBy(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
return FilterBy(expression).Single();
}
public IQueryable<T> FilterBy(System.Linq.Expressions.Expression<System.Func<T, bool>> expression)
{
return All().Where(expression).AsQueryable();
}
#endregion
}
Edit 2
The base controller class I use
public class BaseController : Controller
{
private readonly IRepository<UserEntity> _userRepository;
public BaseController()
{
_userRepository = new Repository<UserEntity>();
BaseModel = new LayoutModel {Modals = new List<string>()};
}
public UserEntity LoggedUser { get; set; }
public LayoutModel BaseModel { get; set; }
protected override void OnActionExecuting(ActionExecutingContext ctx)
{
base.OnActionExecuting(ctx);
if (HttpContext.User.Identity.IsAuthenticated)
{
if (Session != null && Session["User"] != null)
{
LoggedUser = (User) Session["User"];
}
var curUsername = HttpContext.User.Identity.Name;
if (LoggedUser == null || LoggedUser.Entity2.un!= curUsername)
{
LoggedUser = _userRepository.FindBy(u => u.Entity2.un== curUsername);
Session["User"] = LoggedUser;
}
BaseModel.LoggedUser = LoggedUser;
BaseModel.Authenticated = true;
}
else
{
LoggedUser = new UserEntity
{
Entity= new Entity{un= "Guest"},
};
BaseModel.LoggedUser = LoggedUser;
}
}
}
The extended question and all the snippets - are finally helping to find out where is the issue.
There is a really big issue: Session["User"] = LoggedUser;
This would hardly work. Why?
because we place into long running object (Web Session)
an instance loaded via very shortly lasting Web Request
Not all its properties will/could be loaded, When we place LoggedUser into session. It could be just a root entity with many proxies representing references and collections. These will NEVER be loaded later, because its Mather session is closed... gone
Solution?
I would use .Clone() of the User object. In its implementation we can explicitly load all needed references and collections and clone them as well. Such object could be placed into the Web Session
[Serializable]
public class User, ICloneable, ...
{
...
public override object Clone()
{
var entity = base.Clone() as User;
entity.Role = Role.Clone() as Role;
...
return entity;
}
So, what would be placed into session?
Session["User"] = LoggedUser.Clone();
As Radim Köhler noted i was saving a lazy-loaded object in Session that caused the problem.
But i wanted to avoid the Serilization of all objects and i fixed it as follows.
I added the following method to eager-load an entity instead of lazy
public T FindByEager(int id)
{
T entity = FindBy(id);
NHibernateUtil.Initialize(entity);
return entity;
}
And changed BaseController to
if (Session != null) Session["User"] = userRepository.FindByEager(LoggedUser.Id);
I am working on crud operations in mvc 4.0 with unitofwork and generic repository with Ninject for DI.
I am able to get a particular record from a table, I am even able to get all the records from the table.
but I am not able to insert a new record in the database table. I am not getting any error/exception and it is running each statement
cleanly but there is no effect in database below is my controller where I am using the repository and unitof work.
Can somebody tell me where I am wron or what code/statements I have left in this code. I ahve checked it lot of time and I am stucked now.
Not getting the problem
Controller:
private IUnitOfWork _unitOfWork;
private IRepository<tbl_Employee> _Repo;
private IRepository<tbl_Department> _Department;
public HomeController( IUnitOfWork UOW, IRepository<tbl_Employee> Repository, IRepository<tbl_Department> Depart)
{
this._unitOfWork = UOW;
this._Repo = Repository;
this._Department = Depart;
}
//This runs successfully and gets all the records in the view page and I am displaying all records using foreach in div structure
public ActionResult Index()
{
EmployeeModel ObjModel = new EmployeeModel();
ObjModel.Employees = this._Repo.GetALL();
//ObjModel.Employees = this._Employee.GetEmployees();
return View(ObjModel);
}
//This also runs successfully and it brought me a single record on selection of particular record from employee listing.
public ActionResult EmployeeDetail(string id)
{
EmployeeDetailModel ObjModel = new EmployeeDetailModel();
if (!string.IsNullOrEmpty(id))
{
var Employee = this._Repo.Find(Convert.ToInt32(id));
if (Employee != null)
{
ObjModel.InjectFrom(Employee);
}
}
return View(ObjModel);
}
// Here is the problem . Not able to insert the record. The model object is not empty . I have checked it and there is no error.It brought me a message
"Employee Created Successfully but in database there is no record.
public ActionResult SaveEmployee(EmployeeDetailModel Model)
{
string Msg = string.Empty;
try
{
tbl_Employee ObjEmployee = new tbl_Employee();
ObjEmployee.InjectFrom(Model);
if (Model.Male)
{
ObjEmployee.Sex = "m";
}
else
{
ObjEmployee.Sex = "f";
}
ObjEmployee.Department_Id = Model.Dept_id;
ObjEmployee.Salary = Convert.ToInt32(Model.Salary);
this._Repo.Insert(ObjEmployee);
this._unitOfWork.Commit();
Msg = "Employee Created Successfully";
}
catch
{
Msg = "Error occurred while creating the employee, Please try again.";
}
return Json(new { Message = Msg });
}
/// Repository interface
public interface IRepository<T> where T : class
{
void Insert(T entity);
void Delete(T entity);
void Update(T entity);
T Find(int key);
IEnumerable<T> GetALL();
}
Repository class
public class Repository<T> : Connection, IRepository<T> where T : class
{
private readonly DbSet<T> _dbSet;
public Repository()
{
_dbSet = _dbContext.Set<T>();
}
public void Insert(T entity)
{
_dbSet.Add(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public void Update(T entity)
{
var updated = _dbSet.Attach(entity);
_dbContext.Entry(entity).State = EntityState.Modified;
//_dataContext.Entry(item).State = EntityState.Modified;
}
public T Find(int Key)
{
var dbResult = _dbSet.Find(Key);
return dbResult;
}
public IEnumerable<T> GetALL()
{
return _dbSet;
}
}
UnitofWork Interface
public interface IUnitOfWork : IDisposable
{
void Commit();
}
Unit of work class
public class UnitOfWork : Connection, IUnitOfWork
{
private bool _disposed;
public void Commit()
{
_dbContext.SaveChanges();
}
public void Dispose()
{
Dispose(true);
// Take yourself off the Finalization queue to prevent finalization code for object from executing a second time.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// Check to see if Dispose has already been called.
if (!_disposed)
{
// If disposing equals true, dispose all managed and unmanaged resources.
if (disposing)
{
// Dispose managed resources.
if (_dbContext != null)
{
_dbContext.Dispose();
}
}
}
_disposed = true;
}
}
My UnitofWork and Repository class derives from connection class where dbcontext is defined.
public abstract class Connection
{
protected db_TestEntities _dbContext;
public Connection()
{
this._dbContext = new db_TestEntities();
}
}
Is it that my dbContext is creating a new instance everytime like explained Here
and if yes then how can I resolve it.
tbl_Employee ObjEmployee = new tbl_Employee();
ObjEmployee.InjectFrom(Model);
if (Model.Male)
{
ObjEmployee.Sex = "m";
}
else
{
ObjEmployee.Sex = "f";
}
ObjEmployee.Department_Id = Model.Dept_id;
ObjEmployee.Salary = Convert.ToInt32(Model.Salary);
this._Repo.Insert(ObjEmployee);
After this, you should see your object mapped by EF in local memory.
this._unitOfWork.Commit();
Here your object should be pushed to database. dbContext.SaveChanges() return number of changed records which should be in your case 1.
Msg = "Employee Created Successfully";
Update:
So the problem is in your Connection class as you suggested.
I would create your DbContext in one place and then pass it to repository and unit of work. You could also create DbContext in unit of work constructor and then pass UOW to repository. This is one of my older implementation of this:
public class EntityFrameworkUnitOfWork : IUnitOfWork
{
private ForexDbContext dbContext;
internal ForexDbContext DbContext
{
get { return dbContext ?? (dbContext = new ForexDbContext()); }
}
internal DbSet<T> Set<T>()
where T : class
{
return DbContext.Set<T>();
}
public void Dispose()
{
if(dbContext == null) return;
dbContext.Dispose();
dbContext = null;
}
public void SaveChanges()
{
int result = DbContext.SaveChanges();
}
public ITransaction BeginTransaction()
{
return new EntityFrameworkTransaction(DbContext.BeginTransaction());
}
}
public class ContactsRepositoryWithUow : IRepository<Contact>
{
private SampleDbEntities entities = null;
public ContactsRepositoryWithUow(SampleDbEntities _entities)
{
entities = _entities;
}
public IEnumerable<Contact> GetAll(Func<Contact, bool> predicate = null)
{
if (predicate != null)
{
if (predicate != null)
{
return entities.Contacts.Where(predicate);
}
}
return entities.Contacts;
}
public Contact Get(Func<Contact, bool> predicate)
{
return entities.Contacts.FirstOrDefault(predicate);
}
public void Add(Contact entity)
{
entities.Contacts.AddObject(entity);
}
public void Attach(Contact entity)
{
entities.Contacts.Attach(entity);
entities.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
public void Delete(Contact entity)
{
entities.Contacts.DeleteObject(entity);
}
}
Please find answer in below link for more details
Crud Operation with UnitOfWork
Here is a image from the ANTS memory profiler. It seens that there are a lot of objects hold in memory. How can I find what I am doing wrong?
**UPDATE**
Here is my repository classes:
public class Repository<T> : IRepository<T> where T : class, IDataEntity
{
ObjectContext _context;
IObjectSet<T> _objectSet;
readonly string _entitySetName;
readonly string[] _keyNames;
private ObjectContext Context
{
get
{
if (_context == null)
{
_context = GetCurrentUnitOfWork<EFUnitOfWork>().Context;
}
return _context;
}
}
private IObjectSet<T> ObjectSet
{
get
{
if (_objectSet == null)
{
_objectSet = this.Context.CreateObjectSet<T>();
}
return _objectSet;
}
}
public TUnitOfWork GetCurrentUnitOfWork<TUnitOfWork>() where TUnitOfWork : IUnitOfWork
{
return (TUnitOfWork)UnitOfWork.Current;
}
public virtual IEnumerable<T> GetQuery()
{
return ObjectSet;
}
public virtual IEnumerable<T> GetQuery(params Expression<Func<T, object>>[] includes)
{
return ObjectSet.IncludeMultiple(includes);
}
public virtual IEnumerable<T> GetQuery(
IEnumerable<Expression<Func<T, bool>>> filters,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy,
IEnumerable<Expression<Func<T, object>>> includes)
{
IQueryable<T> _query = ObjectSet;
if (filters != null)
{
foreach (var filter in filters)
{
_query = _query.Where(filter);
}
}
if (includes != null && includes.Count() > 0)
{
_query = _query.IncludeMultiple(includes.ToArray());
}
if (orderBy != null)
{
_query = orderBy(_query);
}
return _query;
}
public virtual IPaged<T> GetQuery(
IEnumerable<Expression<Func<T, bool>>> filters,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy,
int pageNumber, int pageSize,
IEnumerable<Expression<Func<T, object>>> includes)
{
IQueryable<T> _query = ObjectSet;
if (filters != null)
{
foreach (var filter in filters)
{
_query = _query.Where(filter);
}
}
if (orderBy != null)
{
_query = orderBy(_query);
}
IPaged<T> page = new Paged<T>(_query, pageNumber, pageSize, includes);
return page;
}
public virtual void Insert(T entity)
{
this.ObjectSet.AddObject(entity);
}
public virtual void Delete(T entity)
{
if (entity is ISoftDeletable)
{
((ISoftDeletable)entity).IsDeleted = true;
//Update(entity);
}
else
{
this.ObjectSet.DeleteObject(entity);
}
}
public virtual void Attach(T entity)
{
ObjectStateEntry entry = null;
if (this.Context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry) == false)
{
this.ObjectSet.Attach(entity);
}
}
public virtual void Detach(T entity)
{
ObjectStateEntry entry = null;
if (this.Context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry) == true)
{
this.ObjectSet.Detach(entity);
}
}
}
Now, if I have class A that holds records from table A, I also create class:
public class ARepository:BaseRepository<A> {
// Implementation of A's queries and specific db operations
}
Here is my EFUnitOfWork class:
public class EFUnitOfWork : IUnitOfWork, IDisposable
{
public ObjectContext Context { get; private set; }
public EFUnitOfWork(ObjectContext context)
{
Context = context;
context.ContextOptions.LazyLoadingEnabled = true;
}
public void Commit()
{
Context.SaveChanges();
}
public void Dispose()
{
if (Context != null)
{
Context.Dispose();
}
GC.SuppressFinalize(this);
}
}
And UnitOfWork class:
public static class UnitOfWork
{
private const string HTTPCONTEXTKEY = "MyProj.Domain.Business.Repository.HttpContext.Key";
private static IUnitOfWorkFactory _unitOfWorkFactory;
private static readonly Hashtable _threads = new Hashtable();
public static void Commit()
{
IUnitOfWork unitOfWork = GetUnitOfWork();
if (unitOfWork != null)
{
unitOfWork.Commit();
}
}
public static IUnitOfWork Current
{
get
{
IUnitOfWork unitOfWork = GetUnitOfWork();
if (unitOfWork == null)
{
_unitOfWorkFactory = ObjectFactory.GetInstance<IUnitOfWorkFactory>();
unitOfWork = _unitOfWorkFactory.Create();
SaveUnitOfWork(unitOfWork);
}
return unitOfWork;
}
}
private static IUnitOfWork GetUnitOfWork()
{
if (HttpContext.Current != null)
{
if (HttpContext.Current.Items.Contains(HTTPCONTEXTKEY))
{
return (IUnitOfWork)HttpContext.Current.Items[HTTPCONTEXTKEY];
}
return null;
}
else
{
Thread thread = Thread.CurrentThread;
if (string.IsNullOrEmpty(thread.Name))
{
thread.Name = Guid.NewGuid().ToString();
return null;
}
else
{
lock (_threads.SyncRoot)
{
return (IUnitOfWork)_threads[Thread.CurrentThread.Name];
}
}
}
}
private static void SaveUnitOfWork(IUnitOfWork unitOfWork)
{
if (HttpContext.Current != null)
{
HttpContext.Current.Items[HTTPCONTEXTKEY] = unitOfWork;
}
else
{
lock(_threads.SyncRoot)
{
_threads[Thread.CurrentThread.Name] = unitOfWork;
}
}
}
}
Here is how I use this:
public class TaskPriceRepository : BaseRepository<TaskPrice>
{
public void Set(TaskPrice entity)
{
TaskPrice taskPrice = GetQuery().SingleOrDefault(x => x.TaskId == entity.TaskId);
if (taskPrice != null)
{
CommonUtils.CopyObject<TaskPrice>(entity, ref taskPrice);
}
else
{
this.Insert(entity);
}
}
}
public class BranchRepository : BaseRepository<Branch>
{
public IList<Branch> GetBranchesList(Guid companyId, long? branchId, string branchName)
{
return Repository.GetQuery().
Where(b => companyId == b.CompanyId).
Where(b => b.IsDeleted == false).
Where(b => !branchId.HasValue || b.BranchId.Equals(branchId.Value)).
Where(b => branchName == null || b.BranchName.Contains(branchName)).
ToList();
}
}
[WebMethod]
public void SetTaskPrice(TaskPriceDTO taskPrice)
{
TaskPrice tp = taskPrice.ToEntity();
TaskPriceRepository rep = new TaskPriceRepository();
rep.Set(tp);
UnitOfWork.Commit();
}
[WebMethod]
public IList<Branch> GetBranchesList()
{
BranchRepository rep = new BranchRepository();
return rep.GetBranchesList(m_User.UserCompany.CompanyId, null, null).ToList();
}
I hope this is enough info to help me solving the problem. Thanks.
UPDATE 2
There is also UnitOfWorkFactory that initializes UnitOfWork:
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
private static Func<ObjectContext> _objectContextDelegate;
private static readonly Object _lockObject = new object();
public static void SetObjectContext(Func<ObjectContext> objectContextDelegate)
{
_objectContextDelegate = objectContextDelegate;
}
public IUnitOfWork Create()
{
ObjectContext context;
lock (_lockObject)
{
context = _objectContextDelegate();
}
return new EFUnitOfWork(context);
}
}
In order to use this, in the application startup I use structuremap:
ObjectFactory.Initialize(x =>
{
x.For<IUnitOfWorkFactory>().Use<UnitOfWorkFactory>();
x.For(typeof(IRepository<>)).Use(typeof(Repository<>));
});
I have a hunch you don't dispose the context.
I suggest disposing the context whenever you done interacting with database.
Use using statement whenever you create the context.
[Edit]
As far as I can see, you cache and don't dispose your EFUnitOfWork object. It is disposable, which is correct, but I don't see when disposable is called. Seems like you hold a reference to the context for all application run time. Moreover, you create and hold one context per thread, which will make it even worse.
I can't tell you for sure where you should put Dispose or using, as I don't know the usages.
You could put it probably to your Commit method, but I don't know if the Commit called only once during database interaction session.
Also, your design might be overcomplicated.
If I were you, I would:
Find the way to dispose the context using current code, as a short-term solution
Simplify the design, as the long-term solution
If I had time I would do long-term solution right away.
But again, I can't tell if the complexity of your design is justified, as I don't know how big your application is and what it does and what the requirements are.
Couple of things come to my mind:
You aren't probably Disposing the ObjectContext. Make sure all your database codes are within using(var context = CreateObjectContext()) block
You have an N-tier architecture and you are passing entities from the data access layer to upper layer without Detaching the entities from ObjectContext. You need to call ObjectContext.Detach(...)
You are most likely returning a full collection of entities, instead of returning a single enity for single Get operations. For ex, you have queries like from customer in context.Customers select customer instead of doing from customer in context.Customers select customer.FirstOrDefault()
I have had hard time making Entity Framework to work in an N-tier application. It's just not suitable for using in N-tier apps as is. Only EF 4.0 is. You can read about all my adventure in making EF 3 work in an N-tier app.
http://www.codeproject.com/KB/linq/ef.aspx
Does this answer your question?
Do you clear the ObjectContext once in a while. If you keep an ObjectContext alive for a long time this will consume memory related to the size of the EntityDataModel and the number of Entities loaded into this ObjectContext.
I had the same problem in a class which uses dependency injection, so the using() option was not an alternative. My solution was to add DbContextOptions<Context> to the constructor and as a private field to the class. Then, you can call
_db.Dispose();
_db = new BlockExplorerContext(_dBContextOptions);
at appropriate times. This fixed my problem where I was running out of RAM and the application was killed by the OS.