Entity Framework 4 - checking to see if an entity is attached - c#

In NHibernate we override Equals() and GetHashCode() to calculate entity equality.
Is the same necessary in Entity Framework?
The reason I ask is we are using EF4 with POCOs and are in the process of implementing a caching layer. The problem we have is when checking to see if an item is already attached to the object context. Currently this evaluates to false, even when the entity already exists in the current object context.

Maybe this code will give you some ideas. Im not using POCOs, but common points are the same I think.
Here is sample Update method which checks if Context has entity attached before performing updating routine.
public T Update(T entity)
{
if (entity == null) throw new ArgumentNullException("entity");
var key = ObjectContext.CreateEntityKey(ObjectContext.GetEntitySet<T>().Name, entity);
if (ObjectContext.IsAttached(key))
{
ObjectContext.ApplyCurrentValues(key.EntitySetName, entity);
}
else
{
ObjectContext.AttachTo(ObjectContext.GetEntitySet<T>().Name, entity);
ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
return entity;
}
internal static EntitySetBase GetEntitySet<TEntity>(this ObjectContext context)
{
var container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
var baseType = GetBaseType(typeof(TEntity));
var entitySet = container.BaseEntitySets
.Where(item => item.ElementType.Name.Equals(baseType.Name))
.FirstOrDefault();
return entitySet;
}
internal static bool IsAttached(this ObjectContext context, EntityKey key)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
ObjectStateEntry entry;
if (context.ObjectStateManager.TryGetObjectStateEntry(key, out entry))
{
return (entry.State != EntityState.Detached);
}
return false;
}
private static Type GetBaseType(Type type)
{
var baseType = type.BaseType;
if (baseType != null && baseType != typeof(EntityObject))
{
return GetBaseType(type.BaseType);
}
return type;
}
Hope this can help you a bit=)

Related

EF Core change tracking - issue with original values and altered values

