Updating Parent Entity doesn't Update Child Entity - c#

Whether attaching parent entity to the context again and updating it should update the child entity? Am i missing something?
Or should I necessarily write the updating EF logic (in DAL) for the child entity?
This is my sample code model:
ChildEntity entityChild;
if (ParentEntity.ChildEntity.SingleOrDefault() != null)
entityChild = ParentEntity.ChildEntity.SingleOrDefault();
else
{
entityChild = new ChildEntity();
ParentEntity.ChildEntity.Add(entityChild);
}
entityChild.ColumnA= txtA.Text;
entityChild.ColumnB= txtB.Text;
// Send entityParent for update
_objParent.Update(entityParent)
_objParent.Update() code:
context.vouchers.Attach(entityParent);
ObjectStateEntry objectState = context.ObjectStateManager.GetObjectStateEntry(entityParent);
objectState.ChangeState(System.Data.EntityState.Modified);
context.SaveChanges();
UPDATE (Parent Loading Code Sample)
public ParentEntity GetById(int id)
{
using (var context = new DBEntities())
{
ParentEntity _entity = context.ParentEntity
.Include("ChildEntity")
.Where(e => e.parent_id == id);
return (ParentEntity)_entity.SingleOrDefault()
}
}

EF only track changes from the point where it gets to know about the object. The most simple way to work with updates in EF is to load the existing object from the DB, update it and then save it back. In this case it means that you should load the ParentEntity object from the DB.
The other approach is to use Attach as you do. In that case you should first call attach the unchanged ParentEntity and then call ParentEntity.ChildEntity.Add(entityChild).
Another alternative is to explicitly add the new ChildEntity directly to the DbContext, then you could simply set the foreign key value of the ChildEntity to the key value of the ParentEntity to make them connected.

You should load and update your entities inside the using statement - this way all the changes will be tracked by the Entity Framework:
using (var context = new DBEntities())
{
// load and update entities
// ....
context.SaveChanges();
}
UPDATE - This is just an example, I'll keep it simple. I would create a service where I'd put my logic - something like this:
public class ParentService
{
// other service methods here
protected DBEntities CreateContext()
{
return new DBEntities();
}
public ParentEntity Update(int id, string columnA, string columnB)
{
ParentEntity _entity = null;
using (var context = CreateContext())
{
bool isNew = false;
_entity = context.ParentEntity
.Include("ChildEntity")
.SingleOrDefault(e => e.parent_id == id);
ChildEntity entityChild = ParentEntity.ChildEntity.SingleOrDefault();
if(entityChild == null)
{
entityChild = new ChildEntity();
isNew = true;
}
entityChild.ColumnA = columnA;
entityChild.ColumnB = columnB;
if(isNew)
{
ParentEntity.ChildEntity.Add(entityChild);
}
context.SaveChanges();
}
return _entity;
}
}
In the UI code:
string id = .....;
string columnA= txtA.Text;
strign columnB = txtB.Text;
var service = new ParentService();
ParentEntity parent = service.Update(id, columnA, columnB);

Related

Update Navigation Property in Entity Framework

