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();
Related
I believe this is asked somewhere else but I can't find straight solution.
My Api is passing object model and on the server side every value of that object which is not passed is considered null (makes sense).
Is there way I can tell EF6 not to update entity with null values from passed object in manner I don't have to write each property and check if it's null.
Pseudo code
API
Update(int id, TaskEntity obj)
{
unitOfWork.Tasks.Update(id, userTask);
...
unitOfWork.Save()
}
Repo update
Update(int id, T entity)
{
var existingRecord = Get(id); //Gets entity from db based on passed id
if (existingRecord != null)
{
var attachedEntry = Context.Entry(existingRecord);
attachedEntry.CurrentValues.SetValues(entity);
}
}
My problem is that any data with null values will actually rewrite existing db record value with nulls.
Please point me to a solution or article where this is solved. Should I go reflections, maybe automapper could handle this (it's not its purpose i believe), or some kind of helper method should be written, as my objects can contain sub object.
Thank you in advance.
You can do something like this
Update(int id, T entity,string[] excludedFields)
{
var existingRecord = Get(id); //Gets entity from db based on passed id
if (existingRecord != null)
{
var attachedEntry = Context.Entry(existingRecord);
attachedEntry.CurrentValues.SetValues(entity);
for(var field in excludedFields)
{
attachedEntry.Property(field).IsModified = false;
}
}
}
some scenaries requires you to update part of the object and sometimes other parts, the best way in my opinion is to pass the fields to exclude from the update
hope it will help you
Personally not a big fan of hitting database and doing a get operation before doing an update. May be while doing the ajax call, you can send a list of properties which you should update (so that the scenario where updating to null values (erasing existing ones) will also be handled).
I'm doing small modifications to what #Hadi Hassan has done (without hitting database for getting the entity):
Update(T entity,string[] includedFields)
{
var existingRecord = Context.Attach(entity); // assuming primary key (id) will be there in this entity
var attachedEntry = Context.Entry(existingRecord);
for(var field in includedFields)
{
attachedEntry.Property(field).IsModified = true;
}
}
Note - attachedEntry.Property(field).IsModified will not work for related entities
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();
}
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.
I'm having a very weird bug, that only seems to happen on a particular item I'm trying to update in the DB, using entity framework.
Here is my code:
var shoppingCartItem = GetShoppingCartItemById(shoppingCartItemId);
shoppingCartItem.Quantity = newQuantity;
_context.SaveChanges();
and this is the method that gets the item from the DB before updateing:
public ShoppingCartItem GetShoppingCartItemById(int shoppingCartItemId)
{
if (shoppingCartItemId == 0)
return null;
var query = from sci in _context.ShoppingCartItems
where sci.ShoppingCartItemId == shoppingCartItemId
select sci;
var shoppingCartItem = query.SingleOrDefault();
return shoppingCartItem;
}
This code works on some items, but doesn't on a particular one I'm testing with now. If I check the state of the object (using _context.ObjectStateManager.GetObjectStateEntry(shoppingCartItem)) after changing the property Quantity it's Unchanged :(
Has this ever happens to any of you? Any idea why this happens, or what could be causing this?
I have had a similar problem while passing the data object to a view, then getting it back again - in this case it breaks the link to the datacontext. this shouldnt happen in this case, but setting the EntityState to Modified and re-attaching it worked for me, it may be worth trying for you.
var shoppingCartItem = GetShoppingCartItemById(shoppingCartItemId);
shoppingCartItem.Quantity = newQuantity;
_context.Entry(shoppingCartItem).State = System.Data.EntityState.Modified;
_context.SaveChanges();
Note however that this only works with EF4.1 - I had to update to 4.1 just to get an easy way to edit items!
I am thinking about how to use Linq in the classic 3-tier archetecture of .net project. Apprently, Linq to SQL should appear in Data tier. The reason I choose Linq is because it will save me much time on code than using store procedure. I did some search on line about the insert/update/delete method of Linq, but didn't find an appropriate method for record update using entities. Usually, people will do update using this way:
public void UpdateUser(String username, String password, int userId)
{
using (var db = new UserDataContext()){
var user = db.user.Single(p => p.Id = userId);
user.Username = username;
user.Password = password;
db.SubmitChanges();
}
}
Why we don't use entity to pass the record like this:
public void Update(Application info)
{
VettingDataContext dc = new VettingDataContext(_connString);
var query = (from a in dc.Applications
where a.Id==info.Id
select a).First();
query = info;
try{
dc.SubmitChanges();
}
catch(Exception e){
//...
}
}
But unfortunately, the above code is wrong because of "query=info", but if I assign each value from "info" to "query", it works fine. like
query.firstName=info.firstName;
query.lastName=info.lastName;
So if this table have 40 fields, I have to write 40 lines code. Is there any easier way to do the update? Hope I describe this issue clearly.
Adding another answer as a comment was not sufficient to expand on my previous answer.
Lets take a step back and look at what you want to do here from a logical perspective. You want to tell your data access layer how it should update the database, with all the new/changed values it needs to write.
One very common way of doing this is to pass an entity which has those changes (which is what you're doing in your example). This can become tricky, as you have seen, because if you simply overwrite the entity variable with the changed entity, Linq2Sql will lose change tracking... just because the new entity is assigned to the same variable, doesn't mean that Linq2Sql automatically picks up changes from the new object... in fact Linq2Sql has no knowledge of the new object at all...
Example:
// In domain layer:
MyEntity entity = new MyEntity();
entity.PrimaryKey = 10;
entity.Name = "Toby Larone";
entity.Age = 27;
myDataRepository.Update(entity);
// In data layer:
void Update(MyEntity changedEntity)
{
using (var db = new DataContext())
{
var entity = (from e in db.MyEntities
where e.PrimaryKey == changedEntity.PrimaryKey
select e).First();
// Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
entity = changedEntity;
// Linq2Sql does **not** have change tracking of changedEntity - the fact that it has been assigned to the same variable that once stored a tracked entity does not mean that Linq2Sql will magically pick up the changes...
db.SubmitChanges(); // Nothing happens - as far as Linq2Sql is concerned, the entity that was selected in the first query has not been changed (only the variable in this scope has been changed to reference a different entity).
}
}
Now you've already seen that assigning each field to the entity rather than replacing it works as intended - this is because the changes are being made to the original entity, which is still inside the Linq2Sql change tracking system..
One possible solution to this problem would be to write a method that "applies" the changes of another Entity to an existing one, ie:
partial class MyEntity
{
void ApplyChanges(MyEntity changedEntity)
{
this.PrimaryKey = changeEntity.PrimaryKey;
this.Name = changedEntity.Name;
this.Age = changedEntity.Age;
}
}
and then your data access would look like this:
// In data layer:
void Update(MyEntity changedEntity)
{
using (var db = new DataContext())
{
var entity = (from e in db.MyEntities
where e.PrimaryKey == changedEntity.PrimaryKey
select e).First();
// Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
entity.ApplyChanges(changedEntity);
db.SubmitChanges(); // Works OK...
}
}
But im sure you don't like this solution - because all you have done is effectively move the repetitive field assignment out of the repository and into the Entity class itself...
Going back to the logical perspective - all you really need to do is tell the data access repository 2 things - 1) which record you want to update and 2) what the changes are. Sending an entirely new entity which encapsulates those two requirements is not necessary to achieve that goal, in fact I think it's very inefficient.
In the following example, you are sending the data repository only the changes, not an entire entity. Becuase there is no entity, there are no change tracking issues to work around
Example:
// In domain layer:
myDataRepository.Update(10, entity =>
{
entity.Name = "Toby Larone";
entity.Age = 27;
});
// In data layer:
void Update(int primaryKey, Action<MyEntity> callback)
{
using (var db = new DataContext())
{
var entity = (from e in db.MyEntities
where e.PrimaryKey == primaryKey
select e).First();
// Linq2Sql now has change tracking of "entity"... any changes made will be persisted when SubmitChanges is called...
// The changes that were sent are being applied directly to the Linq2Sql entity, which is already under change tracking...
callback(entity);
db.SubmitChanges();
}
}
In the previous examples, the field assignments were happening twice - once when you described the changes you wanted to make, and again in the data repository when you needed to apply those changes to a Linq2Sql change tracked entity.
Using the callback, the field assignments only happen once - the description of the change itself is what updates the tracked entity.
I hope I explained this well enough :)
Think about what the data repository actually requires in order to perform the update. It does not require an object that contains those changes, but a description of what changes need to be made. This can be encapsulated easily into a callback delegate...
public void UpdateUser(int userId, Action<User> callback)
{
using (var db = new DataContext())
{
User entity = db.Users.Where(u => u.Id == userId).Single();
callback(entity);
db.SubmitChanges();
}
}
myrepository.UpdateUser(userId, user =>
{
user.Username = username;
user.Password = password;
// etc...
});
query is not the same type as info. They may have the same properties to you, but the code doesn't know that.
Now, if you want to avoid writing a bunch of unnecesary code, you can use a third party library like AutoMapper which can do that for you.