I have Net core API configured with .net core 2.0 and EF core 2.0. it contains repository pattern architecture.
Now, I am trying to implement Audit log for each save change using EF change tracker.
My Issue : Whenever I tries to add a log for edit/modification endpoint, the original value and current value remain same and it's newly updated value. so in that way I am not able to track the modification or a change.
Here is my ApplicationContext file where I have overridden save call.
public class ApplicationContext : DbContext
{
public ApplicationContext(DbContextOptions options) : base(options: options) { }
public DbSet<Item> Item { get; set; }
public DbSet<ChangeLog> ChangeLog { get; set; }
public override int SaveChanges()
{
var modifiedEntities = ChangeTracker.Entries();
foreach (var change in modifiedEntities)
{
var entityType = change.Entity.GetType().Name;
if (entityType == "LogItem")
continue;
if (change.State == EntityState.Modified)
{
foreach (var prop in change.OriginalValues.Properties)
{
var id = change.CurrentValues["Id"].ToString();
//here both originalValue and currentValue are same and it's newly updated value
var originalValue = change.OriginalValues[prop]?.ToString();
var currentValue = change.CurrentValues[prop]?.ToString();
if (originalValue != currentValue)
{
ChangeLog.Add(
new ChangeLog()
{
CreationDateTime = DateTime.Now,
CreationUserId = 1,
Log = $"Edited item named {prop.Name} in {entityType} Id {id}.",
OldValue = originalValue,
NewValue = currentValue,
TableName = entityType,
FieldName = prop.Name
}
);
}
}
}
}
return base.SaveChanges();
}
}
Here is my base repository.
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IFullAuditedEntity, new()
{
private readonly ApplicationContext context;
public EntityBaseRepository(ApplicationContext context)
{
this.context = context;
}
public virtual T GetSingle(int id) => context.Set<T>().AsNoTracking().FirstOrDefault(x => x.Id == id);
public virtual T Add(T entity) => Operations(entity: entity, state: EntityState.Added);
public virtual T Update(T entity) => Operations(entity: entity, state: EntityState.Modified);
public virtual T Delete(T entity) => Operations(entity: entity, state: EntityState.Deleted);
public virtual T Operations(T entity, EntityState state)
{
EntityEntry dbEntityEntry = context.Entry<T>(entity);
if (state == EntityState.Added)
{
entity.CreationDateTime = DateTime.UtcNow;
entity.CreationUserId = 1;
context.Set<T>().Add(entity);
dbEntityEntry.State = EntityState.Added;
}
else if (state == EntityState.Modified)
{
entity.LastModificationDateTime = DateTime.UtcNow;
entity.LastModificationUserId = 1;
//var local = context.Set<T>().Local.FirstOrDefault(entry => entry.Id.Equals(entity.Id));
//if (local != null)
//{
// context.Entry(local).State = EntityState.Detached;
//}
dbEntityEntry.State = EntityState.Modified;
}
else if (state == EntityState.Deleted)
{
entity.DeletionFlag = true;
entity.DeletionUserId = 1;
entity.DeletionDateTime = DateTime.UtcNow;
dbEntityEntry.State = EntityState.Modified;
}
return entity;
}
public virtual void Commit() => context.SaveChanges();
}
And Lastly my controller with end point for put.
[Produces("application/json")]
[Route("api/Item")]
public class ItemController : Controller
{
private readonly IItemRepository repository;
private readonly IChangeLogRepository changeLogRepository;
private readonly IMapper mapper;
public ItemController(IItemRepository repository, IChangeLogRepository _changeLogRepository, IMapper mapper)
{
this.repository = repository;
this.changeLogRepository = _changeLogRepository;
this.mapper = mapper;
}
[HttpPut]
public IActionResult Put([FromBody]ItemDto transactionItemDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (transactionItemDto.Id <= 0)
{
return new NotFoundResult();
}
Item item = repository.GetSingle(transactionItemDto.Id); //find entity first
if (item == null)
{
return new NotFoundResult();
}
//map all the properties and commit
var entity = mapper.Map<Item>(transactionItemDto);
var updatedItem = repository.Update(entity);
repository.Commit();
return new OkObjectResult(mapper.Map<Item, ItemDto>(source: updatedItem));
}
}
I am not sure where I am doing any mistake, I tried to check this case in SO, but no luck.
any help will be appreciated, thanks.
I am not using the repository pattern, but I have implemented a very similar audit log for EF Core 2.1. I loop through the list of changes that are being tracked by the entity framework change tracker, and log them.
What I have noticed is that when I want to update an entity, there are two ways to do it. One is that I read the existing entity from the database, reassign the variable, then save it. The second way is to simply create an object, attach it to the database context, and set the property I want to update to the modified state. When I do this, my auditing won't work for the original value, since the original value was never actually read from the database.
examples:
//auditing works fine
var myEntity = await db.MyEntity.FindAsync(entityId);
myEntity.Property = newValue;
await db.SaveChangesAsync();
//auditing can't track the old value
var myEntity = new MyEntity();
db.Attach(myEntity);
myEntity.Property = newValue;
await db.SaveChangesAsync();
Here is the important bit of my auditing code for example
foreach (var entity in db.ChangeTracker.Entries())
{
if(entity.State == EntityState.Detached || entity.State == EntityState.Unchanged)
{
continue;
}
var audits = new List<Audit>();
//the typeId is a string representing the primary keys of this entity.
//this will not be available for ADDED entities with generated primary keys, so we need to update those later
string typeId;
if (entity.State == EntityState.Added && entity.Properties.Any(prop => prop.Metadata.IsPrimaryKey() && prop.IsTemporary))
{
typeId = null;
}
else
{
var primaryKey = entity.Metadata.FindPrimaryKey();
typeId = string.Join(',', primaryKey.Properties.Select(prop => prop.PropertyInfo.GetValue(entity.Entity)));
}
//record an audit for each property of each entity that has been changed
foreach (var prop in entity.Properties)
{
//don't audit anything about primary keys (those can't change, and are already in the typeId)
if(prop.Metadata.IsPrimaryKey() && entity.Properties.Any(p => !p.Metadata.IsPrimaryKey()))
{
continue;
}
//ignore values that won't actually be written
if(entity.State != EntityState.Deleted && entity.State != EntityState.Added && prop.Metadata.AfterSaveBehavior != PropertySaveBehavior.Save)
{
continue;
}
//ignore values that won't actually be written
if (entity.State == EntityState.Added && prop.Metadata.BeforeSaveBehavior != PropertySaveBehavior.Save)
{
continue;
}
//ignore properties that didn't change
if(entity.State == EntityState.Modified && !prop.IsModified)
{
continue;
}
var audit = new Audit
{
Action = (int)entity.State,
TypeId = typeId,
ColumnName = prop.Metadata.SqlServer().ColumnName,
OldValue = (entity.State == EntityState.Added || entity.OriginalValues == null) ? null : JsonConvert.SerializeObject(prop.OriginalValue),
NewValue = entity.State == EntityState.Deleted ? null : JsonConvert.SerializeObject(prop.CurrentValue)
};
}
//Do something with audits
}
I think I see the issue with your code. In your controller:
//map all the properties and commit
var entity = mapper.Map<Item>(transactionItemDto);
var updatedItem = repository.Update(entity);
repository.Commit();
In that code you are taking your DTO and mapping it to a new instance of Item. That new instance of Item knows nothing of the current database values, that is why you are seeing the same new values for both OriginalValue and CurrentValue.
If you reuse the Item item variable that you get in this line:
Item item = repository.GetSingle(transactionItemDto.Id); //find entity first
Note, you'll need to get the entity with tracking on however, vs how your repository GetSingle does it with AsNoTracking. If you use that item (which now has the original/current database values) and map your transactionItemDto properties onto it like this:
var entityToUpdate = mapper.Map<ItemDto, Item>(transactionItemDto);
Then, when you call your repository.Update method passing it entityToUpdate, I believe you'll see the correct before/after values.
.
.
.
.
Old (wrong) answer I originally posted:
In your ApplicationContext code you have the following loop
foreach (var prop in change.OriginalValues.Properties)
I believe that is what is causing your original value/current values to be the same because you are looping over the original values properties. Try changing that loop to:
foreach (var prop in change.Properties)
Then, try reading the values off each property via the prop variable like so:
var currentValue = prop.CurrentValue;
var originalValue = prop.OriginalValue;
EDIT: Ah - I see now that in your code you are trying to read the original value from the change.OriginalValues collection, so I don't think this is going to help.
there are two approaches for this:
var entry = _dbContext.Attach(entity);
var updated = entry.CurrentValues.Clone();
entry.Reload();
entry.CurrentValues.SetValues(updated);
entry.State = EntityState.Modified;
db.SaveChanges();
Or Simply do:
var persondb = db.Persons.Find(person.id);
db.Entry(persondb).CurrentValues.SetValues(person);
db.SaveChanges();