I am a new at depth of the Entity Framework
I have just wondered why Entity Framework doesn't save changes especially the navigation property although all other properties are already updated
Please I want simple explanation
This is My Service Class
public class ProductsService
{
AppDbContext _Context;
public ProductsService()
{
_Context = new AppDbContext();
}
public Product GetProduct(int id)
{
return _Context.Products.Include(p=>p.Category).Where(pro =>pro.Id == id).SingleOrDefault();
}
public void UpdateProduct(Product product)
{
_Context.Entry(product).State = System.Data.Entity.EntityState.Modified;
_Context.SaveChanges();
}
}
In Controller:
[HttpPost]
public ActionResult Edit(NewCategoryViewModel pro,int Id)
{
CategoriesService ser = new CategoriesService();
var NewProduct = ProService.GetProduct(Id);
var NewCat = ser.GetCategory(pro.CategoryId);
NewProduct.Description = pro.Description;
NewProduct.Name = pro.Name;
NewProduct.Price = pro.Price;
NewProduct.Category = NewCat;
ProService.UpdateCategory(NewProduct);
return RedirectToAction("ProductTable");
}
I have tried this and it works fine
[HttpPost]
public ActionResult Edit(NewCategoryViewModel pro,int Id)
{
using (var Context = new AppDbContext())
{
var NewProd = Context.Products.FirstOrDefault(pr => pr.Id == Id);
var Cat = Context.Categories.FirstOrDefault(cat => cat.Id == pro.CategoryId);
Context.Entry(NewProd).State = EntityState.Modified;
NewProd.Name = pro.Name;
NewProd.Description = pro.Description;
NewProd.Price = pro.Price;
NewProd.Category = Cat;
Context.SaveChanges();
}
}
and for UpdateCategory
public void UpdateCategory(Category category)
{
using (var Context = new AppDbContext())
{
Context.Entry(category).State = System.Data.Entity.EntityState.Modified;
Context.SaveChanges();
}
}
Why the first one Not work
I know may be the problem in the state of the navigation property
Since you created the DbContext inside ProductService and you created a new Context inside:
public void UpdateCategory(Category category)
{
using (var Context = new AppDbContext())
{
Context.Entry(category).State = System.Data.Entity.EntityState.Modified;
Context.SaveChanges();
}
}
-> you use two different DbContext's together (which can cause problems with change tracking)!
Solution:
Try to use DependencyInjection for all DbContext's instead of creating them locally to prevent problems with change tracking.
You might consider using .add() instead of .entry().
.add() will also track other reachable entities.
documentation can be found here:
entity framework

EntityFramework Core: Get changes occured in entity and related data

I want to get changes occured in an entity and related datas attached to it.
I know how to get the property names that have changed in one entity:
dbContext.Entry(entity).Properties.Where(x => x.IsModified).Select(x => x.Metadata.Name).ToList();
How to do the same for related data in navigation properties ?
Based on this article (Entity Change Tracking using DbContext in Entity Framework 6), you should override SaveChanges() method to track entity changes and its related entities.
public override int SaveChanges()
{
return base.SaveChanges();
}
Actually, You should change the above code to the following sample:
public override int SaveChanges()
{
var modifiedEntities = ChangeTracker.Entries()
.Where(p => p.State == EntityState.Modified).ToList();
var now = DateTime.UtcNow;
foreach (var change in modifiedEntities)
{
var entityName = change.Entity.GetType().Name;
var primaryKey = GetPrimaryKeyValue(change);
foreach(var prop in change.OriginalValues.PropertyNames)
{
var originalValue = change.OriginalValues[prop].ToString();
var currentValue = change.CurrentValues[prop].ToString();
if (originalValue != currentValue) //Only create a log if the value changes
{
//Create the Change Log
}
}
}
return base.SaveChanges();
}

How to update a single property in Entity Framework Core

