asp.net mvc - An entity object cannot be referenced by multiple instances of IEntityChangeTracker when saving changes -
i have 2 domain objects, both identical different pk properties:
public partial class maintenance : maintenancebase { [key] [databasegeneratedattribute(databasegeneratedoption.identity)] public int maintenanceid { get; set; } public virtual employee employee { get; set; } } public partial class mymaintenance : maintenancebase { [key] [databasegeneratedattribute(databasegeneratedoption.identity)] public int rowid { get; set; } public virtual employee employee { get; set; } }
the rest of properties inherited base class. problem having when try call save changes in post controller, getting following error:
an entity object cannot referenced multiple instances of ientitychangetracker
this (basically) controller method:
[httppost] public actionresult submitmymaintenance(ilist<mymaintenance> mymaintenancelist, string username) { foreach (var result in mymaintenancelist) { var m = imymaintenancerepository.getsingle(result.rowid); maintenance maintenance = new maintenance(); // use injector handle mapping between viewmodel , model maintenance.injectfrom(m); try { if (modelstate.isvalid) { // save maintenance item imaintenancerepository.add(maintenance); imaintenancerepository.save(); // delete item in mymaintenance imymaintenancerepository.delete(m); imymaintenancerepository.save(); } } catch (dataexception ex) { message = ex.innerexception.tostring(); } } // refresh view var mvm = new mymaintenancelistviewmodel { mymaintenancelist = imymaintenancerepository.findby(v => v.createdby.equals(username)).tolist(), message = "your maintenance items added." }; return view("mymaintenance", mvm); }
i suspect because have instances of respositories (imaintenancerepository
& imymaintenancerepository
) both domain objects in same controller post method, , both have reference employee entity.
for instance, when dispose imymaintenancerepository , create new instance (before refreshing view @ end), en error inserting null value in employee table, not inserting anything. reason suspect employee entity exists in 2 different data contexts. not sure how resolve though. none of solutions have found seem apply , thinking more of implementation problem on part.
edit: repositories
namespace emms.models.interfaces { public interface imymaintenancerepository : igenericrepository<mymaintenance> { mymaintenance getsingle(int rowid); } } namespace emms.models.repositories { public class mymaintenancerepository : genericrepository<appdbcontext, mymaintenance>, imymaintenancerepository { public mymaintenance getsingle(int rowid) { var query = getall().firstordefault(x => x.rowid == rowid); return query; } } } namespace emms.viewmodels.repositories { public class genericrepository<c, t> : idisposable, igenericrepository<t> t : class c : dbcontext, new() { private c _entities = new c(); public c context { { return _entities; } set { _entities = value; } } public virtual iqueryable<t> getall() { iqueryable<t> query = _entities.set<t>(); return query; } public iqueryable<t> findby(system.linq.expressions.expression<func<t, bool>> predicate) { iqueryable<t> query = _entities.set<t>().where(predicate); return query; } // enforce referential itegrity public bool valueinuse(system.linq.expressions.expression<func<t, bool>> predicate) { iqueryable<t> query = _entities.set<t>().where(predicate); int count = query.count(); return count > 0 ? true : false; } public virtual void add(t entity) { _entities.set<t>().add(entity); } public virtual void delete(t entity) { _entities.entry(entity).state = system.data.entitystate.deleted; } public virtual void edit(t entity) { _entities.entry(entity).state = system.data.entitystate.modified; } public virtual void save() { _entities.savechanges(); } private bool disposed = false; // detect redundant calls protected virtual void dispose(bool disposing) { if (!disposed) { if (disposing) { if (_entities != null) { _entities.dispose(); } } disposed = true; } } public void dispose() { dispose(true); gc.suppressfinalize(this); } } } namespace emms.viewmodels.interfaces { public interface igenericrepository<t> t : class { iqueryable<t> getall(); iqueryable<t> findby(expression<func<t, bool>> predicate); bool valueinuse(system.linq.expressions.expression<func<t, bool>> predicate); void add(t entity); void delete(t entity); void edit(t entity); void save(); void dispose(); } }
you're absolute correct issue. actually, in particular, it's because each repository has it's own instance of context object, , you're trying pass employee
retrieved via 1 instance , saving via different instance.
the easiest solution track things in 1 repository. in other words, use 1 maintenancerepository
can have calls return both maintenance
, mymaintenance
. though stretches idea of "repository" bit. why repositories typically combined unit of work class, house context repositories share. however, @ point, you're recreating structure entity framework implements. so, holding in 1 "repository" makes more sense, you're talking "service" pattern rather repository pattern. it's semantics though.
update
disclaimer: i'm using in project , works me. may not best practice , reasonable people disagree approach.
iservice
interface
public interface iservice<tcontext, tentity> tcontext : dbcontext tentity : class { ienumerable<tentity> getall( func<iqueryable<tentity>, iorderedqueryable<tentity>> orderby = null, string includeproperties = ""); ienumerable<tentity> get( expression<func<tentity, bool>> filter = null, func<iqueryable<tentity>, iorderedqueryable<tentity>> orderby = null, string includeproperties = ""); tentity getbyid(int id, string includeproperties = ""); tentity getone( expression<func<tentity, bool>> filter = null, string includeproperties = ""); tentity getfirst( expression<func<tentity, bool>> filter = null, func<iqueryable<tentity>, iorderedqueryable<tentity>> orderby = null, string includeproperties = ""); tentity getlast( expression<func<tentity, bool>> filter = null, func<iqueryable<tentity>, iorderedqueryable<tentity>> orderby = null, string includeproperties = ""); void create(tentity entity); void update(tentity entity); void delete(int id); void delete(tentity entity); int count(expression<func<tentity, bool>> filter = null); bool any(expression<func<tentity, bool>> filter = null); }
service
, implementation of iservice
public class service<tcontext, tentity> : iservice<tcontext, tentity> tcontext : dbcontext tentity : class { internal tcontext context; internal dbset<tentity> dbset; public service(tcontext context) { this.context = context; this.dbset = context.set<tentity>(); } public virtual ienumerable<tentity> getall( func<iqueryable<tentity>, iorderedqueryable<tentity>> orderby = null, string includeproperties = "") { return get(null, orderby, includeproperties); } public virtual ienumerable<tentity> get( expression<func<tentity, bool>> filter = null, func<iqueryable<tentity>, iorderedqueryable<tentity>> orderby = null, string includeproperties = "") { iqueryable<tentity> query = dbset; 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.distinct().tolist(); } } public virtual tentity getbyid(int id, string includeproperties = "") { return dbset.find(id); } public virtual tentity getone( expression<func<tentity, bool>> filter, string includeproperties = "") { return get(filter, null, includeproperties).singleordefault(); } public virtual tentity getfirst( expression<func<tentity, bool>> filter = null, func<iqueryable<tentity>, iorderedqueryable<tentity>> orderby = null, string includeproperties = "") { return get(filter, orderby, includeproperties).firstordefault(); } public virtual tentity getlast( expression<func<tentity, bool>> filter = null, func<iqueryable<tentity>, iorderedqueryable<tentity>> orderby = null, string includeproperties = "") { return get(filter, orderby, includeproperties).lastordefault(); } public virtual void create(tentity entity) { dbset.add(entity); } public virtual void delete(int id) { var entity = getbyid(id); delete(entity); } public virtual void delete(tentity entity) { if (context.entry(entity).state == entitystate.detached) { dbset.attach(entity); } dbset.remove(entity); } public virtual void update(tentity entity) { if (context.entry(entity).state == entitystate.detached) { dbset.attach(entity); } context.entry(entity).state = entitystate.modified; } public virtual int count(expression<func<tentity, bool>> filter = null) { return get(filter).count(); } public virtual bool any(expression<func<tentity, bool>> filter = null) { return count(filter) > 0; } }
servicegroup
, abstract container services
public abstract class servicegroup<tcontext> : idisposable tcontext : dbcontext { protected tcontext context; public virtual void save() { try { context.savechanges(); } catch (dbentityvalidationexception validationexception) { string validationerrormessage = dbentityvalidationmessageparser.geterrormessage(validationexception); console.writeline(validationerrormessage); } } #region disposable private bool disposed = false; protected virtual void dispose(bool disposing) { if (!this.disposed) { if (disposing) { context.dispose(); } } this.disposed = true; } public virtual void dispose() { dispose(true); gc.suppressfinalize(this); } #endregion }
so, way use whenever want create collection of things work with, subclass servicegroup
so:
public class sampleservice : servicegroup<mydbcontext> { public sampleservice() { this.context = new mydbcontext(); } private service<mydbcontext, somemodel> somemodels; public service<mydbcontext, somemodel> somemodels { { if (somemodels == null) { somemodels = new service<mydbcontext, somemodel>(context); } return somemodels; } } private service<mydbcontext, anothermodel> anothermodels; public service<mydbcontext, anothermodel> anothermodels { { if (anothermodels == null) { anothermodels = new service<mydbcontext, anothermodel>(context); } return anothermodels; } } // rinse , repeat }
this makes sure using same context instance. use it, do:
var service = new sampleservice(); somemodels = service.somemodels.getall();
Comments
Post a Comment