Many-To-Many Generic Update Method with Entity Framework 6

I have a generic Update method for Entity Framework in an abstract DatabaseOperations<T,U> class:
public virtual void Update(T updatedObject, int key)
{
if (updatedObject == null)
{
return;
}
using (var databaseContext = new U())
{
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
databaseContext.Entry(foundEntity).CurrentValues.SetValues(updatedObject);
databaseContext.SaveChanges();
}
}
However, this does not handle many-to-many relationships.
This many-to-many update problem can be overcome by overriding the Update method in TrussSetDatabaseOperations : DatabaseOperations<TrussSet, TrussManagementDatabaseContext> to read as follows:
public override void Update(TrussSet updatedTrussSet, int key)
{
if (updatedTrussSet == null)
{
return;
}
using (var databaseContext = new TrussManagementDatabaseContext())
{
databaseContext.Database.Log = Console.Write;
TrussSet foundTrussSet = databaseContext.TrussSets.Find(key);
databaseContext.Entry(foundTrussSet).CurrentValues.SetValues(updatedTrussSet)
// Update the many-to-many relationship of TrussSets to Seals
databaseContext.Entry(foundTrussSet).Collection(trussSet => trussSet.Seals).Load();
databaseContext.Entry(foundTrussSet).Collection(trussSet => trussSet.Seals).CurrentValue = updatedTrussSet.Seals;
databaseContext.SaveChanges();
}
}
However, this overriding would proliferate through all the classes that inherit from DatabaseOperations and have a TrussSet object. Can I somehow inject the added two lines into the generic update method, so that the update method is given the collection properties, loads them, and applies the respective updated collection to that entity? Thanks in advance.
Looking at your code, the following comes to mind:
public virtual void Update(T updatedObject, int key, params string[] navigationProperties) {
if (updatedObject == null) {
return;
}
using (var databaseContext = new U()) {
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
var entry = databaseContext.Entry(foundEntity);
entry.CurrentValues.SetValues(updatedObject);
foreach (var prop in navigationProperties) {
var collection = entry.Collection(prop);
collection.Load();
collection.CurrentValue = typeof(T).GetProperty(prop).GetValue(updatedObject);
}
databaseContext.SaveChanges();
}
}
You can also use Expressions instead of strings (and then extract property names from those expressions) if you want more type-safety.
Update: here is what I mean by "use Expressions" in this case:
public virtual void Update(T updatedObject, int key, params Expression<Func<T, IEnumerable>>[] navigationProperties) {
if (updatedObject == null) {
return;
}
using (var databaseContext = new U()) {
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
var entry = databaseContext.Entry(foundEntity);
entry.CurrentValues.SetValues(updatedObject);
foreach (var prop in navigationProperties) {
string memberName;
var member = prop.Body as MemberExpression;
if (member != null)
memberName = member.Member.Name;
else throw new Exception("One of the navigationProperties is not a member access expression");
var collection = entry.Collection(memberName);
collection.Load();
collection.CurrentValue = typeof (T).GetProperty(memberName).GetValue(updatedObject);
}
databaseContext.SaveChanges();
}
}