I need to update only one or two properties of an Entity. In other words, I have an entity with an Id, ParentId, Name, and Description.
The issue is that when the name is updated, the Description is wiped out if it already existed in the database.
This is the code:
internal void Update(ItemInfo itemInfo)
{
var item = new Item { Id = itemInfo.Id, ParentId = itemInfo.ParentId, Name = itemInfo.Name };
var entry = this.DbContext.Items.Attach(item);
if (item.ParentId != null) entry.Property(x => x.ParentId).IsModified = true;
if (!(String.IsNullOrWhiteSpace(item.Name))) entry.Property(x => x.Name).IsModified = true;
this.SaveChanges();;
}
I thought that since I was setting the particular property as modified, only that property would be updated.
Or should I be getting the entity from the database first, and then just setting the property and saving. I wanted to avoid two trips to the database for a single save.
You can do following in order to update a single property:
internal void Update(ItemInfo itemInfo)
{
if (itemInfo == null) { throw new Exception($"item info was not supplied"); }
var itemInfoEntity = new ItemInfo()
{
Id = itemInfo.Id,
ParentId = itemInfo.ParentId,
Name = itemInfo.Name
};
dbContext.ItemInfo.Attach(itemInfoEntity);
dbContext.Entry(itemInfoEntity).Property(x => x.Id).IsModified = true;
dbContext.Entry(itemInfoEntity).Property(x => x.ParentId).IsModified = true;
dbContext.Entry(itemInfoEntity).Property(x => x.Name).IsModified = true;
dbContext.SaveChanges();
}
But if you only are updating few properties, then I think you should not send the whole object as parameter, just send the properties that needs to be updated something like this:
internal void Update(id, parentId, name)
{
...
}
The thing you're really after is Concurrency Handling. When multiple users are editting the same object at the same time, the second user will overwrite the changes of the first user.
Or should I be getting the entity from the database first, and then just setting the property and saving.
Yes, but having a controller method for each property of your object would be very tedious. That's why concurrency handling is the best option.
Also, your domain model should be entirely seperated from your database model. Never use entities in your web application directly. You are already doing so by having the Item entity (database model) and the ItemInfo class (domain model, used to handle the post-request).
Implement concurrency handling
First add a Timestamp column to your entity:
internal class Item
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
[Timestamp]
[ConcurrencyCheck]
public byte[] ConcurrencyStamp { get; set; }
}
Then, at the place where you update your entity:
[Controller]
public class ItemController : Controller
{
private readonly DbContext dbContext;
public ItemController(DbContext dbContext)
{
this.dbContext = dbContext;
}
[HttpPost]
//[Authorize]
//[ValidateAntiForgeryToken]
public async Task<ActionResult<ItemInfo>> Update([FromBody] ItemInfo item)
{
var existingItem = dbContext.Items.SingleOrDefaultAsync(i => i.Id == item.Id);
if (Convert.ToBase64String(existingItem.ConcurrencyStamp) != item.ConcurrencyStamp)
{
var databaseValue = new ItemInfo
{
Id = existingItem.Id,
ParentId = existingItem.ParentId,
Name = existingItem.Name,
};
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status409Conflict, databaseValue);
}
// Set new properties
existingItem.Id = item.Id;
existingItem.ParentId = item.ParentId;
existingItem.Name = item.Name;
// Save changes
await dbContext.SaveChangesAsync();
// Now return the updated item
// Now you should map the entity properties back to a new domain model...
var result = new ItemInfo
{
Id = existingItem.Id,
ParentId = existingItem.ParentId,
Name = existingItem.Name,
ConcurrencyStamp = Convert.ToBase64String(existingItem.ConcurrencyStamp),
};
return Ok(item);
}
}
Now when you try to update your item from the client-side, and receive a 409Conflict statuscode, you should decide how you want to handle this. I've chosen to display the database values below the respective input boxes.
You can find an entire implementation of this here

EntityFramework Update Item by another Item

I have this Code:
public static void SaveItem(Item itemFrom)
{
using (myEntitites ctx = new myEntitites())
{
Item itemTo = ctx.Items.First(x => x.ID = itemFrom.ID);
itemTo.Property1 = itemFrom.Property1;
itemTo.Property2 = itemFrom.Property2;
itemTo.Property3 = itemFrom.Property3;
//..lot of properties
ctx.SaveChanges();
}
}
I'm wondering whether there is a way to update an item without assigning each property.
itemFrom is an updated version of itemTo.
You can manually attach the item to the context without getting an object from the database.
Entity Framework will update the correct row by using the primary key defined in the model.
public static void SaveItem(Item itemFrom)
{
using (myEntitites ctx = new myEntitites())
{
ctx.Items.Attach(itemFrom);
ctx.Entry(itemFrom).State = EntityState.Modified;
ctx.SaveChanges();
}
}

How can I log all entities change, during .SaveChanges() using EF code first?

