This is really odd. I'm trying to a simple thing, I'm trying to find an entity, change a field of that entity, and then commit the changes to the database:
using (AppDbContext context = new AppDbContext()) {
try {
var member = context.Members.SingleOrDefault(m = m.MembershipNumberId == membershipNo);
var centre = context.Centres.SingleOrDefault(c => c.CentreId == oldCentreId);
if (member != null) {
if (centre != null) {
member.Centre_id = Centre.id;
context.SaveChanges();
}
}
} catch (Exception e) {
HandleError(e);
}
}
So pretty straight forward stuff. However, for some reason no changes are being committed to the the database, and in fact, the EntityState is remaining Modified.
Here's where things get really weird in my view: I put a breakpoint in AppDbContext.SaveChanges() so I could look at the changetracker in debugging.
When I do this, everything looks good, I see my Centre entity in the Unchanged state, and I see my Member entity in the Modified state. When I open the Member entity that is in the ChangeTracker in the watch window, for some reason this makes the code work when I resume, and the database is updated accordingly.
So long story short: SaveChanges doesn't commit changes to the database. But, if I put a breakpoint in save changes, look at the changetracker in the watch window, and then open up the modifed entity (Member in my example), then just resume, it does work!
So what could be happening when you look at an entity in the changetracker in the watch window that is causing the code to work? I'm at a loss here and would greatly appreciate any help. Thanks in advance.
I've worked out what it was. The Member POCO had the following field on it:
[Required]
public virtual Account
A required lazy loaded field. It was spitting out a validation error because it thought there was no account, when I opened the entity in the watch window, however, it was forcing the Lazy loading to kick in, hence loading the Account. I've taken the [Required] off and now it's working.
Now to work out why I wasn't seeing the validation error at first :/
Related
I'm surprised that despite spending quite some time looking for the answer I haven't found it on the internet myself. What's even more surprising to me is that people tend to compare Attach to Add, and not to the Update. Furthermore, I've found very little mentions of the actual Update method, despite it being seemingly most obvious choice for the desired modify operation.
From the materials I managed to find I figured, that Attach should be used when you know the object exists somewhere in the database. If you don't make changes to it, no operations will be performed on the entity, and if you do - it will be updated. But then why would you need Update if it does the same thing from the sound of it?
I have this code snippet which I took from udemy course.
MovieController.cs
[HttpPut("{id}")]
public IActionResult ReplaceMovie(long id, [FromBody] Movie m)
{
if (ModelState.IsValid)
{
////
// logic in question starts here vvv
////
m.MovieId = id;
if (m.Studio != null && m.Studio.StudioId != 0)
{
context.Attach(m.Studio);
}
context.Update(m);
context.SaveChanges();
return Ok();
}
else
{
return BadRequest(ModelState);
}
}
But then why not just make it this way?
m.MovieId = id;
context.Update(m);
context.SaveChanges();
return Ok();
Would it not result in the same outcome, since we know that in case the Movie already has it's Studio assigned, then it (the Studio) exists in the database and we don't need to apply any changes to it?
Now if we applied some changes to the movie AND to the studio objects, then wouldn't it be safer to just perform the update modifications in their individual controllers? For example - if I want to update the movie and the studio, then I would first call the StudioController which would perform the modify operation on the Studio and then call the MovieController to update the movie.
What approach would be optimal for the best flexibility and clarity? What should I know about Attach and Update in general?
.Update will mark the entity as being in a Modified state. Update can be used to start tracking a mix of new and existing entities where the existing entities may have some modifications. The new entities will be inserted while the existing entities will be updated.
.Attach will mark the entity as being in an Unchanged state. However, entities will be put in the Added state if they have store-generated keys (e.g. Identity column) and no key value has been set. This means that when exclusively using store-generated keys, Attach can be used to start tracking a mix of new and existing entities where the existing entities have not changed. The new entities will be inserted while the existing entities will not be saved other than to update any necessary FK values.
Source Info:
Why use Attach for update Entity Framework 6?
https://blog.oneunicorn.com/2016/11/17/add-attach-update-and-remove-methods-in-ef-core-1-1/
Probably not pertinent, but I'm using:
.NET MVC 5.2.3 w/ Razor 3.2.3, Entity Framework 6.1.3 Code First, Visual Studio 2015.
Okay, in my controller method, I have--in essence, but dumbed down for conciseness:
using( var context = new MyContext() ) {
var person = context.Persons.Include( x => x.PostalCode ).FirstOrDefault();
return View(person);
}
Now, originally the Zip data entry property was not a foreign key...just whatever 5-digit string the user entered. Now, however, it's a foreign key, in essence, so that we can get postal-code information.
public string Zip { get; set; }
[ForeignKey("Zip")]
public virtual PostalCode PostalCode { get; set; }
Not ideal structure...I know...
But anyway, if the user has a postal-code recognized by our system, all great, everything loads. However, if the user has an unknown or invalid zip code, e.g. 00000, EF sees a non-null Foreign Key and this results in the following problem:
In my view-file (so after I've disposed of my context), I check the property of our greedily-loaded entity:
#if( person.PostalCode != null && person.PostalCode.IsInServiceArea ) {
<div>Service Area HTML</div>
}
Unfortunately, because of EF overriding my virtual property, even when there is no PostalCode, it's not NULL. So, when this code runs it throws an exception that the ObjectContext has been disposed of, which means that EF is trying to lazy-load an Entity even though it already tried to greedily load and should know it doesn't exist :(
The obvious solutions (please don't answer w/ these):
Validate zip codes on entry and only allow ones we know about and set unknown zip codes to NULL (I like this, but I don't have time to re-engineer this)
Get the value of IsInServiceArea while the context is opened and put it directly in my View Model so that I set the value before my context is disposed of. (This is actually what I'm planning to do, so I don't need this answer):
The Question
In Entity Framework, Code-First, what is the correct way to check to
see if a greedily loaded, LEFT OUTER JOIN'd entity, was loaded AFTER
the context is disposed of?
Based on answers I have found, (e.g. the below), I'm thinking it's likely that this is not possible without the context being open...but thought I'd ask anyway:
How to determine if Navigation Property in the Entity Framework is set without loading the related records
So, referring back to my comment, you can disable lazy-loading and proxy creation altogether. Just find the constructor(s) of your dbcontext type, and then add these two lines into the method:
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
This way you can disable it globally (whenever a context is created the ctor is run, these settings are applied).
Or, you can just disable it for one instance if you set these on the context object itself after it's created.
Alternatively let your context live through the entire request so that lazy loading can be fulfilled on the view.
Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
MyContext Create() { return new MyContext(); }
app.CreatePerOwinContext(Create);
...
}
MyController.cs
var context = HttpContext.GetOwinContext().Get<MyContext>();
var person = context.Persons.Include( x => x.PostalCode ).FirstOrDefault();
return View(person);
I've just started using Entity, and so far, it's made my life infinitely easier. However, when I try to update a row, this is where I run into issues. Googling brought me to the Attach(), to which I've tried:
public void Update()
{
using (var context = new HDDCContainer())
{
context.Projects.Attach(this);
context.SaveChanges();
}
}
This is a method that is in my Project class.
However, after querying my database, I find that it, in fact, did not update. I get no errors or exceptions from this. When I step through, I can see the changes to the project that I made.
you need to tell Entity frame work that the state of the object that you have just attached have been changed in comparison with the value that is in the database, you simply modify the state of the entry as the following:
db.Favorites.Attach(favorite);
var ent = db.Entry(favorite);
ent.State = EntityState.Modified;
db.SaveChanges();
Have ago at it and let me know
You must make changes to the entity after it is attached.
For example
context.Projects.Attach(project);
project.Name = "New Funky Name";
context.SaveChanges();
If the entity is being modified outside you save/update method as per your example, then you may need to flag the state manually.
context.Projects.Attach(project);
DbEntityEntry entry = context.Entry(day); // Gets the entity Entry instance
entry.Property("Name").IsModified = true; // Individual property modified
entry.State = System.Data.EntityState.Modified; // or Entire object modified
I have the following Model
public class CourseModel
{
[Key]
public int courseID { get; set; }
...
public virtual ICollection<CourseMeetModel> meets { get; set; }
}
When I try to edit one of the entries and if the input is valid it works fine.
However if the if its not valid it alerts the user of the mistakes. Once the user fixes the mistakes and tries to save i get the following exception.
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded. Refresh ObjectStateManager entries.
I have noticed this happens if the input fails the validation steps in my controller.
My Controller
public ActionResult EditCourseConfirmed(CourseModel course)
{
CoursesDBContext db = new CoursesDBContext();
bool valid = validateCouse(course); //If this fails and the course model is returned back to the view I get that error
if (valid)
{
try
{
db.Entry(course).State = EntityState.Modified;
db.SaveChanges();
Session[d.s_Clear] = false;
return RedirectToAction("Index");
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, "Unable to save the course, please try again." + e.Message);
return View(course);
}
}
return View(course);
}
Try this
try {
context.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
context.Refresh(RefreshMode.ClientWins, db.table);
context.SaveChanges();
}
Props: https://stackoverflow.com/a/6848729/1166147
Adding on to explain, per your comments. Thanks for your EF4 CTP5 tip. Glad this fixed it, please mark as accepted. When you get this error, something happened that changed the data (another user, etc) between load and update, there is a trigger causing problems, or if using a stored procedure it hit 0 recs. It is hard to know without more info. Is this the first update hit? Was there another update run first that succeeded before the user hit the error, modified, and then tried to go on without refreshing? Do you have any triggers? What is your degree of concurrency - did another user edit and save between this users' query and update? Read the link to the post I gave - someone mentioned a ReadOnlyAttribute in an entity key's metadata being replaced, which causes its value to become zero as a potential cause - I am not sure about this, but it makes sense -
(Modified from MSDN) By default, the Entity Framework implements an optimistic concurrency model. This means that locks are not held on data in the data source between when the data is queried and the data is updated, creating the potential for this error if another user modified the data. When this attribute is used, the Entity Framework checks for changes in the database before saving changes.
Any conflicting changes will cause an OptimisticConcurrencyException.
An OptimisticConcurrencyException can also occur when you define an Entity Data Model that uses stored procedures to make updates to the data source. In this case, the exception is raised when the stored procedure that is used to perform updates reports that zero rows were updated. SET NOCOUNT ON would fix thia.
Thanks to user #user1166147 it was causing a dbupdate exception though I still don't know why ...
Since EF4 CTP5 DbContext doesnt have a refresh method i ended up doing this:
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException e)
{
var entry = e.Entries.Single();
entry.OriginalValues.SetValues(entry.CurrentValues);
entry.CurrentValues.SetValues(entry.CurrentValues);
db.SaveChanges();
}
some more details here http://blogs.msdn.com/b/adonet/archive/2011/02/03/using-dbcontext-in-ef-feature-ctp5-part-9-optimistic-concurrency-patterns.aspx
In your view make sure to add this
#Html.HiddenFor(m => m.courseID)
I got the exception Reason was my entity has a Key with a mapping of
HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
and when i added a new Entity and supplied an id, at save time the context will think it is a modified Entity and not a new one, so i did not supply a value to the Key and at save time i check for the keys default value, from this i know if its new or modified
After days studying EF to understand (kinda..) how it works, I finally realized that I might have a big problem.
Imagine that I have two entities: Pais and UF. The relationship between them is Pais (0..1) ... (*) UF. A screenshot: http://i.imgur.com/rSOFU.jpg.
Said that, consider that I have a controller called UFController and it has actions for Edit and Create, which are just fine. My views are using the EditorFor helper (or similar ones) for inputs, so when I submit the form the controller will receive a UF object filled with all the data (automatically) with a reference to an almost-empty Pais. My view code (part of it):
#* UF attributes *#
#Html.EditorFor(m => m.Sigla)
#Html.EditorFor(m => m.Descricao)
#Html.EditorFor(m => m.CodigoIBGE)
#Html.EditorFor(m => m.CodigoGIA)
#* Pais primary key ("ID") *#
#Html.EditorFor(m => m.Pais.Codigo) // Pais id
The controller Edit action code:
[HttpPost]
public ActionResult Edit(UF uf)
{
try
{
if (ModelState.IsValid)
{
db.UFs.Attach(uf);
db.ObjectStateManager.ChangeObjectState(uf, EntityState.Modified);
db.SaveChanges();
return this.ClosePage(); // An extension. Just ignore it.
}
}
catch (Exception e)
{
this.ModelState.AddModelError("Model", e.Message.ToString());
}
return View(uf);
}
When I submit the form, this is what the action receives as uf:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
Descricao: "Foobar 2001"
ID: 936
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
And uf.Pais:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
Codigo: 0
CodigoBACEN: null
CodigoGIA: null
CodigoIBGE: null
Descricao: null
UF: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
The original information (the one on the database) is uf.Pais.Codigo == 716. So, right now I'm receiving the updated information. The problem on that the controller is not upading the FK in the database.
I don't want to set the EntityState from uf.Pais to Modified because the entity itself wasn't changed (I didn't changed the information from that entry), but the relationship was.
In other words, what I'm trying to do is change the value of the FK, pointing the uf.Pais to another instance of Pais. Afaik, it's impossible to change the relationship state to Modified (throw an exception), so I'm looking for alternative solutions.
I've read a bunch of topics I've found on Google about this kind of problem, but I still didn't find a simple and elegant solution. The last ones I read here on stackoverflow:
How to work with navigation properties (/foreign keys) in ASP.NET MVC 3 and EF 4.1 code first
Strongly-Typed ASP.NET MVC with ADO.NET Entity Framework
Getting Error 3007 when I add my Entity Model
I asked a question a few days ago about a similar problem ( Entity Framework 4.1 - default EntityState for a FK? ). I didn't understand how EF works that time, so now a bunch of things look clear to me (that's why I'm opening a new question).
For the Create action I've been testing this solution (proposed by Ladislav on my other question), but it generates an additional select (which can be eventually slow for us):
// Here UF.Pais is null
db.UFs.AddObject(uf);
// Create dummy Pais
var pais = new Pais { Id = "Codigo" };
// Make context aware of Pais
db.Pais.Attach(pais); // <- Executing a SELECT on the database, which -can- be slow.
// Now make the relation
uf.Pais = pais;
db.SaveChanges();
I can replicate this for the Edit (I guess), but I don't want that additional SELECT.
So, in resume: I'm trying to use navigation properties to send data to the controller and save them directly in the database using a fast and easy way (without messing too much with the entity - these ones are simple, but we have huge and very complex ones with a lot of FKs!). My question is: there's a solution that doesn't involve executing another query in the database (a simple one)?
Thanks,
Ricardo
PS: sorry for any english mistakes and any confusions.
Update 1: using BennyM's solution (kind of..)
I tested the following code, but it doesn't work. It throws an exception: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key." Probably because Pais is already in the context, I guess?
I'm using a Entities (created by EF) class as context. Also, I don't know what is the method Entry, and I don't know where is it. Just for "fun", I tested this:
// Attach the Pais referenced on editedUF, since editedUF has the new Pais ID, not the old one.
Pais existingPais = new Pais { Codigo = editedUF.Pais.Codigo };
db.Paises.Attach(existingPais);
// Attach the edited UF.
db.UFs.Attach(editedUF);
// Set the correct Pais reference (ignoring the current almost-null one).
editedUF.Pais = existingPais;
// Change the object state to modified.
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
return this.ClosePage();
The exception is throwed when I try to attach the editedUF to the current context. I'm working with this idea right now, trying to find other solutions. Also, you're right BennyM, attaching the Pais to the context is not generating an additional SELECT. I don't know what happened that time, it really doesn't do anything with the database.
Still, this is a manual solution: I have to do that for each FK. That's what I'm trying to avoid. You see, some programmers, even if you explain 100 times, won't remember to do that with each FK. Eventually that'll come back to me, so I'm trying to avoid anything that can lead into errors (database or code ones) to make sure everyone can work without any stress. :)
I'm answering my own question because I've found a simple solution (at least in my case). My scenario uses a lot of Views for data input (which means that I have a lot of entities). I needed a simple and easy to use solution, so I deleted my entire Entities EDMX file (Ctrl+A, Delete!).
Then I decided to add again Pais and UF entities, but checking the checkbox for exposing the FK attribute. On first I though they can't work together, but they can, but you need to be a little careful on how to use it. They're now linked with navigation properties and the exposed FK.
The reason I couldn't add the FK attribute is because I was doing it manually. Using the "Update model from database" again checking the correct option it worked flawless.
In my edit view, I'm setting the ID of Pais into the FK attribute, not the Pais.Codigo. The reason why I do that is because the FK attribute is a scalar property and then I can detect changes.
This is the current view code for the Pais input (it's not exactly it, but it's similar to this):
#Html.EditorFor(m => m.PaisCodigo)
Btw, PaisCodigo is the FK. Yes, it can get a little confusing with Pais.Codigo, but we didn't decided any naming rules (yet). Any suggestions on this idea would be appreciated.
The final Edit action code is like this (I removed error processing to make it look simple!):
[HttpPost]
public ActionResult Edit(UF editedUF)
{
if (ModelState.IsValid)
{
// Attach the edited UF into the context and change the state to Modified.
db.UFs.Attach(editedUF);
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
// Call an extension (it's a redirect action to another page, just ignore it).
return this.ClosePage();
}
}
This is what is received when I submit the form for editedUF:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
CodigoPais: 0 <-- new Pais ID!
Descricao: "Foobar 2000"
ID: 902
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
Sigla: "RI"
Usuarios: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.Usuario>}
As you can see, CodigoPais is pointing to the new Pais ID.
About the editedUF.Pais navigation property, there's a small detail. Before attaching it into the context, it's null. But, hey, after adding, this is what happens:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
(...)
Codigo: 0
CodigoBACEN: 1058
CodigoGIA: 0
CodigoIBGE: null
Descricao: "Brasil"
UFs: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
So, it has been filled. The cost for that should be one query, but I couldn't capture it on the monitor.
In other words, just expose the FK, change it using the View and use the navigation property to make the code a little more clear. That's it! :)
Thanks everyone,
Ricardo
PS: I'm using dotConnect for Oracle as a base for the EF 4.1. We don't use SQL Server (at least for now). The "monitor" I said before was devArt's dbMonitor, so I can see all queries sent to the Oracle database. And, again, sorry for any english mistakes!
If you include the foreign keys in your model. So add a PaisId property to the UF entity, you can directly set it and it will update the association.
using (var db = new Context())
{
db.UFs.Attach(editedUF);
editedUF.PaisId = theForeignKey;
db.Entry(editedUF).State = EntityState.Modified;
db.SaveChanges();
}
Also I've tested the approach you already mentioned in your question, and I don't get that extra select when attaching an entity. So this works as well:
using (var db = new Context())
{
ExistingPais pais = new Pais{Id =theId};
db.Pais.Attach(pais);
db.UF.Attach(editedUF);
editedUF.Pais = pais;
db.Entry(editedUF).State = EntityState.Modified;
db.SaveChanges();
}
Assuming your code looks like this:
public class Pais
{
public int Id { get; set; }
public virtual ICollection<UF> UFs { get; set; }
}
public class UF
{
public int Id { get; set; }
public virtual Pais Pais { get; set; }
public int PaisId { get; set; }
}