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
Related
I guess I just don't understanding EF tracking. I have the context added via dependency injection via:
builder.Services.AddDbContext<OracleContext>(options => options.UseOracle(OracleConnectionString, b => b.UseOracleSQLCompatibility("11"))
.LogTo(s => System.Diagnostics.Debug.WriteLine(s))
.EnableDetailedErrors(Settings.Dev_System)
.EnableSensitiveDataLogging(Settings.Dev_System)
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
I set the tracking behavior to NoTracking here (at least so I thought).
I have a .NET Controller that has the context in its constructor. It passes this context to my class constructor containing my methods. Pretty much everything works fine... except for one:
I have a method that does a context.RawSqlQuery to get a list of objects. I iterate over these objects calling two separate methods from a different class that was generated the same way (using the injected context). This method first does a EF query to verify the object does not already exist, if it does it returns it and we move on - no issues. On the query to check if it exists I also added .AsNoTracking() for SnGs. However, if the object does not exist, and I try to make a new one... every time I do an context.add I get
"The instance of entity type 'Whatever' cannot be tracked because another instance with the key value '{MfrId: 90}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
I have tried adding
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking - no change
I have tried adding context.Entry(NewItem).State = EntityState.Detached; before and after the call - no change.
I tried a loop in the context that gets all tracked objects and sets them to detached - no change.
What am I missing here? First - why is it tracking at all? Second - any suggestions on how to get passed this? Should I just give up using dependency injection for the context (suck... lots of rework for this)?
As requested - here is the class & method that is failing (non related stuff removed):
public class AssetMethods : IAssetMethods
{
public OracleContext db;
public AssetMethods(OracleContext context)
{
db = context;
}
public CcpManufacturer? CreateNewManufacturer(CcpManufacturer NewMan, string ActorID)
{
...blah blah non DB validation stuff removed...
//Check if it exists already
CcpManufacturer? OldMan = db.CcpManufacturers.Where(m=>m.MfrName == NewMan.MfrName).AsNoTracking().FirstOrDefault();
if (OldMan != null) {
return OldMan;
}
//Who done did it
NewMan.CreatedBy = ActorID;
NewMan.CreationDate = DateTime.Now;
NewMan.Isenabled = true;
//save
db.CcpManufacturers.Add(NewMan);
db.SaveChanges(); //fails here
//Prevent XSS Reflection
return db.CcpManufacturers.Find(NewMan.MfrId);
}
}
this method is called from this code. The OM is also using the injected context
List<MasterItem> Items = OM.RawSqlQuery(Query, x => new MasterItem { MFR_NAME = (string)x[0], MODEL_NUMBER = (string)x[1], LEGAL_NAME= (string)x[2]});
foreach (MasterItem item in Items)
{
CcpManufacturer? Man = new() {
MfrName = item.MFR_NAME,
Displayname = item.MFR_NAME
};
Man = AM.CreateNewManufacturer(Man,System.Id); //if its new.. it never get passed here because of the discussed error...
if (Man == null || Man.MfrId == 0)
{
continue;
}
.... other stuff
}
So the mfr id is added to a new object that's passed to a pretty much identical methods to create a item (where the mfr id is attached). Now - if I detach THAT item - I am ok. But why is it tracking when I have it turned off pretty much everywhere?
Yes, you found your problem.
Turning off Tracking affects what EF does when querying for entities. This means when I tell EF to read data from the DB and give me entities, it will not hang onto references of those entities.
However, entities you tell a DBContext to ADD to a DbSet and related entities will be tracked, regardless of your tracking setting.
So if I do something like:
var entity1 = context.Entities.Single(x => x.Id == entityId).AsNoTracking();
var entity2 = context.Entities.Single(x => x.Id == entityId).AsNoTracking();
The references to entity1 and entity2 will be 2 distinct references to the same record. Both are detached, so the DbContext isn't tracking either of them. I can use and attach either of them to perform an update, but that entity would be from that point considered Attached until I explicitly detach it again. Attempting to use the other reference for an update would result in that error. If I query specifying NoTracking after I have attached and updated that first entity reference, I will get back a new untracked entity reference. The DbContext doesn't return it's tracked reference, but it doesn't discard it either.
The exact same thing happens if I add a new entity then query for it specifying NoTracking. The query returns an untracked reference. So if you try and attach it to update a row, EF will complain about the reference it is already tracking.
I don't recommend diving down the rabbit hole of passing around detached entities unless you're keen to spend the time to really understand what is going on behind the scenes and prepared for the pretty deliberate and careful handling of references. The implications aren't just things not working as expected, it's having things work or not work on a completely situational basis which can be a nasty thing to debug and fix, even when you know what to look for.
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 :/
I have a database with layers and figures on layers (for drawing). I use a SQL Server CE, create database context on application start, work with db.Layers.Local and call SaveChanges before application's exit.
All operations with this "local" db are separated into two types: read and write. When I want to read some entities I'm not going to change it.
For example:
MainModel db = new MainModel(); //created at application start and stored as field of repository
public List<Figure> GetAllFigures(){
db.Configuration.AutoDetectChangesEnabled = false; //disable before querying local
var res = db.Layers.Local.SelectMany(x=>x.Figures).ToList();
db.Configuration.AutoDetectChangesEnabled = true;
return res;
}
public void ChangeLayer(Figure figure, Layer layer){
figure.Layer = layer;
db.Figures.Local;
db.Layers.Local; //manually call detectChanges
}
So the logic is call DetectChanges on updates after any change to allow disabling it on reading. I do reading much more often than changing and reading without DetectChanges is sometimes 100 times faster. Is that logic correct and everything will work as expected? Did I understand right why EF calls DetectChanges when query to DbSet.Local?
Read everything with the AsNoTracking() extension so that the entities are not attached to the context.
As suggested, before changing the properties, attach it to the context, change the properties (layer) and mark it as modified so that it will be persisted.
Also you can call DetectChanges() so that the modifications are tracked automatically
After change Attach your entity to Context
Context.YourEntity.Attach(YourEntityObject);
// And Save here
Or use
Context.Entry(YourEntityObject).State = EntityState.Modified;
I have an issue with my method that updates the database with user information.
during HttpGet it uses a view model to ensure the integrity of the data, and in HttpPost, the data is passed back and is there. (ran some checks with breakpoints and everything was holding the correct user data that they should have been)
However, when I run my .Save() method, this information is not stored into the DB.
I've checked to see if its pointing to the correct database by changing the data manually, it comes up in a list view just fine. I'M missing something, just can't figure out what ><
Below is the code there is form data in all of the relevant view models, but it just doesn't save!
[HttpPost]
public ActionResult HouseCreate(CreateHouseViewModel viewModel)
{
if (ModelState.IsValid)
{
var house = new House();
house.address = viewModel.address;
house.postCode = viewModel.postCode;
house.noOfDisputes = viewModel.noOfDisputes;
_db.Save();
return RedirectToAction("House");
}
return View(viewModel);
}
house is an object of my house database, modelled correctly with correct Primary keys in place (double checked this in the database viewer.)
save calls:
void DataSources.Save()
{
SaveChanges();
}
No errors come up, which makes it even worse.
using (var context = new UnicornsContext())
{
var unicorn = new Unicorn { Name = "Franky", PrincessId = 1};
context.Unicorns.Add(unicorn); // your missing line
context.SaveChanges();
}
Source
Your "_db" object, I assume is entity framework.
This will have a collection on it, maybe _db.Houses ? You need to add the object to that.
Since it appears you are using Entity Framework, you'll need to inform Entity Framework that your model has been updated. Inbetween the HttpGet and the HttpPost methods, the model becomes dissassociated from the database context. I think the following code work to re-attach it to the context so you can save the changes:
context.Entry(myEntity).State = EntityState.Modified;
context.MyEntities.Attach(myModel);
context.SaveChanges();
Edit: Forgot to add the re-attach method part, assuming this is an existing record in the DB that your updating.
This I find is a far more reliable means to adding items to the database:
// Note: db is your dbContext, model is your view model
var entity = db.Entry(model); // Required to attach the entity model to the database
// you don't need the "var entity = " but its useful
// when you want to access it for logging
db.Entry(model).State = EntityState.Added; // Required to set the entity as added -
// modified does not work correctly
db.SaveChanges(); // Finally save
I need to edit my record in database. I don't think that I'm doing it good enough. I've tried make code shorter by dev = newDev; but it is not saving it then.
Code
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(Dev newDev)
{
try
{
if (TryUpdateModel(newDev) == true)
{
var dev = _db.Devs.Where(x => x.ID == newDev.ID).Single();
dev.Title = newDev.Title;
dev.Body = newDev.Body;
dev.Tags = newDev.Tags;
dev.Image1 = newDev.Image1;
dev.Image2 = newDev.Image2;
dev.Image3 = newDev.Image3;
dev.Image4 = newDev.Image4;
_db.SubmitChanges();
return RedirectToAction("");
}
else
{
return Content("Fail.");
}
}
catch
{
return View();
}
}
Can you help me optimize my code here?
You'll want to read up on the memory model used by .NET. Setting dev = newDev is going to change the value of the dev variable, but variables have a local scope. It won't change the value of other variables that were referencing the same object.
In this case, the context that you pulled dev from is probably keeping track of the dev that it pulled out and gave to you, so if you change its properties and call SaveChanges then it will know which values to change.
If you're just trying to copy all the values from one object to another without having to manually write a line of code for each property, you should be able to use a tool like AutoMapper to automatically map all the similarly-named properties from one object to another.
If you're using Entity Framework (it looks like it?) then you can't update entities this way.
The Entity Framework uses change tracking to understand what changes have happened to an entity. To do this, it keeps a list of the specific instances that are loaded from that context, and what their initial state was, so it can detect what changes have been made to those instances before you call SaveChanges on the context.
When you assign directly to a reference type, you're just reseating the reference. You aren't changing the initial object instance. Existing references to that instance (like the one the Entity Framework keeps internally to keep track of changes) don't change, and you will end up pointing at different object instances.
So, just use your code the way you have it now. This is the only way to blindly update every field. If nothing was updated, the Entity Framework's change tracking should cause SaveChanges to do nothing. If something was updated, it will perform the corresponding SQL to persist the changes to the DB.
If that doesn't work, then:
1. dev is a reference type
2. A reference of dev is being changed to point to another object (newDev)
Which means, that you're already working with an object out of context. And to be able to update that object, you first ned to somehow attach that object to the context and make the context be aware of that its the updated object of some existing entity there.
Hope this will help.