I'm using EF code first. I'm using a base Repository for all my repositories and an IUnitofWork that inject to the repositories, too:
public interface IUnitOfWork : IDisposable
{
IDbSet<TEntity> Set<TEntity>() where TEntity : class;
int SaveChanges();
}
public class BaseRepository<T> where T : class
{
protected readonly DbContext _dbContext;
protected readonly IDbSet<T> _dbSet;
public BaseRepository(IUnitOfWork uow)
{
_dbContext = (DbContext)uow;
_dbSet = uow.Set<T>();
}
//other methods
}
e.g my OrderRepository is like this:
class OrderRepository: BaseRepository<Order>
{
IUnitOfWork _uow;
IDbSet<Order> _order;
public OrderRepository(IUnitOfWork uow)
: base(uow)
{
_uow = uow;
_order = _uow.Set<Order>();
}
//other methods
}
And I use it in this way:
public void Save(Order order)
{
using (IUnitOfWork uow = new MyDBContext())
{
OrderRepository repository = new OrderRepository(uow);
try
{
repository.ApplyChanges<Order>(order);
uow.SaveChanges();
}
}
}
Is there any way to log change histories of all entities(include their navigation properties) during .SaveChanges()? I want to log original values(before save occurs) and changed values(after save occurs).
You can get the before and after values for all changed entities by going through DbContext.ChangeTracker. Unfortunately the API is a little verbose:
var changeInfo = context.ChangeTracker.Entries()
.Where (t => t.State == EntityState.Modified)
.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]),
});
You can modify that to include things like the type of the entity if you need that for your logging. There is also a ToObject() method on the DbPropertyValues (the type of OriginalValues and CurrentValues) you could call if you already have a way to log whole objects, although the objects returned from that method will not have their navigation properties populated.
You can also modify that code to get all entities in the context by taking out the Where clause, if that makes more sense given your requirements.
I have overridded the default SaveChanges method to log changes for add/update/delete in entity. Though it does not cover navigation property changes.
Based on this article: Using entity framework for auditing
public int SaveChanges(string userId)
{
int objectsCount;
List<DbEntityEntry> newEntities = new List<DbEntityEntry>();
// Get all Added/Deleted/Modified entities (not Unmodified or Detached)
foreach (var entry in this.ChangeTracker.Entries().Where
(x => (x.State == System.Data.EntityState.Added) ||
(x.State == System.Data.EntityState.Deleted) ||
(x.State == System.Data.EntityState.Modified)))
{
if (entry.State == System.Data.EntityState.Added)
{
newEntities.Add(entry);
}
else
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog changeDescription in GetAuditRecordsForEntity(entry, userId))
{
this.AuditLogs.Add(changeDescription);
}
}
}
// Default save changes call to actually save changes to the database
objectsCount = base.SaveChanges();
// We don't have recordId for insert statements that's why we need to call this method again.
foreach (var entry in newEntities)
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog changeDescription in GetAuditRecordsForEntity(entry, userId, true))
{
this.AuditLogs.Add(changeDescription);
}
// TODO: Think about performance here. We are calling db twice for one insertion.
objectsCount += base.SaveChanges();
}
return objectsCount;
}
#endregion
#region Helper Methods
/// <summary>
/// Helper method to create record description for Audit table based on operation done on dbEntity
/// - Insert, Delete, Update
/// </summary>
/// <param name="dbEntity"></param>
/// <param name="userId"></param>
/// <returns></returns>
private List<AuditLog> GetAuditRecordsForEntity(DbEntityEntry dbEntity, string userId, bool insertSpecial = false)
{
List<AuditLog> changesCollection = new List<AuditLog>();
DateTime changeTime = DateTime.Now;
// Get Entity Type Name.
string tableName1 = dbEntity.GetTableName();
// http://stackoverflow.com/questions/2281972/how-to-get-a-list-of-properties-with-a-given-attribute
// Get primary key value (If we have more than one key column, this will need to be adjusted)
string primaryKeyName = dbEntity.GetAuditRecordKeyName();
int primaryKeyId = 0;
object primaryKeyValue;
if (dbEntity.State == System.Data.EntityState.Added || insertSpecial)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName, true);
if(primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
// For Inserts, just add the whole record
// If the dbEntity implements IDescribableEntity,
// use the description from Describe(), otherwise use ToString()
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_ADD,
TableName = tableName1,
RecordId = primaryKeyId, // Again, adjust this if you have a multi-column key
ColumnName = "ALL", // To show all column names have been changed
NewValue = (dbEntity.CurrentValues.ToObject() is IAuditableEntity) ?
(dbEntity.CurrentValues.ToObject() as IAuditableEntity).Describe() :
dbEntity.CurrentValues.ToObject().ToString()
}
);
}
else if (dbEntity.State == System.Data.EntityState.Deleted)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName);
if (primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
// With deletes use whole record and get description from Describe() or ToString()
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_DELETE,
TableName = tableName1,
RecordId = primaryKeyId,
ColumnName = "ALL",
OriginalValue = (dbEntity.OriginalValues.ToObject() is IAuditableEntity) ?
(dbEntity.OriginalValues.ToObject() as IAuditableEntity).Describe() :
dbEntity.OriginalValues.ToObject().ToString()
});
}
else if (dbEntity.State == System.Data.EntityState.Modified)
{
primaryKeyValue = dbEntity.GetPropertyValue(primaryKeyName);
if (primaryKeyValue != null)
{
Int32.TryParse(primaryKeyValue.ToString(), out primaryKeyId);
}
foreach (string propertyName in dbEntity.OriginalValues.PropertyNames)
{
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntity.OriginalValues.GetValue<object>(propertyName),
dbEntity.CurrentValues.GetValue<object>(propertyName)))
{
changesCollection.Add(new AuditLog()
{
UserId = userId,
EventDate = changeTime,
EventType = ModelConstants.UPDATE_TYPE_MODIFY,
TableName = tableName1,
RecordId = primaryKeyId,
ColumnName = propertyName,
OriginalValue = dbEntity.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntity.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntity.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntity.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
}
// Otherwise, don't do anything, we don't care about Unchanged or Detached entities
return changesCollection;
}
you have scared people away with the extra requirement
Include their navigation properties
This is simply a non trivial exercise.
And if this is important, you should manage/track changes across references with code.
this is a sample covering this topic
Undo changes in entity framework entities
There is a sample doing close top what you want here
undo changes
It can easily be converted to load before and after images elsewhere.
Given the ObjectState entry after DetectChanges is called, you can implement a simple entity by entity option. and per UOW. But the navigation / references version makes this very complex as you worded the requirement.
EDIT : How to access the changeList
public class Repository<TPoco>{
/....
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public virtual IList<ChangePair> GetChanges(object poco) {
var changes = new List<ObjectPair>();
var thePoco = (TPoco) poco;
foreach (var propName in Entry(thePoco).CurrentValues.PropertyNames) {
var curr = Entry(thePoco).CurrentValues[propName];
var orig = Entry(thePoco).OriginalValues[propName];
if (curr != null && orig != null) {
if (curr.Equals(orig)) {
continue;
}
}
if (curr == null && orig == null) {
continue;
}
var aChangePair = new ChangePair {Key = propName, Current = curr, Original = orig};
changes.Add(aChangePair);
}
return changes;
}
///... partial repository shown
}
// FYI the simple return structure
public class ChangePair {
public string Key { get; set; }
public object Original { get; set; }
public object Current { get; set; }
}
DbContext has ChangeTracker property.
You can override .SaveChanges() in your context and log changes.
I don't think that entity framework can do it for you. Probably, you must detect changes directly in your model classes.
I've expanded on Steve's answer to provide a check for Changed, Added, and Deleted entities and print them in a sensible way.
(My use case is to ensure there are no unsaved changes before disposing of a DbContext instance, but this check could be done at any point)
/// <summary>Helper method that checks whether the DbContext had any unsaved changes before it was disposed.</summary>
private void CheckForUnsavedChanges(DbContext dbContext)
{
try
{
List<DbEntityEntry> changedEntityEntries = dbContext.ChangeTracker.Entries()
.Where(t => t.State != EntityState.Unchanged && t.State != EntityState.Detached).ToList();
if (!changedEntityEntries.Any())
return;
throw new Exception("Detected that there were unsaved changes made using a DbContext. This could be due to a missing call to `.SaveChanges()` or possibly " +
"some read-only operations that modified the returned entities (in which case you might wish to use `.AsNoTracking()` in your query). Changes:\n " +
String.Join("\n ", changedEntityEntries.Select(entry => $"{entry.Entity.GetType()} {entry.State}:\n " + String.Join("\n ",
entry.State == EntityState.Modified ? entry.CurrentValues.PropertyNames
// Only output properties whose values have changed (and hope they have a good ToString() implementation)
.Where(pn => entry.OriginalValues?[pn] != entry.CurrentValues[pn])
.Select(pn => $"{pn} ({entry.OriginalValues?[pn]} -> {entry.CurrentValues[pn]})") :
// Added or Deleted entities are output in their entirety
entry.State == EntityState.Added ? entry.CurrentValues.PropertyNames.Select(pn => $"{pn} = {entry.CurrentValues[pn]}") :
/* entry.State == EntityState.Deleted ? */ entry.CurrentValues.PropertyNames.Select(pn => $"{pn} = {entry.OriginalValues[pn]}")))));
}
catch (Exception ex)
{
_logger.Error("Issue encountered when checking for unsaved changes.", ex);
}
}

Categories

Resources