I have the following code that does a great job at saving a detached object in a .Net 4 / EF 4 project. I wanted to use that code in a new .Net 4.5 / EF 5 project. I copied it in and now it gives me a compile error of:
"MyEntities does not contain a definition for 'TryGetObjectByKey' and no extension method 'TryGetObjectByKey' accepting a first argument of type MyEntities could be found (are you missing a using directive or an assembly reference?)"
Allegedly it is in the System.Data.Objects namespace (so I have a using for it) from the System.Data.Entity assembly (.dll) which is referenced.
public bool UpdateChanged(IEntityWithKey DetachedObject = null) {
bool Result = false;
try {
using (MyEntities db = new MyEntities()) {
if (DetachedObject != null) {
object Original = null;
if (db.TryGetObjectByKey(DetachedObject.EntityKey, out Original))
db.ApplyCurrentValues(DetachedObject.EntityKey.EntitySetName, DetachedObject);
} // if they want to Update an Entity
db.SaveChanges();
Result = true;
} // using the database
} catch (Exception e) {
} // try-catch
return Result;
} // UpdateChanged - Method
According to this link it should work: http://msdn.microsoft.com/en-us/library/bb738728.aspx
Can you please help?
UPDATE / SOLUTION:
Based on #Rowan's answer below I have simply modified my Save method to something like the following instead of creating an UpdateChanged method that takes Detached objects:
using (MyEntities db = new MyEntities()) {
if (o.ID > 0) {
// Existing Owner
db.Owners.Attach(o);
db.Entry(o).State = EntityState.Modified;
db.Entry(o.Address).State = EntityState.Modified;
} else {
// New Owner
db.Owners.Add(o);
} // if this is a New Owner
db.SaveChanges();
} // using the database
TryGetObjectByKey is a method on ObjectContext. Starting in Visual Studio 2012 new models will generate a DbContext based context by default (DbContext is designed to be a simpler and more intuitive API surface). Existing models will keep generating ObjectContext unless you choose to swap to DbContext. You can also revert back to ObjectContext for new models.
You can always get at the underlying ObjectContext using ((IObjectContextAdapter)db).ObjectContext.TryGetObjectByKey(...).
Related
This question already has answers here:
Solving "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection" InvalidOperationException
(8 answers)
Closed 3 years ago.
How can i get a list with all the data and reference data from database from a static function? I try to use db.Configuration.LazyLoadingEnabled = true; but the function return only the project info and the reference data was null.
public static Project GetProject(Guid ProjectID)
{
if (!ProjectID.Equals(null))
{
using (var db = new dbEntity())
{
return db.Projects.Single(Project => Project.Id.Equals(ProjectID));
}
}
return null;
}
Error:
The ObjectContext instance has been disposed and can no longer be used
for operations that require a connection.
Where i call the function:
#{var project = StaticMethods.GetProject(Guid.Parse(ViewContext.RouteData.Values["id"].ToString()));}
#for(var item in project.Users().ToList()){
....
}
You can use Linq eager loading and express explicitly what do you want to include to resulting objectgraph. Eager loading is achieved with .Include()-method.
You did not provide model classes of your entities, but I assume that relationship is one-one (Project has Reference as property). Following snippet loads Project with it's Reference-property:
public static Project GetProject(Guid ProjectID)
{
if (!ProjectID.Equals(null))
{
using (var db = new dbEntity())
{
return db.Projects.Include(r => r.Reference).Single(Project => Project.Id.Equals(ProjectID));
}
}
return null;
}
In short, lazy loading is the problem.
It's caused by the following:
You open the DbContext
Perform your query
Return your Project
Access a virtual property which is trying to lazily load a related resources after the DbContext is disposed.
You will have to do one of the following:
Turn lazy loading off.
Include / Eager load any related resources Project uses.
I have a Project entity and a Rule entity. A project can have many rules.
Edit 1: Here is the relation we have defined. Sorry for not using plural in the navigation property, we haven't updated it yet.
So I have this method:
public bool Update(Project project)
{
Logger.Log(GetType(), string.Format("Updating project {0}", project));
using (var ctx = new EntityModelContainer())
{
if (project.Id == 0)
{
return false;
}
try
{
ctx.ProjectSet.Attach(project);
ctx.Entry(project).State = EntityState.Modified;
return ctx.SaveChanges() >= 1;
}
catch (Exception exc)
{
throw new ComplianceException(exc.Message, exc);
}
}
}
It will update all scalar attributes of a project. But if I add or delete a rule to a project and run Update(project), it is not considered. Why?
Edit 2: I have also tried to re-read the project and assign the updated values. Please notice that projectRead.Rule is a list.
public bool Update(Project projectGiven)
{
// Add all changes here
Project projectRead = this.ReadProject(projectGiven.Id);
if (projectRead == null)
{
Logger.Log(GetType(), "Project not found");
return false;
}
projectRead.ProjectName = projectRead.ProjectName;
projectRead.Report = projectRead.Report;
projectRead.Rule = projectRead.Rule;
Logger.Log(GetType(), string.Format("Updating project {0}", projectRead));
using (var ctx = new EntityModelContainer())
{
...
Thank you very much!
Seems to me that with latest version of entity framework, and entity is not added automatically to the change tracker if assigned to and entity that is already in the change tracker.
Obviously this depends on the way role and project are related.
I suppose the more simple way si to create a procedure that reload the project including all the roles, delete the selected one from the list, and after to save it, at this point I think al the updates are done correrctly.
I solved the problem. I need to reload the project within the same context before I update.
Error message: Attaching an entity of type failed because another entity of the same type already has the same primary key value.
Question: How do I attached an entity in a similar fashion as demonstrated in the AttachActivity method in the code below?
I have to assume the "another entity" part of the error message above refers to an object that exists in memory but is out of scope (??). I note this because the Local property of the DBSet for the entity type I am trying to attach returns zero.
I am reasonably confident the entities do not exist in the context because I step through the code and watch the context as it is created. The entities are added in the few lines immediately following creation of the dbcontext.
Am testing for attached entities as specified here:what is the most reasonable way to find out if entity is attached to dbContext or not?
When looking at locals in the locals window of visual studio I see no entities of type Activity (regardless of ID) except the one I am trying to attach.
The code executes in this order: Try -> ModifyProject -> AttachActivity
Code fails in the AttachActivity at the commented line.
Note the code between the debug comments which will throw if any entities have been added to the context.
private string AttachActivity(Activity activity)
{
string errorMsg = ValidateActivity(activity); // has no code yet. No. It does not query db.
if(String.IsNullOrEmpty(errorMsg))
{
// debug
var state = db.Entry(activity).State; // Detached
int activityCount = db.Activities.Local.Count;
int projectCount = db.Activities.Local.Count;
if (activityCount > 0 || projectCount > 0)
throw new Exception("objects exist in dbcontext");
// end debug
if (activity.ID == 0)
db.Activities.Add(activity);
else
{
db.Activities.Attach(activity); // throws here
db.Entry(activity).State = System.Data.Entity.EntityState.Modified;
}
}
return errorMsg;
}
public int ModifyProject(Presentation.PresProject presProject, out int id, out string errorMsg)
{
// snip
foreach (PresActivity presActivity in presProject.Activities)
{
Activity a = presActivity.ToActivity(); // returns new Activity object
errorMsg = ValidateActivity(a); // has no code yet. No. It does not query db.
if (String.IsNullOrEmpty(errorMsg))
{
a.Project = project;
project.Activities.Add(a);
AttachActivity(a);
}
else
break;
}
if (string.IsNullOrEmpty(errorMsg))
{
if (project.ID == 0)
db.Projects.Add(project);
else
db.AttachAsModfied(project);
saveCount = db.SaveChanges();
id = project.ID;
}
return saveCount;
}
This is the class that news up the dbContext:
public void Try(Action<IServices> work)
{
using(IServices client = GetClient()) // dbContext is newd up here
{
try
{
work(client); // ModifyProject is called here
HangUp(client, false);
}
catch (CommunicationException e)
{
HangUp(client, true);
}
catch (TimeoutException e)
{
HangUp(client, true);
}
catch (Exception e)
{
HangUp(client, true);
throw;
}
}
I am not asking: How do I use AsNoTracking What difference does .AsNoTracking() make?
One solution to avoid receiving this error is using Find method. before attaching entity, query DbContext for desired entity, if entity exists in memory you get local entity otherwise entity will be retrieved from database.
private void AttachActivity(Activity activity)
{
var activityInDb = db.Activities.Find(activity.Id);
// Activity does not exist in database and it's new one
if(activityInDb == null)
{
db.Activities.Add(activity);
return;
}
// Activity already exist in database and modify it
db.Entry(activityInDb).CurrentValues.SetValues(activity);
db.Entry(activityInDb ).State = EntityState.Modified;
}
Attaching an entity of type 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.
The solution is that
if you had to use GetAll()
public virtual IEnumerable<T> GetAll()
{
return dbSet.ToList();
}
Change To
public virtual IEnumerable<T> GetAll()
{
return dbSet.AsNoTracking().ToList();
}
I resolved this error by changing Update method like below.
if you are using generic repository and Entity
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
or normal(non-generic) repository and entity , then
_dbContext.Set<TaskEntity>().AddOrUpdate(entityToBeUpdatedWithId);
If you use AddOrUpdate() method, please make sure you have added
System.Data.Entity.Migrations namespace.
Which is the Best Practise in Declaring Entity FrameWork Contexts
function()
{
DBContext context = new DBContext();
//Entity code
return ;
}
or
function()
{
using(DBContext context = new DBContext())
{
//Entity code
}
}
Do we need to use using in EntityFrameWork ? If yes my 2nd question
In DataAccess Layer am executing EF and storing the result in IEnumerable inside using
MY DL
function()
{
IEnumerable something = null;
using(DBContext context = new DBContext())
{
IEnumerable something = ....
}
return something;
}
In Controller
function()
{
List some = something.ToList();
}
And in my controller am getting this as a list as i need to do some Find operation am getting
"The operation cannot be completed because the DbContext has been disposed Entity Framework"
Yes i can return a list from DL and it works fine
How do i handle this if i use using with IEnumerable?
You can avoid the lazy-loading EF behaviour by calling .ToList() on the IEnumerable before the context is disposed (i.e. within your using block)
Yes, a using is the best practice because it cleans up your context. The Using statement is a shortcut for:
try {
// Execute your code inside the using statement
}
finally {
// Cleanup the context no matter what by calling .Dispose()
}
Keep in mind, your context likely returns IEnumerables and since EF supports lazy loading these objects won't be populated until you fetch them to a concrete collection (ie. yourResult.ToList()).
A common negative outcome occurs in this scenario:
public IEnumerable<Employee> GetEmployeesInAccounting()
{
using(var myContext = new MyDbContext())
{
return myContext.Employees.Where(emp => emp.Department == 'Accounting');
}
}
// Code that fails, Assuming Manager is a lazy loaded entity, this results in an exception but it compiles no problem
var acctEmps = GetEmployeesInAccounting();
var something = acctEmps.First().Department.Manager.Department;
You can avoid this by using the .Include(emp => emp.Manager) (linq extension method) and binding your result using .ToList();
Your request will be executed toward the datasource as soon as you'll call .ToList() method.
That's why you cannot perform .ToList() in your Controller as your context as been disposed at the end of the using block.
In your DL method, just do something like:
IEnumerable<Something> function()
{
using(DBContext context = new DBContext())
{
return something.ToList();
}
}
and in your Controller you'll get an IEnumerable of Something:
var mySomethingIEnumerable = DL.Function();
Hope that helps!
In my ViewModel I have some code like that:
public class OrderViewModel
{
private UserOrder order;
private DeliveryCentre deliveryCentre;
// This is my EF Container
private CatalogueContainer catalogue = new CatalogueContainer();
// do some stuff...
public void Save()
{
if (order == null)
{
order = catalogue.UserOrders.CreateObject();
}
// do some other stuff...
if ((deliveryCentre == null)
|| (deliveryCentre.Id != deliveryCentreId))
{
deliveryCentre = catalogue.DeliveryCentres.First(centre => centre.Id == deliveryCentreId);
//Causes a context error, not sure why...
order.DeliveryCentre= deliveryCentre;
}
catalogue.SaveChanges();
}
So when the delivery centre is new and the order is new, I am hit by the old "The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects" error, which seems a trifle unfair to me - I just can't figure out what I need to do to make them belong more to the same object context. I assume this is due to some fundamental misunderstanding of the behaviour of Entity Framework.
You are not disposing your context. It may be possible that one of the entities order or deliveryCentre is attached to an old context which still holds references to the entities. You can create and dispose your context with an using statement inside of the Save method instead to using it as a member variable:
public void Save()
{
using (var catalogue = new CatalogueContainer())
{
// your code...
}
}
And remove the private catalogue member.
The solution turned out to only be indirectly related to the error message- #Slauma asked about the //do stuff... placeholders and when I commented those out the error disappeared.
It turned out that there was another relationship there, where I was creating the object as this.Item = new Item() rather than using this.Item = catalogue.Items.CreateObject() so it was being created out of context and when it was added to the order, although the order itself was created from the local context, when the Item was added to it this was somehow dirtying up the context but for some reason this only showed up as a problem when I added the next related object.