How do I share dbContext without CodeFirst in EntityFramework 4.1? - c#

Long time lurker, first time poster.
I've found tons of stuff on here about how to share dbContext across repositories using CodeFirst, but I can't seem to relate that to the project I'm working on, which doesn't use code first or dependency injection.
First, a little background on the project to make sure that I'm approaching this the right way. I came into this project and they were using EF4 and with a DB first. I'm far from an expert on EF, but I've fumbled around with several different projects now.
I've had to implement several different requirements that have forced me to intervene between their "service" level and the database. In other words, their objects were making calls directly to the EF db objects like
using (var db = new MYDB()){
var bar = db.Foo
.Include("Transactions")
.Include("blah")
.Where(...);
//do stuff
db.SaveChanges();
}
One thing I had to do was track all fields that changed, so I abstracted back a level and now we have
FooObject bar = GetFooObject(...);
bar.Title = "asdfasdf";
//do stuff to bar
bar.Save();
which wraps up all the fields into properties so I can log out any changes. In bar.save I open a db context, get the existing Foo or create a new one, assign all the values and then call db.SaveChanges.
As it turns out they also do lots of sub-queries based on Transactions and blah. So when they do something like
var bar = GetFooObject(...);
var t = new Transaction();
//do stuff to t
...
bar.Transactions.Add(t);
bar.Save();
I get hit with all kinds of context errors saying that the dbcontext is no longer available etc. Which I totally understand. What I don't know is how to fix it. I've seen lots of stuff about creating a dbContext before it's used and then passing it in, but I can't seem to figure out the proper way to do it so it will work with my code.
My most recent attempt based on several examples about how to convert DBContext to ObjectContext (which was in turn based on the fact that all of the examples I found about sharing a connection referenced ObjectContext and not DBContext) looks like this:
using (var db = ((IObjectContextAdapter)(new FooDB())).ObjectContext)
{
using (var context = new DbContext(db, false))
{
var bar = FooObject.GetFooObject(fooId);
Result r = bar.ProcTrans(amount,
transDate,
db.TransactionTypes
.Include(tt => tt.Description)
.SingleOrDefault(tt => tt.TypeID == transactionTypeId),
employeeId,
comment);
But with this code I get an error that I have no definition for TransactionTypes. It doesn't recognize any of my db Objects.
How can I create a DBContext and pass it to my FooObject so that I can keep it open for the related updates? I don't even know if I'm asking the question exactly right. How do I bridge this gap without recoding the whole thing?
EDIT
Here are some things I've found since opening this question. Maybe one of the two will do the trick.
Well, this finding certainly is more along the lines of recoding the whole thing but I did find this when looking for links regarding the "do change tracking" with triggers response.
poco in the entity framework part-3: change tracking with poco
And I just found this how do I share a data context across various model repositories in asp.net which might be a simple way to approach it.

I would leave behind the object context stuff.
The way I achieve a shared DBContext in my MVC app is like this:
public class BaseRepository
{
public static MyAppContext GetDataContext()
{
string ocKey = "ocm_" + HttpContext.Current.GetHashCode().ToString("x");
if (!HttpContext.Current.Items.Contains(ocKey))
HttpContext.Current.Items.Add(ocKey, new MyAppContext());
return HttpContext.Current.Items[ocKey] as MyAppContext;
}
}
Then whenever I need to do a database operation I can call:
BaseRepository.GetDataContext().YourObjects.Where(x => ...);
....
BaseRepository.GetDataContext().SaveChanges();
As long as you are still in the same HTTP context you will be sharing the same DB Context. Not entirely sure this will eliminate the errors that you are getting, but it's at least a way to share your context.

The answer, for me, was related to one of the links I posted.
how do I share a data context across various model repositories in asp.net
What threw me off when I saw these types of injection answers was that syntactically they didn't work for me. I don't have DataContext nor do I have any Repository models, but I decided to give it a try conceptually and pass the Context around everywhere.
Basically, I passed in the connection to the Object constructor or to any factory methods where a new object is created and store that in a local variable, sorta like this.
public class Foo{
private MyDB _db;
private Foo _foo;
public FooObject(MyDB dbContext)
{
_db = dbContext;
}
public static FooObject GetFooObject(int FooID, MyDB db){
bool closeFlag = false;
//if null was passed in, then we will create our own connection and manage it
if (db == null)
{
_db = new MyDB();
closeFlag = true;
} else {
//otherwise, we set our local variable
_db = db;
}
//from now on, all queries are done using the local variable
var _f = _db.Foos
.Include("x")
.Include("y")
.Include("z")
.SingleOrDefault(f => f.FooID == FooID);
var fo = FooObjectFromFoo(_f, db);
if (closeFlag)
db.Dispose();
return fo;
}
// This copies all of the values from Foo and puts the into a FooObject
public static FooObject FooObjectFromFoo(Foo f, MyDB dbContext){
if (l == null)
return null;
// note that we pass the dbContext to the constuctor
FooObject _f = new FooObject(dbContext){
_foo = f,
...
//note x, y, and z are the other EF "table references". I'm not sure what you technically call them.
x = f.x,
y = f.y,
z = f.z
};
return _f;
}
//we call this to save the changes when we're done
public bool Save(){
bool close = false;
bool retval = true;
MyDB db = _db;
//remember we set _db in the constructor
if (db == null) {
db = new MyDB();
close = true;
}
try
{
// a reference to this row should have been saved in _foo if we loaded it from the db.
// take a look at FooObjectFromFoo
if (_foo == null)
{
_foo = db.Foos.SingleOrDefault(x => x.FooID == _FooID);
}
if (_foo == null)
{
_foo = new Foo();
}
//copy all my object values back to the EF Object
_foo.blah = blah;
_foo.x = x;
_foo.y = y;
try
{
//save the new one.
db.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
TransactionResult.AddErrors(dbEx);
retval = false;
}
}
catch { throw new Exception("Something went wrong here.");}
finally { if (close) db.Dispose(); } //if we created this connection then let's close it up.
}
}
And now in my methods, I always use the local _db connection. Outside of my FooObject we have a FooService which is what is called from all of the controllers. So when FooService is instantiated I create a db connection using my class below.
If I understand it properly, this should give me a context that exists for the duration of my service request which, in my case, fairly reliably mimics the request.
namespace My.Domain
{
public class MyDataContext : IDisposable {
private MyDB _context;
private bool _ownContext;
public MyDataContext(){
_context = new MyDB();
_ownContext = true;
}
public MyDataContext(MyDB db)
{
_context = db;
_ownContext = false;
}
public MyDB Context
{
get { if (_context == null) { _context = new MyDB(); _ownContext = true; } return _context; }
set { _context = value; }
}
public bool OwnContext
{
get { return _ownContext; }
set { _ownContext = value; }
}
public void Dispose()
{
if (_context != null && _ownContext)
_context.Dispose();
}
}
}
In the FooService I do stuff like this.
private MyDb db;
public FooService (){
var _db = new MyDataContext();
db = _db.Context;
}
public Result ProcessTransaction(int FooId, string comment)
{
var foo = FooObject.GetFooObject(FooId,db);
Result r = foo.ProcessTransaction(comment);
if (r.Success)
foo.Save();
return r;
}
I think to do it "right" I should only save the changes when I close out the context... but I already had a Save method on my FooObject, so I just call db.SaveChanges in there.
I know there are lots of ways to improve this and I'm sure I'll implement some of them over time, but for now, this did the trick. This was how I got around all of the "Context is no longer available" and this object was from a different context errors.
The thing that tripped me up when looking at other peoples examples is they were all using CodeFirst and dependency injection of some sort. They commonly used Repository patterns and we don't have any of that. But it turns out that I just had to implment my own hacked-up localized version of connection injection! :)

Related

How to mock an EF6 context and/or the underlying database when the values to be tested reside in the database?

I'm trying to add unit testing to my projects that use EF6. The TDD approach works fine for simple methods that taken an input and return some output, but I'm not clear on how to make it work for methods that do read/write operations on the database. More specifically, I'm not entirely sure about how to create an in-memory representation of the database (for the test data) and then "mock" the context to point to that in-memory representation.
As an example, please consider the below class (not representative of the actual production code but demonstrates the problem) that reads a file line by line, references a bunch of tables to do some validation and then stores results to two separate database tables -
class Importer {
private repository;
private bool IsValid(string line) {
//Refer to a bunch of database tables and return true or false. Below is just a random code that demonstrates this
if(repository.Context.SomeTable1.Count(t => t.Prop1 == line[2]) > 0 &&
repository.Context.SomeTable2.First(t => t.Prop2 == line[3]).Prop3 != null &&
...
repository.Context.SomeTable10.Last(t => t.Prop5 == line[5]).Prop9 != null)
return true;
return false;
}
public Importer(IRepository repository) { this.repository = repository; }
public void Process(string fileName) {
ImportLog log = new ImportLog();
foreach(var line in GetNextLine(fileName) { //GetNextLine reads the file and yield returns lines
if(IsValid(line)) { //IsValid refers to a bunch of tables and returns true/false
log.Imported++;
FileTable fileTable = new fileTable();
fileTable.Line = line;
repository.Context.FileTables.Add(fileTable);
repository.Context.Entry(fileTable).State = EntityState.Added;
repository.Context.SaveChanges(); //Must save here, can't buffer because the file and lines are too large
}
else { log.Rejected++; }
}
repository.Context.ImportLogs.Add(log);
repository.Context.Entry(log).State = EntityState.Added;
repository.Context.SaveChanges();
}
}
Now, the only real test that verifies that the class is working is to run the Process method and then check the database to ensure that ImportLog and FileTable tables contain correct values for the given file.
This brings us to my question - How do I create an in-memory representation of the database (that contains ImportLog, FileTable, SomeTable1 to 10 tables and then point the repository.Context to that in-memory representation? Or am I going about it in a completely wrong manner?
Note: I suppose I could create mock CRUDs in the repository instead of just using the DBContext but that would be a momentous effort because the database has close to 100 tables. Just pointing the DBContext to a mocked database solves the problem most efficiently. Alternatively, I could create a real test database but I'm keeping that option only for if an in-memory database solution is not possible.
Your IRepository could be generic and not expose Context directly. By mocking both repositories, you could verify the Add method and only test what Process method is doing.
public interface IRepository<T> : where T : class
{
void Add(T item);
IQueryable<T> Query();
void SaveChanges(bool unitOfWork);
}
public class Repository<T> : IRepository<T>
{
...
public void Add(T item)
{
_dbContext.Entry(item).State = EntityState.Added;
}
public IQueryable<T> Query()
{
return _dbContext.Set<T>().AsQueryable();
}
public void SaveChanges(bool unitofWork = false)
{
if (!unitofWork)
{
_dbContext.SaveChanges();
}
}
}
and finally your Importer might look like this...
public class Importer
{
private readonly IRepository<FileTable> _fileRepository;
private readonly IRepository<ImportLog> _importRepo;
private bool IsValid(string line)
{
//Refer to a bunch of database tables and return true or false. Below is just a random code that demonstrates this
//if (_fileRepository.Query().Count(t => t.Prop1 == line[2]) > 0 &&
// _importRepo.Query().First(t => t.Prop2 == line[3]).Prop3 != null
return false;
}
public Importer(IRepository<FileTable> fileRepository, IRepository<ImportLog> importRepo, ILogParser logFile)
{
//use DI...
//var dbContext = new FusionContext();
//fileRepository = new Repository<FileTable>(dbContext);
//importRepo = new Repository<ImportLog>(dbContext);
_fileRepository = fileRepository;
_importRepo = importRepo;
}
public void Process(string fileName)
{
var log = new ImportLog();
//I would use and interface to get logfile
foreach (var line in _logParser.GetLinesFrom(fileName) { //GetNextLine reads the file and yield returns lines
if (IsValid(line))
{ //IsValid refers to a bunch of tables and returns true/false
log.Imported++;
FileTable fileTable = new FileTable();
fileTable.Line = line;
_fileRepository.Add(fileTable, true);
}
else { log.Rejected++; }
}
_importRepo.Add(log, true);
_importRepo.SaveChanges();
//because importRepo and fileRepo are using same dbContext instance, they will be saved in one transaction
}
}
(Update)
Unit test... below example uses moq framework for mocking
public void Should_Add_Logs()
{
//arrange
var fileRepoMock = new Mock<IRepository<FileTable>>();
var importer = new Importer(fileRepoMock.Object,...);
//action
importer.Process("path");
//assert
fileRepoMock.Verify(r=>r.Add(It.IsAny<FileTable>(),Times.AtMostOnce());
}
Hope this helps
For this you can implement the "Repository" pattern. At high level, for your DB related logic you can create an interface that contains methods you need to use on your DB entities. Then, you will have 2 repository classes, both implement the interface. One will work with the actual EF context and access the DB, and the other will work with in memory collections of data. You client code will be able to work with either implementation for testing or running the logic against the DB. Look here for a walk through. Also, this pattern has been recommended by the Asp.Net team.

Entity Framework + 'using' pattern best practice

I'm wondering what would be best practice in this scenario:
I have method which calls some other method in using statement (disposable Database Context), and that other method also needs to access Database Context, so this is option 1:
using (var db = new Database())
{
// some code here
//calling other function
var cnt = SomeFunction();
}
int SomeFunction()
{
using (var db = new Database())
{
// some code here for example:
return db.Users.Count();
}
}
And here is option 2:
using (var db = new Database())
{
// some code here
//calling other function
var cnt = SomeFunction(db);
}
int SomeFunction(Database db)
{
return db.Users.Count();
}
Note: Database looks smth. like this:
public class Database : IdentityDbContext<User>
{
public Database()
: base("DefaultConnection", throwIfV1Schema: false)
{
Configuration.LazyLoadingEnabled = true;
}
public DbSet<IdentityUser> AspNetUsers { get; set; }
}
I would go with option 2, because there is no need for initialization of Database Context again, but I'm not sure if this is correct way of doing this.
DbContext instances have their own changetracker. In case you want to use a changed DbSet inside SomeFunction, use the same DbContext. If not, you can choose whatever you prefer.
I would go with option 3: overload your SomeFunction.
int SomeFunction()
{
using (var db = new Database())
return SomeFunction(db);
}
int SomeFunction(Database db)
{
return db.Users.Count();
}
Let the caller decide whether to pass in an existing Database. Sometimes it makes sense, if the caller has one already, as in your example. Other times, the caller just has one database operation to perform, and then there is some benefit in simplifying the API by not bothering the caller with the responsibility of creating a Database and remembering to dispose of it properly.

Database connection errors on EF

I am very new to entity framework and I am having a problem with a web api based site (connected to mssql) that I am writing. I keep getting seemingly random errors (mostly seeming to be database related). These errors happen most often when the site is first published but they do sometimes happen when it has been hours since the last publish. A selection of the errors:
Invalid operation. The connection is closed.
There is already an open DataReader associated with this Command which must be closed first.
The connection was not closed. The connection's current state is connecting.
The context cannot be viewed while the model is being created
Underlying provider failed to open
My context looks like this:
public class Context : DbContext
{
public Context() : base("name=DefaultConnection")
{
}
public override int SaveChanges()
{
DateTime now = DateTime.Now;
foreach (ObjectStateEntry entry in (this as IObjectContextAdapter).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
IHasUpdated updated = entry.Entity as IHasUpdated;
if (updated != null)
updated.updated = now;
}
}
return base.SaveChanges();
}
public DbSet<Branch> Branches { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UsefulLink> UsefulLinks { get; set; }
}
There are many more DbSets than this. Should I be creating a separate context for each?
One of my basic controllers:
public class UsefulLinksController : ApiController
{
private Context db = new Context();
[ResponseType(typeof(UsefulLinksWrapper))]
public IHttpActionResult GetUsefulLinks([FromUri]UsefulLinkParams prams)
{
UsefulLinksWrapper wrapper = new UsefulLinksWrapper();
Meta meta = new Meta();
IQueryable<UsefulLink> query = db.UsefulLinks;
if (prams.sortBy == null)
{
prams.sortBy = "ID";
}
// Paging
query = query.OrderBy(prams.sortBy + " " + prams.sortDirection).Skip(prams.offset - 1).Take(prams.limit);
List<UsefulLink> data = query.ToList();
meta.totalCount = query.Count();
meta.offset = 1;
meta.limit = prams.limit;
wrapper.meta = meta;
wrapper.data = data;
return Ok(wrapper);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool UsefulLinkExists(int id)
{
return db.UsefulLinks.Count(e => e.ID == id) > 0;
}
}
I don't seem to see these errors when I run the site locally though there are two of us hitting it when it is published so perhaps the issue stems from multiple users?
Chris, I notice in your controller you are sharing your db context with all of the methods in your controller class.
This is generally not a best practice in Entity Framework (see: EntityFramework 4 ObjectContext Lifetime). You should keep your context alive as briefly as possible. Leaving the context alive to share across multiple methods could result in many of the errors that you list above.
I would recommend trying to instantiate a new instance of the context, instead, wherever it is used and quickly disposing of it.
This should generally result in more stable behavior.
So the below:
class SomeClass
{
private context = new Context(); //sharing your context with all methods
public someMethod()
{
context.doSomething;
}
public someMethod2()
{
context.doSomething;
}
}
should become:
class SomeClass
{
public someMethod()
{
Context context = new Context(); //now your context is declared and disposed of within each method
context.doSomething;
}
public someMethod2()
{
Context context = new Context(); //now your context is declared and disposed of within each method
context.doSomething;
}
}
Or even better, you can use a using construct to ensure that your context is properly disposed of:
class SomeClass
{
public someMethod3()
{
using(Context context = new Context()) //now wrapping the context in a using to ensure it is disposed
{
context.doSomething;
}
}
}
I would recommend trying the above changes and seeing if your behavior becomes more stable.
Since I do not know how your page uses the methods UsefulLinksController and in which order, I would say UsefulLinkExists is perhaps the culprit due to lazy loading
Lazy loading means delaying the loading of related data until you
specifically request it
Which would explain why your "reader" remains "open".
Try:
return db.UsefulLinks.ToList().Count(e => e.ID == id) > 0;
In any case, you can disable lazy loading by default in the context constructor as such noted here:
public MyEntitiesContext() : base("name=MyEntitiesContext", "MyEntitiesContext")
{
this.ContextOptions.LazyLoadingEnabled = false;
OnContextCreated();
}
As far as I know, it applies to EF4 and up.

ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value

In a nutshell the exception is thrown during POSTing wrapper model and changing the state of one entry to 'Modified'. Before changing the state, the state is set to 'Detached' but calling Attach() does throw the same error. I'm using EF6.
Please find my code below(model names have been changed to make it easier to read)
Model
// Wrapper classes
public class AViewModel
{
public A a { get; set; }
public List<B> b { get; set; }
public C c { get; set; }
}
Controller
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (!canUserAccessA(id.Value))
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
var aViewModel = new AViewModel();
aViewModel.A = db.As.Find(id);
if (aViewModel.Receipt == null)
{
return HttpNotFound();
}
aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList();
aViewModel.Vendor = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault();
return View(aViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(AViewModel aViewModel)
{
if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
if (ModelState.IsValid)
{
db.Entry(aViewModel.a).State = EntityState.Modified; //THIS IS WHERE THE ERROR IS BEING THROWN
db.SaveChanges();
return RedirectToAction("Index");
}
return View(aViewModel);
}
As shown above line
db.Entry(aViewModel.a).State = EntityState.Modified;
throws exception:
Attaching an entity of type 'A' failed because another entity of the
same type already has the same primary key value. This can happen when
using the 'Attach' method or setting the state of an entity to
'Unchanged' or 'Modified' if any entities in the graph have
conflicting key values. This may be because some entities are new and
have not yet received database-generated key values. In this case use
the 'Add' method or the 'Added' entity state to track the graph and
then set the state of non-new entities to 'Unchanged' or 'Modified' as
appropriate.
Does anybody see anything wrong in my code or understand in what circumstances it would throw such error during editing a model?
Problem SOLVED!
Attach method could potentially help somebody but it wouldn't help in this situation as the document was already being tracked while being loaded in Edit GET controller function. Attach would throw exactly the same error.
The issue I encounter here was caused by function canUserAccessA() which loads the A entity before updating the state of object a. This was screwing up the tracked entity and it was changing state of a object to Detached.
The solution was to amend canUserAccessA() so that the object I was loading wouldn't be tracked. Function AsNoTracking() should be called while querying the context.
// User -> Receipt validation
private bool canUserAccessA(int aID)
{
int userID = WebSecurity.GetUserId(User.Identity.Name);
int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
return (aFound > 0); //if aFound > 0, then return true, else return false.
}
For some reason I couldnt use .Find(aID) with AsNoTracking() but it doesn't really matter as I could achieve the same by changing the query.
Hope this will help anybody with similar problem!
Interestingly:
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
Or if you still is not generic:
_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);
seems to solved my problem smoothly.
It seems that entity you are trying to modify is not being tracked correctly and therefore is not recognized as edited, but added instead.
Instead of directly setting state, try to do the following:
//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a);
db.SaveChanges();
Also, I would like to warn you that your code contains potential security vulnerability. If you are using entity directly in your view model, then you risk that somebody could modify contents of entity by adding correctly named fields in submitted form. For example, if user added input box with name "A.FirstName" and the entity contained such field, then the value would be bound to viewmodel and saved to database even if the user would not be allowed to change that in normal operation of application.
Update:
To get over security vulnerability mentioned previously, you should never expose your domain model as your viewmodel but use separate viewmodel instead. Then your action would receive viewmodel which you could map back to domain model using some mapping tool like AutoMapper. This would keep you safe from user modifying sensitive data.
Here is extended explanation:
http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/
Try this:
var local = yourDbContext.Set<YourModel>()
.Local
.FirstOrDefault(f => f.Id == yourModel.Id);
if (local != null)
{
yourDbContext.Entry(local).State = EntityState.Detached;
}
yourDbContext.Entry(applicationModel).State = EntityState.Modified;
for me the local copy was the source of the problem.
this solved it
var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
if (local != null)
{
context.Entry(local).State = EntityState.Detached;
}
My case was that I did not have direct access to EF context from my MVC app.
So if you are using some kind of repository for entity persistence it could be appropiate to simply detach explicitly loaded entity and then set binded EntityState to Modified.
Sample (abstract) code:
MVC
public ActionResult(A a)
{
A aa = repo.Find(...);
// some logic
repo.Detach(aa);
repo.Update(a);
}
Repository
void Update(A a)
{
context.Entry(a).EntityState = EntityState.Modified;
context.SaveChanges();
}
void Detach(A a)
{
context.Entry(a).EntityState = EntityState.Detached;
}
Use AsNoTracking() where you are getting your query.
var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
I have added this answer only because the problem is explained based on more complex data pattern and I found it hard to understand here.
I created a fairly simple application. This error occurred inside Edit POST action. The action accepted ViewModel as an input parameter. The reason for using the ViewModel was to make some calculation before the record was saved.
Once the action passed through validation such as if(ModelState.IsValid), my wrongdoing was to project values from ViewModel into a completely new instance of Entity. I thought I'd have to create a new instance to store updated data and then saved such instance.
What I had realised later was that I had to read the record from database:
Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);
and updated this object. Everything works now.
I thought I'd share my experience on this one, even though I feel a bit silly for not realising sooner.
I am using the repository pattern with the repo instances injected into my controllers. The concrete repositories instantiate my ModelContext (DbContext) which lasts the lifetime of the repository, which is IDisposable and disposed by the controller.
The issue for me was that I have a modified stamp and row version on my entities, so I was getting them first in order to compare with the inbound headers. Of course, this loaded and tracked the entity that was subsequently being updated.
The fix was simply to change the repository from newing-up a context once in the constructor to having the following methods:
private DbContext GetDbContext()
{
return this.GetDbContext(false);
}
protected virtual DbContext GetDbContext(bool canUseCachedContext)
{
if (_dbContext != null)
{
if (canUseCachedContext)
{
return _dbContext;
}
else
{
_dbContext.Dispose();
}
}
_dbContext = new ModelContext();
return _dbContext;
}
#region IDisposable Members
public void Dispose()
{
this.Dispose(true);
}
protected virtual void Dispose(bool isDisposing)
{
if (!_isDisposed)
{
if (isDisposing)
{
// Clear down managed resources.
if (_dbContext != null)
_dbContext.Dispose();
}
_isDisposed = true;
}
}
#endregion
This allows the repository methods to re-new their context instance upon each use by calling GetDbContext, or use a previous instance if they so desire by specifying true.
I had this problem with local var and i just detach it like this:
if (ModelState.IsValid)
{
var old = db.Channel.Find(channel.Id);
if (Request.Files.Count > 0)
{
HttpPostedFileBase objFiles = Request.Files[0];
using (var binaryReader = new BinaryReader(objFiles.InputStream))
{
channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength);
}
}
else
channel.GateImage = old.GateImage;
var cat = db.Category.Find(CatID);
if (cat != null)
channel.Category = cat;
db.Entry(old).State = EntityState.Detached; // just added this line
db.Entry(channel).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(channel);
Problem causes of loaded objects with same Key, so first we will detach that object and do the the updating to avoid conflict between two object with the same Key
i mange to fix the issue by updating state. when you trigger find or any other query operation on the same record sate has been updated with modified so we need to set status to Detached then you can fire your update change
ActivityEntity activity = new ActivityEntity();
activity.name="vv";
activity.ID = 22 ; //sample id
var savedActivity = context.Activities.Find(22);
if (savedActivity!=null)
{
context.Entry(savedActivity).State = EntityState.Detached;
context.SaveChanges();
activity.age= savedActivity.age;
activity.marks= savedActivity.marks;
context.Entry(activity).State = EntityState.Modified;
context.SaveChanges();
return activity.ID;
}
I had a similar issue, after probing for 2-3 days found ".AsNoTracking" should be removed as EF doesn't track the changes and assumes there are no changes unless an object is attached. Also if we don't use .AsNoTracking, EF automatically knows which object to save/update so there is no need to use Attach/Added.
I encountered this error where
two methods, A & B, in a single controller both used the same instance of an ApplicationDbContext, and
method A called method B
private ApplicationDbContext db;
// api methods
public JsonResult methodA(string id){
Resource resource = db.Resources.Find(id);
db.Entry(resource).State = EntityState.Modified;
db.SaveChanges();
return methodB()
}
public JsonResult methodB(string id){
Resource resource = db.Resources.Find(id);
db.Entry(resource).State = EntityState.Modified;
db.SaveChanges();
return new JsonResult();
}
I changed method B to have a using statement and rely only on the local db2.
After:
private ApplicationDbContext db;
// api methods
public JsonResult methodA(string id){
Resource resource = db.Resources.Find(id);
db.Entry(resource).State = EntityState.Modified;
db.SaveChanges();
return methodB()
}
public JsonResult methodB(string id){
using (var db2 = new ApplicationDbContext())
{
Resource resource = db2.Resources.Find(id);
db2.Entry(resource).State = EntityState.Modified;
db2.SaveChanges();
}
return new JsonResult();
}
Similar to what Luke Puplett is saying, the problem can be caused by not properly disposing or creating your context.
In my case, I had a class which accepted a context called ContextService:
public class ContextService : IDisposable
{
private Context _context;
public void Dispose()
{
_context.Dispose();
}
public ContextService(Context context)
{
_context = context;
}
//... do stuff with the context
My context service had a function which updates an entity using an instantiated entity object:
public void UpdateEntity(MyEntity myEntity, ICollection<int> ids)
{
var item = _context.Entry(myEntity);
item.State = EntityState.Modified;
item.Collection(x => x.RelatedEntities).Load();
myEntity.RelatedEntities.Clear();
foreach (var id in ids)
{
myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id));
}
_context.SaveChanges();
}
All of this was fine, my controller where I initialized the service was the problem. My controller originally looked like this:
private static NotificationService _service =
new NotificationService(new NotificationContext());
public void Dispose()
{
}
I changed it to this and the error went away:
private static NotificationService _service;
public TemplateController()
{
_service = new NotificationService(new NotificationContext());
}
public void Dispose()
{
_service.Dispose();
}
Here what I did in the similar case.
That sitatuation means that same entity has already been existed in the context.So following can help
First check from ChangeTracker if the entity is in the context
var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList();
var isAlreadyTracked =
trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id);
If it exists
if (isAlreadyTracked)
{
myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity;
}
else
{
//Attach or Modify depending on your needs
}
I solve this problem with a "using" block
using (SqlConnection conn = new SqlConnection(connectionString))
{
// stuff to do with data base
}
// or if you are using entity framework
using (DataBaseEntity data = new DataBaseEntity)
{
}
Here is where I get the idea https://social.msdn.microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework?forum=vcses is in spanish (look for the second answer)
you can use added method like;
_dbContext.Entry(modelclassname).State = EntityState.Added;
but in many case if you want to use more than one model at that time this won't work because entity is already attached to another entity. So, at that time you can use ADDOrUpdate Entity Migration method which simply migrates object from one to another and as a result you wouldn't get any error.
_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);
Clear all State
dbContextGlobalERP.ChangeTracker.Entries().Where(e => e.Entity != null).ToList().ForEach(e => e.State = EntityState.Detached);
Reasons I've encountered this error:
Did not use .AsNoTracking() when querying for existing entities. Especially when calling a helper function to check permissions.
Calling .Include() on a query and then trying to edit the parent. Example: var ent = repo.Query<Ent>().Include(e=>e.Ent2).First(); ...repo.Edit(e.Ent2); repo.Edit(e); If I'm going to edit a nested object, I try to separate these into separate query calls now. If you can't do that, set the child object to null and iterate through lists, detaching objects like this
Editing an old entity in a Put web call. The new item is already added to the repo, so modify that one and have it be saved in super.Put(). Example of what will throw an error: public void Put(key, newItem){ var old = repo.Query<Entity>().Where(e=>Id==key).First(); ... repo.Edit(old); super.Put(key,newItem); ... }
Multiple helper functions edit the same entity. Instead of passing the ID as a parameter into each function, pass a reference to the entity. Error solved!
In my case , I had wrote really two times an entity of same type . So I delete it and all things work correctly
This problem may also be seen during ViewModel to EntityModel mapping (by using AutoMapper, etc.) and trying to include context.Entry().State and context.SaveChanges() such a using block as shown below would solve the problem. Please keep in mind that context.SaveChanges() method is used two times instead of using just after if-block as it must be in using block also.
public void Save(YourEntity entity)
{
if (entity.Id == 0)
{
context.YourEntity.Add(entity);
context.SaveChanges();
}
else
{
using (var context = new YourDbContext())
{
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges(); //Must be in using block
}
}
}

Entity Framework Code First Repository really generic and problems with two context

sorry my bad English.
In company where I work we are migrating for Entity Framework Code First.
But problems happen when i create two instance of repository
RepositoryGeneric<Anuncio> repAnuncio1 = new RepositoryGeneric<Anuncio>();
Anuncio anuncio1 = repAnuncio1.find(1)
Anuncio anuncio2 = new Anuncio (1,20,"any thing");
RepositoryGeneric<Anuncio> repAnuncio2 = new RepositoryGeneric<Anuncio>();
repAnuncio2.salvar(anuncio2); //ok work
repAnuncio2.salvar(anuncio1); // error the anuncio1 is atach in repAnuncio1
it is only exemple, but in real application i need several repository interacting.
else, we application use DataContext:
public static void Save(Entity entity)
{
if (entity != null)
{
SqlServer sql = new SqlServer();
Type tipoEntidade = entity.GetType();
PropertyInfo[] propriedades = tipoEntidade.GetProperties();
foreach (PropertyInfo propriedade in propriedades)
{
if (propriedade.PropertyType.IsPublic && propriedade.CanWrite && propriedade.PropertyType.Namespace == "System")
{
object valor = propriedade.GetValue(entidade, null);
if (valor != null && propriedade.Name != "ID" && propriedade.Name != "Excluido")
sql.AdicionarParametro("#" + propriedade.Name, valor);
}
}
but in Entity Framework i could not implement.
else:
public int Save(Anuncio anuncio)
{
if (anuncio.Id != 0)
// ctx.Anuncio.Attach(anuncio);
//attach Error when othe repository get
//else i need...
{
BancoContext ctx = new BancoContext (); //my Dbcontext
AnuncioAxiliar = ctx.Anuncio.FirstOrDefault(x => x.Id == entidade.Id); //Entity help
AnuncioAxiliar.Nome = anuncio.Nome;
AnuncioAxiliar.Cliente= anuncio.Cliente;
AnuncioAxiliar.Contrato= anuncio.Contrato;
ctx.SaveChanges(); //vai salvar o AnuncioAxiliar
}
else
ctx.Set(entidade.GetType()).Add(entidade);
return ctx.SaveChanges();
}
but so i need one Save method for each classe Poco
Any Idea?
That is common problem. You cannot pass entity loaded from one context to another context. You should share context among your repositories when working in one unit of work (logical transaction). It is also enough to use one repository instance per type in the unit of work.
If you want to pass entity from one context to other context you must first detach it from the first context by calling:
ctx.Entry(entity).State = EntityState.Detached;
Be aware that if entity has any loaded relations these relations will be lost / broken when you detach it from the context.
You need to share the EF db context between your repositories. When you retrieve data from the database via EF, the object that gets returned by EF is attached to a specific instance of a db context for change tracking. If you attempt to perform any operations on that entity in another database context, EF will throw an exception as the database context can see it's already attached to another context.
Note that if this is a web application, your db context should only be shared between repositories in the same web request only. If they are shared between all repositories for all web requests, conflicts will occur.
change the constructor of your RepositoryGeneric to accept your data context as a parameter. Then use the same context for all the repositories. You should probably use dependency injection to achieve this.
Something like below
public class RepositoryGeneric<TEntity>
{
public RepositoryGeneric(DomainContext domainContext)
{
DomainContext = domainContext;
}
protected DomainContext DomainContext { get; private set; }
protected virtual IDbSet<TEntity> DbSet
{
get { return DomainContext.Set<TEntity>(); }
}
public virtual TEntity GetByKey(params object[] keys)
{
return DbSet.Find(keys);
}
}

Categories