First of all, I would like to say that I read the related posts (notably EF 4.1 SaveChanges not updating navigation or reference properties, Entity Framework Code First - Why can't I update complex properties this way?, and Entity Framework 4.1 RC (Code First) - Entity not updating over association).
However, I could not solve my problem. I am quite new to Entity Framework so I guess I must have misunderstood those posts answers.
Anyway I would be really grateful is someone could help me understand because I am quite stuck.
I have two tables :
Person
Item with a nullable PersonId and a Type
An item can have an owner, or not.
Consequently, Person has an Items property which is an IEnumerable of Item.
A person can have one only Item by type.
If the person wants to change, he can replace his current item by any other of the same type in his items :
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
Here is the repositories structure and the AddOrUpdate method :
public class PersonRepo : RepositoryBase<Person>
{
...
}
public class RepositoryBase<TObject> where TObject : class, IEntity
{
protected MyEntities entities
{
get { return UnitOfWork.Current.Context; }
}
public virtual void AddOrUpdate(TObject entity)
{
if (entity != null)
{
var entry = entities.Entry<IEntity>(entity);
if (Exists(entity.Id))
{
if (entry.State == EntityState.Detached)
{
var set = entities.Set<TObject>();
var currentEntry = set.Find(entity.Id);
if (currentEntry != null)
{
var attachedEntry = entities.Entry(currentEntry);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
set.Attach(entity);
entry.State = EntityState.Modified;
}
}
else
entry.State = EntityState.Modified;
}
else
{
entry.State = EntityState.Added;
}
}
}
}
This works pretty well and the old and the new items' PersonId properties are correctly updated in database.
However, if I check person.Items after the SaveChanges(), the old item still appears instead of the new one and I need it to be correct in order to update the page's controls values.
Although I read the posts with the same issue I could not resolve it...
I tried lots of things, notably calling entities.Entry(person).Collection(p => p.Items).Load() but got an exception each time I tried.
If somebody has any idea please feel free, I can add some more code if needed.
Thanks a lot !
EDIT : UnitOfWork
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
public class UnitOfWork : IDisposable
{
private const string _httpContextKey = "_unitOfWork";
private MyEntities _dbContext;
public static UnitOfWork Current
{
get { return (UnitOfWork)HttpContext.Current.Items[_httpContextKey]; }
}
public UnitOfWork()
{
HttpContext.Current.Items[_httpContextKey] = this;
}
public MyEntities Context
{
get
{
if (_dbContext == null)
_dbContext = new MyEntities();
return _dbContext;
}
}
public void Commit()
{
_dbContext.SaveChanges();
}
public void Dispose()
{
if (_dbContext != null)
_dbContext.Dispose();
}
}
Two solutions that worked
Solution 1 (reload from context after SaveChanges)
public partial class MyPage
{
private MyService service;
private Person person;
protected void Page_Load(object sender, EventArgs e)
{
service = new MyService();
person = service.GetCurrentPerson(Request.QueryString["id"]);
...
}
protected void SelectNewItem(object sender, EventArgs e)
{
Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);
service.SelectNewItem(person, itemId);
UpdatePage();
}
private void UpdatePage()
{
if (person != null)
person = service.GetCurrentPerson(Request.QueryString["id"]);
// Update controls values using person's properties here
}
}
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
Solution 2 (update database AND property)
public partial class MyPage
{
private MyService service;
private Person person;
protected void Page_Load(object sender, EventArgs e)
{
service = new MyService();
person = service.GetCurrentPerson(Request.QueryString["id"]);
...
}
protected void SelectNewItem(object sender, EventArgs e)
{
Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);
service.SelectNewItem(person, itemId);
UpdatePage();
}
private void UpdatePage()
{
// Update controls values using person's properties here
}
}
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
person.Items.Remove(oldItem);
person.Items.Add(newItem);
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
How about refreshing your context to make sure you have the latest db changes after the .SaveChanges() method. Pass in the entity to be refreshed an call Refresh on the context:
((IObjectContextAdapter)_dbContext).ObjectContext.Refresh(RefreshMode.StoreWins, entityPassed);
Or leave the Commit() method as is and use a more dynamic approach something like:
var changedEntities = (from item in context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added
| EntityState.Deleted
| EntityState.Modified
| EntityState.Unchanged)
where item.EntityKey != null
select item.Entity);
context.Refresh(RefreshMode.StoreWins, changedEntities);
The RefreshMode.StoreWins simply indicates that the database (store) takes priority and will override client (in-memory) changes.
If the Refresh method does not work, you can consider the following:
public void RefreshEntity(T entity)
{
_dbContext.Entry<T>(entity).Reload();
}
Or if all else fails, keep it simple and Dispose of your DbContext once you're done with each transaction (In this case after SaveChanges() has been called). Then if you need to use results after a commit, treat it as a new transaction and, instantiating a fresh DbContext and load your necessary data again.
Use Transection for example.
It's working fine.
public class UnitOfWork : IUnitOfWork
{
public readonly DatabaseContext _context;
private readonly IDbTransaction _transaction;
private readonly ObjectContext _objectContext;
public UnitOfWork(DatabaseContext context)
{
_context = context as DatabaseContext ?? new DatabaseContext ();
this._objectContext = ((IObjectContextAdapter)this._context).ObjectContext;
if (this._objectContext.Connection.State != ConnectionState.Open)
{
this._objectContext.Connection.Open();
this._transaction = _objectContext.Connection.BeginTransaction();
}
}
public int Complete()
{
int result = 0;
try
{
result = _context.SaveChanges();
this._transaction.Commit();
}
catch (Exception ex)
{
Rollback();
}
return result;
}
private void Rollback()
{
this._transaction.Rollback();
foreach (var entry in this._context.ChangeTracker.Entries())
{
switch (entry.State)
{
case System.Data.Entity.EntityState.Modified:
entry.State = System.Data.Entity.EntityState.Unchanged;
break;
case System.Data.Entity.EntityState.Added:
entry.State = System.Data.Entity.EntityState.Detached;
break;
case System.Data.Entity.EntityState.Deleted:
entry.State = System.Data.Entity.EntityState.Unchanged;
break;
}
}
}
public void Dispose()
{
if (this._objectContext.Connection.State == ConnectionState.Open)
{
this._objectContext.Connection.Close();
}
_context.Dispose();
}
}
Related
I currently have a multi one-to-many relationship hierarchy database tblProjects->tblLines->tblGroups->tblStations etc. And an Entity framework 6 model.
These entity framework classes all implement a base class "tblBase":
public abstract class TblBase : INotifyPropertyChanged
{
private int _id;
public int ID
{
get
{
return _id;
}
set
{
_id = value;
NotifyPropertyChanged();
}
}
private Nullable<int> _coid;
public Nullable<int> COID
{
get
{
NotifyPropertyChanged();
return _coid;
}
set
{
_coid = value;
NotifyPropertyChanged();
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged();
}
}
I have a treeview that allows me to select any node as the parent type, and currently I have a method for each type that allows me to reload all the child entities.
I would like to see how this could be made generic:
private async static Task<bool> RefreshLinesAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
//Scan through all DB child entities and reload their DB values
foreach (var SubEntity in DBContext.tblLines.Where(x => x.ProjectID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
//Continue to child entities method
await RefreshGroupsAsync(ThisUser, DBContext, SubEntity);
}
return true;
}
private async static Task<bool> RefreshGroupsAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.tblGroups.Where(x => x.LineID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
await RefreshStationsAsync(ThisUser,DBContext, SubEntity);
}
return true;
}
The only method I can see useful is Set(), although it does not provide a Where() method, which is critical since I do not want to retrieve the entire table.
You can make your functions generic. They maybe like this one:
private async static Task<bool> RefreshLinesAsync<TEntity>(LocalUser ThisUser, ProjectEntities DBContext, TEntity Entity) where TEntity : TblBase
{
List<TEntity> NonExistingNodes = new List<TEntity>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.Set<TEntity>().Where(x => (x as TblBase).ProjectID == PassedEntity.ID).ToList()) {
//Your other code here...
}
}
The where clause in function definition, make you sure that this method can be called only with subclasses of TblBase.
EDIT:
I forgot to mention that you need to cast SubEntity as TblBase inside foreach loop to use it...
EDIT (in response of comments):
If you need to get all TblBase subclasses from your entity, you cannot make your function so generic if you keep them in separate tables: It will became hardly mantainable when you have to add more subclasses.
I suggest you to use a single table through Table Per Hierarchy (see this article in MSDN) changing TblBase from abstract to concrete class, then you can get all of them this way:
var allSubClassEntities = DBContext.Set<TblBase>();
I have a "DatabaseController" Class, which contains several repository classes, each for every table in a database, and they only perform crud operations. My issue is this.
When i delete an entry , i call the corresponding repository method. I also need to call some delete methods from other tables (repositories). What is the best approach to do this?
Example Code:
DatabaseController Class
public DatabaseController(){
this.dbContext = new WSATDbContext();
}
private IWSATGenericRepository<Restmethod> _restmethod ;
private IWSATGenericRepository<Soapservice> _soapservice ;
public IWSATGenericRepository<Restmethod> Restmethod
{
get
{
if (_restmethod == null)
{
_restmethod = new RestmethodRepository(dbContext);
}
return _restmethod;
}
}
public IWSATGenericRepository<Soapservice> Soapservice
{
get
{
if (_soapservice == null)
{
_soapservice = new SoapserviceRepository(dbContext);
}
return _soapservice;
}
}
Example Repository:
public class RestmethodRepository : IWSATGenericRepository<Restmethod>
{
public RestmethodRepository(DbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("Null DbContext");
this.dbContext = dbContext;
this.dbSet = dbContext.Set<Restmethod>();
}
private DbContext dbContext { get; set; }
private DbSet<Restmethod> dbSet { get; set; }
public Restmethod get(int id)
{
Restmethod restmethod = dbSet.Find(id) ;
dbContext.Entry(restmethod).Collection(s => s.methodkeywords).Load() ;
return restmethod ;
}
public Restmethod delete(int id)
{
var entity = this.get(id);
//I want to call some other repo methods here
dbContext.Entry(entity).State = EntityState.Deleted;
try
{
dbContext.SaveChanges();
}
catch (DbUpdateException e)
{
System.Diagnostics.Debug.WriteLine(e.StackTrace);
return null;
}
return entity;
}
}
}
EDIT: Is it good practice to pass the "DatabaseController" UOW class as an argument to each repository? Are there any theoretical problems with that? This is an academic project , so i'd like to stay as formal as possible.
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
I have the following code that creates a new bookmark and adds one or more tags to it. If a tag does not already exist it is created and added to the bookmark.
Bookmark bookmark = new Bookmark();
bookmark.Title = request.Title;
bookmark.Link = request.Link;
bookmark.DateCreated = request.DateCreated;
bookmark.DateModified = request.DateCreated;
bookmark.User = _userRepository.GetUserByUsername(request.Username);
IList<Tag> myTags = _tagRepository.GetTags(request.Username);
IList<string> myTagsToString = myTags.Select(x => x.Title).ToList<string>();
foreach (var tag in request.Tags)
{
if (myTagsToString.Contains(tag))
{
Tag oldTag = myTags.SingleOrDefault(x => x.Title == tag);
bookmark.Tags.Add(oldTag);
}
else
{
Tag newTag = new Tag();
newTag.Title = tag;
newTag.User = _userRepository.GetUserByUsername(request.Username);
newTag.DateCreated = request.DateCreated;
newTag.DateModified = request.DateCreated;
bookmark.Tags.Add(newTag);
}
}
_bookmarkRepository.Add(bookmark);
_uow.Commit();
I implemented unit of work but I am not sure if I did this correctly. I use the save method followed by a commit. The save method inserts the bookmark and tags to the database and the commit method does an insert to the junction table (bookmarks have many tags and tags have many bookmarks).
So everything is inserted correctly. But if I remove the commit method the bookmark and tags still get inserted. But their is no insert to the junction table. Does this mean these inserts are not in the same transaction since commit is not necessary to save the bookmark and tags to the database? The commit is only necessary to save the relationship between tags and bookmarks.
EDIT:
Repository
public abstract class Repository<T, TEntityKey> where T : IAggregateRoot
{
private IUnitOfWork _uow;
public Repository(IUnitOfWork uow)
{
_uow = uow;
}
public void Save(T entity)
{
SessionFactory.GetCurrentSession().SaveOrUpdate(entity);
}
public void Add(T entity)
{
SessionFactory.GetCurrentSession().Save(entity);
}
public void Remove(T entity)
{
SessionFactory.GetCurrentSession().Delete(entity);
}
public IEnumerable<T> FindAll()
{
ICriteria criteriaQuery = SessionFactory.GetCurrentSession().CreateCriteria(typeof(T));
return (List<T>)criteriaQuery.List<T>();
}
}
UnitOfWork
public class NHUnitOfWork : IUnitOfWork
{
public void RegisterAmended(IAggregateRoot entity)
{
SessionFactory.GetCurrentSession().SaveOrUpdate(entity);
}
public void RegisterNew(IAggregateRoot entity)
{
SessionFactory.GetCurrentSession().Save(entity);
}
public void RegisterRemoved(IAggregateRoot entity)
{
SessionFactory.GetCurrentSession().Delete(entity);
}
public void Commit()
{
using (ITransaction transaction = SessionFactory.GetCurrentSession().BeginTransaction())
{
try
{
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
}
}
BookmarkRepository
public class BookmarkRepository : Repository<Bookmark, int>, IBookmarkRepository
{
public BookmarkRepository(IUnitOfWork uow)
: base(uow)
{
}
}
UPDATE:
I changed NHUnitOfWork to this:
public class NHUnitOfWork : IUnitOfWork
{
private ITransaction _transaction;
public void RegisterAmended(IAggregateRoot entity)
{
SessionFactory.GetCurrentSession().SaveOrUpdate(entity);
}
public void RegisterNew(IAggregateRoot entity)
{
SessionFactory.GetCurrentSession().Save(entity);
}
public void RegisterRemoved(IAggregateRoot entity)
{
SessionFactory.GetCurrentSession().Delete(entity);
}
public void BeginTransaction()
{
_transaction = SessionFactory.GetCurrentSession().BeginTransaction();
}
public void Commit()
{
using (_transaction)
{
try
{
_transaction.Commit();
}
catch (Exception ex)
{
_transaction.Rollback();
throw;
}
}
}
}
So I can use it like this:
_uow.BeginTransaction();
Bookmark bookmark = new Bookmark();
bookmark.Title = request.Title;
bookmark.Link = request.Link;
bookmark.DateCreated = request.DateCreated;
bookmark.DateModified = request.DateCreated;
bookmark.User = _userRepository.GetUserByUsername(request.Username);
IList<Tag> myTags = _tagRepository.GetTags(request.Username);
IList<string> myTagsToString = myTags.Select(x => x.Title).ToList<string>();
foreach (var tag in request.Tags)
{
if (myTagsToString.Contains(tag))
{
Tag oldTag = myTags.SingleOrDefault(x => x.Title == tag);
bookmark.Tags.Add(oldTag);
}
else
{
Tag newTag = new Tag();
newTag.Title = tag;
newTag.User = _userRepository.GetUserByUsername(request.Username);
newTag.DateCreated = request.DateCreated;
newTag.DateModified = request.DateCreated;
bookmark.Tags.Add(newTag);
}
}
_bookmarkRepository.Add(bookmark);
_uow.Commit();
I added a begin transaction to the NHUnitOfWork implementation. This means I need to call _uow.BeginTransaction() before any select or insert and in the end call _uow.Commit(). This seems to work if I look at NHibernate Profiler. If this is wrong please tell me :)
Your Save methods are not executed inside a transaction, because you close and open the transaction in the Commit method.
The correct implementation would be to start the transaction before calling Save.
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.