Unable to create a constant value of type . Only primitive types or enumeration types are supported in this context

I have the following method for a generic check exist,
public virtual bool CheckExist(T entity)
{
var context = new eTRdataEntities();
IQueryable<T> dbQuery = context.Set<T>();
if (dbQuery.Any(e => e == entity))
{
return true;
}
return false;
}
However it returns the exception:
Unable to create a constant value of type . Only primitive types or enumeration types are supported in this context.
Please advice,
Many Thanks,
Try changing the code by taking in the type T next to the method name, like this:
public virtual bool CheckExist<T>(T entity)
{
var context = new eTRIKSdataEntities();
IQueryable<T> dbQuery = context.Set<T>();
if (dbQuery.Any(e => e == entity))
{
return true;
}
return false;
}
You may also want to restrict the type to classes only, like this:
public virtual bool CheckExist<T>(T entity) where T : class
{
var context = new eTRIKSdataEntities();
IQueryable<T> dbQuery = context.Set<T>();
if (dbQuery.Any(e => e == entity))
{
return true;
}
return false;
}

entity framework update one parent entity updates another parent entity

I'm new to entity framework. I have an SupplyItem entity
public class SupplyItem
{
public virtual int Id { get; set; }
public virtual BaseProduct Product
{
get { return product; }
set { product = value; }
}
public virtual Boolean IsPublic
{
get { return isPublic; }
set { isPublic = value; }
}
}
When I add an supplyitem object for the first time, it is adding a product and IsPublic attribute correctly. Like this, I added two objects for supplyitem entity, both objects are referencing to same product.
Now, I changed isPublic attribute for second object of supplyitem entity and updates the entity like this
UnitOfWork.Context.Entry(supplyItem1).State = EntityState.Modified;
Above code line, updates isPublic attribute correctly but it makes null Product for another object of supplyitem entity which is referencing to the same product.
I don't understand this behavior. Any pointer will be really helpful !
Thanks.
uh,when you update,you're not read from db,you new a SupplyItem and set id,set IsPublic,and save,so the object you new it,Product property is default (null)
when you do these,the whole object (every property) updated.
so if you want update parts of fields,you should do like this
var entry = db.Entry(entity);
if (entry.State == EntityState.Detached)
{
db.Set<TEntity>().Attach(entity);
}
entry.Property("propertyName").IsModified = true
the propertyName is a string ,it must be same as the property's Name
you can do this with a lamda
Update<TEntity>(TEntity entity, params Expression<Func<TEntity, object>>[] updateProperty)
so in the function,you need find the propetyname from expression
public static string GetExpressionText<TModel, TProperty>(Expression<Func<TModel, TProperty>> ex)
{
if (ex.Body.NodeType == ExpressionType.MemberAccess)
{
var memExp = ex.Body as MemberExpression;
if (memExp != null)
{
return memExp.Member.Name;
}
}
else if (ex.Body.NodeType == ExpressionType.Convert)
{
var exp = ex.Body as UnaryExpression;
return GetExpressionText(exp);
}
return string.Empty;
}
public static string GetExpressionText(UnaryExpression exp)
{
if (exp != null)
{
if (exp.Operand.NodeType == ExpressionType.MemberAccess)
{
var memExp = exp.Operand as MemberExpression;
if (memExp != null)
{
return memExp.Member.Name;
}
}
}
return string.Empty;
}
hope these code can help you

Identity Generation error with EF5

In my DbContext, I have an override of the SaveChanges method to generate a Guid for entities to be added to the Database. This works fine except in the case of a shared primary key.
It generates different Id instead of one to be shared by the entities. Is there a way to check the type of Relationship?
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(e => e.State == EntityState.Added))
{
var t = entry.Entity.GetType();
if (t.GetProperty("Id") == null)
continue;
var info = t.GetProperty("Id").GetCustomAttributes(typeof (DatabaseGeneratedAttribute), true).Cast<DatabaseGeneratedAttribute>();
if (!info.Any() || info.Single().DatabaseGeneratedOption != DatabaseGeneratedOption.Identity)
{
if (t.GetProperty("Id").PropertyType == typeof(Guid) && (Guid)t.GetProperty("Id").GetValue(entry.Entity, null) == Guid.Empty)
t.GetProperty("Id").SetValue(entry.Entity, Guid.NewGuid(), null);
}
}
return base.SaveChanges();
}

Categories