I am reading the following tutorial about entity framework 6 Link. And inside the section named ”Adding an Edit Page for Instructors”, the author wrote the following code inside the Post edit action method:-
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult EditPost(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var instructorToUpdate = db.Instructors
.Include(i => i.OfficeAssignment)
.Where(i => i.ID == id)
.Single();
if (TryUpdateModel(instructorToUpdate, "",
new string[] { "LastName", "FirstMidName", "HireDate", "OfficeAssignment" }))
{
try
{
if (String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
db.Entry(instructorToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (RetryLimitExceededException /* dex */)
{
//Log the error (uncomment dex variable name and add a line here to write a log.
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
}
return View(instructorToUpdate);
}
This code will cover these three conditions:-
If the user clears the office assignment and it originally had a value, you must remove and delete the OfficeAssignment entity.
If the user enters an office assignment value and it originally was empty, you must create a new OfficeAssignment entity.
If the user changes the value of an office assignment, you must change the value in an existing OfficeAssignment entity.
So does this means that
db.Entry(instructorToUpdate).State = EntityState.Modified;
will cause an insert statement to be performed for the OfficeAssignment record incase the Instructor did not have a prevouse OfficeAssignment object ? and what is the rule that govern this ?
here is the complete model diagram:-
DbContext.Entry method is used to do an explicit loading, that means that It gives you access to all the information that the DbContext has about an entity. This goes beyond the values that are stored in the properties of the actual entity and includes things such as the state of the entity and the original values for each property when it was retrieved from the database.
When you call the TryUpdateModel method, it will update the properties (that you pass their names as a parameter) with values from the model binder. One of these properties is OfficeAssignment, wich is updated too. If in your view you don't enter a Location, then you don't have reason to create a new OfficeAssigment (that's way you need to do instructorToUpdate.OfficeAssignment = null; because even when you don't enter a new Location, you will have a instance of OfficeAssignment). If you add a new Location, you are going to create a new OfficeAssignment, and if you modified the Location, then you are going to modified its value.
When you do this:
db.Entry(instructorToUpdate).State = EntityState.Modified;
You are going to set a flag on the entity indicating it has been changed. When the SaveChanges method is called, the Modified flag causes the Entity Framework to create SQL statements to update the database row. All columns of the database row will be updated, including those that the user didn't change, and concurrency conflicts are ignored. To understand better what happend, you can look the Instructor instance like a tree. Code First recognizes that you have a navegation property, so it need to be updated or insterted(depending on the case). If the OfficeAssignment have an Id different of the default(int) (I'm pressuming that is an interger), then it will be updated, and in other case, it will be inserted.
There are basically two ways an entity can be persisted through EF.
A. Add it directly to the Dbset with the additional relationships you want it to have.
Entity e = new Entity();
e.ForeignEntityId = 123;
context.Entities.Add(e);
context.SaveChanges();
B. Attach it to an existing entity and if that entity is/was untracked, mark that entity as `Modified.
Entity e = new Entity();
ForeignEntity fe = context.Find(...);
//Only needed if 'fe' was untracked
//context.Entry(fe).State = EntityState.Modified;
fe.Entity = e;
context.SaveChanges();
The way presented in your question is the second way. It's all about getting the "new" object to be present in the object graph that represents all tracked EF entities from your DB.
Yes,can load the DbContext.Entry method and can be used to do an explicit loading as mentioned above
I will suggest rather do Delete and Insert until you do not have real-time needs of modification.
{
//Remove existing data
modelname existingobj = dbobj.tablename.Find(id);
dbobj.tablename.Remove(existingobj);
dbobj.SaveChanges();
//Add data
dbobj.Entry(existingobj).State = EntityState.Added;
dbobj.SaveChanges();
}
Related
This relates to my previous question from a few days ago: EF Core duplicate keys: The instance of entity type '' cannot be tracked because another instance with the key value '' is already being tracked
I am using context.ChangeTracker.TrackGraph to give me more control when inserting data. The insert process works great as show by my own answer on the question above. This problem results when I update items and attempt to save them.
My primary model is as follows (type names have been changed for simplicity):
public class Model
{
public List<Data1> List1 { get; set; }
...
}
Data1 looks like this:
public class Data1
{
public string Name{ get; set; }
...
}
To update to the database, I use this:
using var context = Factory.CreateDbContext();
context.ChangeTracker.TrackGraph(model, node =>
{
if (!(node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)) && node.Entry.IsKeySet)) //this is a workaround but it solves the pk problems I have been having
{
node.Entry.State = EntityState.Modified;
}
else if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Added;
}
});
return await context.SaveChangesAsync();
This works great as it prevents my existing entities (inheriting fromEFChangeIgnore) from being inserted and otherwise handles updates and inserts of other entities well. However, if I have more than one Data item in List1, I get the error:
The instance of entity type 'Data1' cannot be tracked because another instance with the key value '{ID: 0}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I understand this error but I am confused as to why it is appearing in this scenario. Yes, both entities have an ID of zero, but only because they have yet to be inserted to the database. What am I doing wrong here?
It turns out my bug was due to a misplaced parenthesis. Go figure.
In my SaveChanges I had accidently negated the entire conditional at the top, such that entities that should have been Modified became Added, and vice versa. This led to multiple entities with an ID of 0 that I intended to add being marked as Modified. EF then complained that two existing entities had the same ID.
That is, this line:
if (!(node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)) && node.Entry.IsKeySet))
now becomes (conditionals swapped for clarity):
if (node.Entry.IsKeySet && (!node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore))))
And my entire SaveChanges method now is:
using var context = Factory.CreateDbContext();
context.ChangeTracker.TrackGraph(model, node => //all entities inheriting from EFChangeIgnore will not be touched by the change tracker. Other entities with keys will be modified, and items without keys will be added
{
if (node.Entry.IsKeySet && (!node.Entry.Entity.GetType().IsSubclassOf(typeof(EFChangeIgnore)))) //this is a workaround but it solves key duplication problems
{
node.Entry.State = EntityState.Modified;
}
else if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Added;
}
return await context.SaveChangesAsync();
For some reason my edit action are not updating my navigations properties
Check de codes
public ActionResult Edit(ClienteViewModel clienteViewModel)
{
if (ModelState.IsValid)
{
var cliente = Mapper.Map<Clientes>(clienteViewModel);
_context.Entry(cliente).State = EntityState.Modified;
_unitOfWork.Commit();
return RedirectToAction("Index");
}
return View(clienteViewModel);
}
Any ideas?
EDIT
I added a hidden field with endereco.id on edit view and now Endereco.Id is going to controller, but the error still the same
If you look at the primary key Id of your navigational property Endereco you will see that its value is Guid.Empty 00000000-0000-0000-0000-000000000000.
So updating that entity will generate a SQL query with this WHERE clause below:
WHERE Id = '00000000-0000-0000-0000-000000000000'
Of course a line with that empty guid doesn't exist into your table.
Your original code do a mapping with AutoMapper so make sure that all properties are mapped correctly when calling the line below:
var cliente = Mapper.Map<Clientes>(clienteViewModel);
Updates: Based on your comment you also need to be sure that the navigational property state is changed to EntityState.Modified (because changing the root entity's state doesn't impact the navigational properties) like below:
_context.Entry(cliente.Endereco).State = EntityState.Modified;
While writing an update method for my Entity Framework repository, I include the following code:
public bool UpdateProduct(int id, Models.Product product)
{
Product ctxProduct = GetProductIncludingProductLists(id); //Pulls directly from context
if (ctxProduct != null && product != null)
{
/*Update ctxProduct fields using product*/
_ctx.Entry(ctxProduct).State = EntityState.Modified; //_ctx is my DbContext
return true;
}
return false;
}
But the line of code where I set the entity's status to modified throws the following error:
A first chance exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll
Attaching an entity of type '..Product' 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.
I am confused by what this error message is trying to tell me. Since I have pulled this entry out of the context, the only thing its Primary key should be conflicting with is itself. In addition, I know this entry has been assigned an id since I accessed it using its id.
Lastly, the reason I am setting this entry's state to modified is because calling _ctx.saveChanges() is returning 0, indicating to me that the context isn't aware I've changed anything (when I have).
Can anyone explain why this error is being thrown and what I need to do to make the context aware of my changes?
EDIT
GetProductIncludingProductLists(id):
public Product GetProductIncludingProductLists(int id)
{
try
{
return _ctx.Products.Include("ProductLists")
.ToList()
.Select(p => new Product()
{
ProductId = p.ProductId,
CUSIP = p.CUSIP,
SEDOL = p.SEDOL,
BUID = p.BUID,
Key = p.Key,
Track = p.Track,
ProductLists = ((p.ProductLists.Select(l => new ProductList()
{
ProductListId = l.ProductListId,
Name = l.Name,
Products = null
})
.ToList() as List<ProductList>) ?? null)
})
.First(item => item.ProductId == id);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
return null;
}
}
The reason for the crazy select statements is because product and product list are related N:N and not including them would cause an error for 'circular serialization'
looking at this code, I'm afraid there is some lack of knowledge on how EF works.
First, to load an entity this code is overcomplicated and does horrible things:
1: this loads all the products in the DB into memory
return _ctx.Products.Include("ProductLists")
.ToList()
2: this copies the properties of already existing objects in new objects
.Select(p => new Product() { // create new object and copy properties !!
3: this queries individually each of the related lists of each and every product in the DB (and creates a new list, when there is one already available)
p.ProductLists.Select( // query the list of each product !!
4: and all of this just to get a product with the given id!
.First(item => item.ProductId == id);
You can simply do this:
return _ctx.Products
.Include(p => p.ProductLists) // it's much safer a lambda than a magic string
.First(item => item.ProductId == id);
which will load only the required product, with its corresponding product lists. And it will be attached to the context.
Second. If your product is already attached to the context, i.e. you've loaded it using the context, for example as I've just shown, and provided that _ctx has not been disposed, the product is already tracked by the context, and you don't need to care about setting its state. Whenever you make any change to it, the context will automatically change its state so that, when you call SaveChanges the changes will be automatically posted to the DB.
As you can see your code is overcomplicated. Try to make some tutorials to understand how EF works. You'll spare a lot of time. You can use the EF section of MSDN. It has clear documentation and examples.
You are returning a new product that is not being change tracked by Entity Framework:
.Select(p => new Product()
the first ToList() bring all products in the context (are you sure you want that ?).
then you create new one with the select
trying to attach the new one conflict with the loaded one.
In Entity Framework - Is there any way to retrieve a newly created ID (identity) inside a transaction before calling 'SaveChanges'?
I need the ID for a second insert, however it is always returned as 0...
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
objectContext.Connection.Open();
using (var transaction = objectContext.Connection.BeginTransaction())
{
foreach (tblTest entity in saveItems)
{
this.context.Entry(entity).State = System.Data.EntityState.Added;
this.context.Set<tblTest>().Add(entity);
int testId = entity.TestID;
.... Add another item using testId
}
try
{
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
objectContext.Connection.Close();
throw ex;
}
}
objectContext.Connection.Close();
The ID is generated by the database after the row is inserted to the table. You can't ask the database what that value is going to be before the row is inserted.
You have two ways around this - the easiest would be to call SaveChanges. Since you are inside a transaction, you can roll back in case there's a problem after you get the ID.
The second way would be not to use the database's built in IDENTITY fields, but rather implement them yourself. This can be very useful when you have a lot of bulk insert operations, but it comes with a price - it's not trivial to implement.
EDIT: SQL Server 2012 has a built-in SEQUENCE type that can be used instead of an IDENTITY column, no need to implement it yourself.
As others have already pointed out, you have no access to the increment value generated by the database before saveChanges() was called – however, if you are only interested in the id as a means to make a connection to another entity (e.g. in the same transaction) then you can also rely on temporary ids assigned by EF Core:
Depending on the database provider being used, values may be generated client side by EF or in the database. If the value is generated by the database, then EF may assign a temporary value when you add the entity to the context. This temporary value will then be replaced by the database generated value during SaveChanges().
Here is an example to demonstrate how this works. Say MyEntity is referenced by MyOtherEntity via property MyEntityId which needs to be assigned before saveChanges is called.
var x = new MyEntity(); // x.Id = 0
dbContext.Add(x); // x.Id = -2147482624 <-- EF Core generated id
var y = new MyOtherEntity(); // y.Id = 0
dbContext.Add(y); // y.Id = -2147482623 <-- EF Core generated id
y.MyEntityId = x.Id; // y.MyEntityId = -2147482624
dbContext.SaveChangesAsync();
Debug.WriteLine(x.Id); // 1261 <- EF Core replaced temp id with "real" id
Debug.WriteLine(y.MyEntityId); // 1261 <- reference also adjusted by EF Core
The above also works when assigning references via navigational properties, i.e. y.MyEntity = x instead of y.MyEntityId = x.Id
If your tblTest entity is connected to other entities that you want to attach, you don't need to have the Id to create the relation. Lets say tblTest is attached to anotherTest object, it the way that in anotherTest object you have tblTest object and tblTestId properties, in that case you can have this code:
using (var transaction = objectContext.Connection.BeginTransaction())
{
foreach (tblTest entity in saveItems)
{
this.context.Entry(entity).State = System.Data.EntityState.Added;
this.context.Set<tblTest>().Add(entity);
anotherTest.tblTest = entity;
....
}
}
After submitting the relation would be created and you don't need to be worry about Ids and etc.
You can retreive an ID before calling .SaveChanges() by using the Hi/Lo alhorithm. The id will be assigned to the object once it is added to dbcontext.
Example configuration with fluent api:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>(e =>
{
e.Property(x => x.Id).UseHiLo();
});
}
An excerpt from the relevant Microsoft article:
The Hi/Lo algorithm is useful when you need unique keys before committing changes. As a summary, the Hi-Lo algorithm assigns unique identifiers to table rows while not depending on storing the row in the database immediately. This lets you start using the identifiers right away, as happens with regular sequential database IDs.
#zmbq is right, you can only get the id after calling save changes.
My suggestion is that you should NOT rely on the generated ID's of the database.
The database should only a detail of your application, not an integral and unchangeable part.
If you can't get around that issue use a GUID as an identifier due it's uniqueness.
MSSQL supports GUID as a native column type and it's fast (though not faster than INT.).
Cheers
A simple work around for this would be
var ParentRecord = new ParentTable () {
SomeProperty = "Some Value",
AnotherProperty = "Another Property Value"
};
ParentRecord.ChildTable.Add(new ChildTable () {
ChildTableProperty = "Some Value",
ChildTableAnotherProperty = "Some Another Value"
});
db.ParentTable.Add(ParentRecord);
db.SaveChanges();
Where ParentTable and ChildTable are two tables connected with Foregin key.
You can look up the value in the ChangeTracker like this:
var newEntity = new MyEntity();
var newEntity.Property = 123;
context.Add(newEntity);
//specify a logic to identity the right entity here:
var entity = context.ChangeTracker.Entries()
.FirstOrDefault(e => e.Entity is MyEntity myEntity &&
myEntity.Property == newEntity.Property);
//In this case we look up the value for an autogenerated id of type int/long
//it will be a negative value like -21445363467
var value = entity.Properties?
.FirstOrDefault(pe => pe.Metadata.GetColumnName() == nameof(MyEntity.Id))?.CurrentValue;
//if you set it on another entity, it will be replaced on SaveChanges()
My setup was mysql 5.7, but should work in other environments also.
I am trying to implement an AuditLog using EF 4.1, by overriding the SaveChanges() method as discussed in the following places:
http://jmdority.wordpress.com/2011/07/20/using-entity-framework-4-1-dbcontext-change-tracking-for-audit-logging/
Entity Framework 4.1 DbContext Override SaveChanges to Audit Property Change
I am having problems with the "modified" entries though. Whenever I attempt to get at the OriginalValue of the property in question, it always has the same value as it does in the CurrentValue field.
I first use this code, and it successfully identifies the Entries that are modified:
public int SaveChanges(string userID)
{
// Have tried both with and without the following line, and received same results:
// ChangeTracker.DetectChanges();
foreach (
var ent in this.ChangeTracker
.Entries()
.Where( p => p.State == System.Data.EntityState.Added ||
p.State == System.Data.EntityState.Deleted ||
p.State == System.Data.EntityState.Modified ))
{
// For each change record, get the audit record entries and add them
foreach (AuditLog log in GetAuditRecordsForChange(ent, userID))
{
this.AuditLog.Add(log);
}
}
return base.SaveChanges();
}
The problem is in this (abbreviated code):
private List<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntry, string userID)
{
if (dbEntry.State == System.Data.EntityState.Modified)
{
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName),
dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
// It never makes it into this if block, even when
// the property has been updated.
}
// If I updated the property "Name" which was originally "OldName" to the value "NewName" and then break here and inspect the values by calling:
// ?dbEntry.OriginalValues.GetValue<object>("Name").ToString()
// the result will be "NewName" and not "OldName" as expected
}
}
}
The strange thing is that the call to dbEntry.Property(propertyName).IsModified(); will
return true in this case. It is just that the OriginalValue doesn't have the expected value inside. Would anyone be willing to help point me in the right direction? I cannot seem to get this to work correctly.
When EF retrieves an entity from the database it takes a snapshot of the original values for all properties of that entity. Later, as changes are made to the values of these properties the original values will remain the same while the current values change.
However, for this to happen EF needs to be tracking the entity throughout the process. In a web or other n-tier application, typically the values are sent to the client and the context used to query the entity is disposed. This means that the entity is now no longer being tracked by EF. This is fine and good practice.
Once the application posts back the entity is reconstructed using values from the client and then re-attached to the context and set into a Modified state. However, by default the only values that come back from the client are the current values. The original values are lost. Usually this doesn't matter unless you are doing optimistic concurrency or want to be very careful about only updating values that have really changed. In these cases the original values should also be sent to the client (usually as hidden fields in a web app) and then re-applied as the original values as a part of the attach process. This was not happening in the example above and this is why the original values were not showing as expected.
If you change
dbEntry.OriginalValues.GetValue<object>(propertyName);
to
dbEntry.GetDatabaseValues().GetValue<object>(propertyName);
then that works.
I got this error when i override SaveChanges in context As follows
public override int SaveChanges()
{
var changeInfo = ChangeTracker.Entries()
.Select(t => new {
Original = t.OriginalValues.PropertyNames.ToDictionary(pn => pn, pn => t.OriginalValues[pn]),
Current = t.CurrentValues.PropertyNames.ToDictionary(pn => pn, pn => t.CurrentValues[pn]),
}).ToList();
return base.SaveChanges();
}
and when I cleared it fixed!
ChangeTracker.Entries().ToList() in SaveChanges is wrong...
The problem is not in the code you show here. The issue is that how you track entities.
If you just create an entity object and calls Update on it EF framework just overwrite the existing value in db ( provided you supplied correct ID ). That is done for efficiency. So if you do:
var company = new Company{Id = mySuppliedId, Name = newname};
Context.Companies.Update(company);
Context.SaveChanges();
EF will go directly to DB in one shot and update all properties on the entity, without bringing anything back first. So it has no way of knowing the original values.
If you change the code in your logic to something like:
var company = Context.Companies.Where(c=>c.Id == mySuppliedId).FirstOrDefault();
company.Name = newName;
Context.SaveChanges()
Then your ChangeTracker code you showed above all of sudden starts working, as EF brought the data from DB first. It is however less efficient as you make and extra query.
I need the old/original value in post method. Finally this worked for me.
//Get Orignal value before save changes
Item entityBeforeChange = db.Items.Single(x => x.Id == item.Id);
db.Entry(entityBeforeChange).State = EntityState.Detached; // breaks up the connection to the Context
var locId = entityBeforeChange.LocationId;//Orignal value
//Saving the current Value
if (ModelState.IsValid)
{
db.Entry(item).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
You can get data that you haven't committed yet.
var Current = _dbContext.Entry(entity).GetDatabaseValues().ToObject();