Entity Framework validate external entity is not modified - c#

I'm trying to validate an entity coming from an External context has not changed.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
I have a method which takes in an entity which has not been loaded from the context.
public bool Validate(Employee employee)
{
using (var context = new Context())
{
return context.Entry(employee).State == EntityState.Modified;
}
}
I would like to attach and verify that the attached entity is not modified from whats in the database.
I would prefer not to manually have to iterate of the properties. Is there a way to hack around this?

No need to attach the external entity. You can use the external entity to set values of the database entity and then check the state of the latter:
public bool Validate(Employee externalEmployee)
{
using var context = new Context(); // C# 8.0
var dbEntity = context.Where(x => x.Id == externalEmployee.Id).SingleOrDefault();
if (dbEntity != null)
{
context.Entry(dbEntity).CurrentValues.SetValues(externalEmployee);
return context.Entry(dbEntity).State == EntityState.Modified;
}
return false; // Or true, depending on your semantics.
}

You can try:
public static List<string> GetChanges<T>(this T obj, T dbObj)
{
List<string> result = new List<string>();
var type = typeof(T);
foreach (var prop in type.GetProperties())
{
var newValue = prop.GetValue(obj, null);
var dbValue = prop.GetValue(dbObj, null);
if(newValue == null && dbValue != null)
{
result.Add(prop.Name);
continue;
}
if (newValue != null && dbValue == null)
{
result.Add(prop.Name);
continue;
}
if (newValue == null && dbValue == null)
continue;
if (!newValue.ToString().Equals(dbValue.ToString()))
result.Add(prop.Name);
}
return result;
}
if resultList.Count > 0, your object has changes.
In your Validate Method:
public bool Validate(Employee employee)
{
using (var context = new Context())
{
Employee dbEmployee = context.Employee.Find(employee.Id);
if(employee.GetChanges(dbEmployee).Count > 0)
return true;
return false;
}
}
It's a god workaround =D
Works for me!

Related

Ignore null values patch ef core

Updating records using EF, but it is not updating accordingly. I just want to update only the fields that are not null and modified. In previous I'm doing like this:
_context.Attach(exist).CurrentValues.SetValues(t);
which it will update all the fields and I don't want that.
Is there anything that I missed on my code?
public virtual async Task<T> UpdateAsync(T t, object key)
{
if (t == null)
return null;
T exist = await _context.Set<T>().FindAsync(key);
if (exist != null)
{
// _context.Attach(exist).CurrentValues.SetValues(t);
_context.Attach(exist);
var entry = _context.Entry(t);
Type type = typeof(T);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(t, null) == null)
{
entry.Property(property.Name).IsModified = false;
}
}
// _context.Attach(exist).CurrentValues.SetValues(t);
await _context.SaveChangesAsync();
}
return exist;
}
I am using something like this
public virtual async Task<T> UpdateAsync(T t, object key)
{
if (t == null)
return null;
T exist = await _context.Set<T>().FindAsync(key);
if (exist != null)
{
_context.Entry(exist).CurrentValues.SetValues(t);
var result = await Context.SaveChangesAsync();
if (result == 0) return null
}
return exist;
}

System.Threading.Thread.CurrentPrincipal is null on SaveChanges

So, we created this project: https://github.com/efonsecab/BlazorRestaurant
The EF Core logic for SaveChanges is extended to automatically fill the data for the auditing columns.
Logic is in the BlazorRestaurantDbContext.partial.cs file.
public partial class BlazorRestaurantDbContext
{
public override int SaveChanges()
{
ValidateAndSetDefaults();
return base.SaveChanges();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
ValidateAndSetDefaults();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
ValidateAndSetDefaults();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ValidateAndSetDefaults();
return base.SaveChangesAsync(cancellationToken);
}
private void ValidateAndSetDefaults()
{
//Check https://www.bricelam.net/2016/12/13/validation-in-efcore.html
var entities = from e in ChangeTracker.Entries()
where e.State == EntityState.Added
|| e.State == EntityState.Modified
select e.Entity;
string ipAddresses = String.Empty;
string assemblyFullName = String.Empty;
string rowCretionUser = String.Empty;
if (entities.Any(p => p is IOriginatorInfo))
{
ipAddresses = String.Join(",", GetCurrentHostIPv4Addresses());
assemblyFullName = System.Reflection.Assembly.GetEntryAssembly().FullName;
if (Thread.CurrentPrincipal != null && Thread.CurrentPrincipal.Identity != null)
rowCretionUser = Thread.CurrentPrincipal.Identity.Name;
else
rowCretionUser = "Unknown";
}
foreach (var entity in entities)
{
if (entity is IOriginatorInfo)
{
IOriginatorInfo entityWithOriginator = entity as IOriginatorInfo;
if (String.IsNullOrWhiteSpace(entityWithOriginator.SourceApplication))
{
entityWithOriginator.SourceApplication = assemblyFullName;
}
if (String.IsNullOrWhiteSpace(entityWithOriginator.OriginatorIpaddress))
{
entityWithOriginator.OriginatorIpaddress = ipAddresses;
}
if (entityWithOriginator.RowCreationDateTime == DateTimeOffset.MinValue)
{
entityWithOriginator.RowCreationDateTime = DateTimeOffset.UtcNow;
}
if (String.IsNullOrWhiteSpace(entityWithOriginator.RowCreationUser))
{
try
{
entityWithOriginator.RowCreationUser = rowCretionUser;
}
catch (Exception)
{
entityWithOriginator.RowCreationUser = "Unknown";
}
}
}
var validationContext = new ValidationContext(entity);
Validator.ValidateObject(
entity,
validationContext,
validateAllProperties: true);
}
}
public static List<string> GetCurrentHostIPv4Addresses()
{
//Check https://stackoverflow.com/questions/50386546/net-core-2-x-how-to-get-the-current-active-local-network-ipv4-address
// order interfaces by speed and filter out down and loopback
// take first of the remaining
var allUpInterfaces = NetworkInterface.GetAllNetworkInterfaces()
.OrderByDescending(c => c.Speed)
.Where(c => c.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
c.OperationalStatus == OperationalStatus.Up).ToList();
List<string> lstIps = new();
if (allUpInterfaces != null && allUpInterfaces.Count > 0)
{
foreach (var singleUpInterface in allUpInterfaces)
{
var props = singleUpInterface.GetIPProperties();
// get first IPV4 address assigned to this interface
var allIpV4Address = props.UnicastAddresses
.Where(c => c.Address.AddressFamily == AddressFamily.InterNetwork)
.Select(c => c.Address)
.ToList();
allIpV4Address.ForEach((IpV4Address) =>
{
lstIps.Add(IpV4Address.ToString());
});
}
}
return lstIps;
}
}
We have used this same code in other apps and works, great.
In this specific app, however, the System.Threading.Thread.CurrentPrincipal and ClaimsPrincipal.Current, are null, when the ValidateAndSetDefaults method is executed.
Even tried the following
services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.NameClaimType = "name";
options.Events.OnTokenValidated = (context) =>
{
System.Threading.Thread.CurrentPrincipal = context.Principal;
return Task.CompletedTask;
};
options.SaveToken = true;
});
The context.Principal does have all the data we need at this point, but we still get null reading the Thread's CurrenPrincipal object.
Any ideas how to fix it, so we can properly get the Authenticated User information in the code above, it exists in context.Principal, it is just not accesible later in the SaveChanges for EF Core.
Fixed by using Dependency Injection along with HttpContextAccesor
https://github.com/efonsecab/BlazorRestaurant/blob/main/src/BlazorRestaurantSln/BlazorRestaurant.DataAccess/Data/BlazorRestaurantDbContext.partial.cs

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();

Howto search through Properties of all kinds of types

I have a base class called Part and derived classes like Wire or Connector and many more that inherit from Part.
Now I want to implement a search function that searches all Properties of the derived classes for a string.
If necessary that string should be tried to be converted to the type of the Property. The Properties can also be Lists and should be searched on the first level.
class Part
{
public int Id { get; set; }
public string Name { get; set; }
}
class Wire : Part
{
public NumberWithUnit Diameter { get; set; }
public Weight Weight { get; set; }
}
class Connector : Part
{
public List<Part> ConnectedParts { get; set; }
}
I know how to generally search through the Properties of base types with Reflection like this
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is string)
{
if (string.Equals(value, searchString))
return true;
}
else if (value is int)
{
int v;
if (int.TryParse(searchString, out v))
{
if(v == (int) value)
return true;
}
}
}
return false;
}
But that would be a long list of types and I have Properties of Type Weight for instance and many more. Is there some kind of general way to search without casting all types?
Consider going the opposite direction with your conversion. Rather than converting your search string into each possible value, just convert the value into a string:
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is IEnumerable)
{
// special handling for collections
}
else if(value != null)
{
string valueString = value.ToString();
if (string.Equals(valueString, searchString))
return true;
}
}
return false;
}
Besides working pretty well for most built-in types, the only thing you have to do to get it to work for Weight, etc. is make sure they implement ToString().
Another solution would be to use TypeDescriptor:
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is IEnumerable)
{
// special handling for collections
}
else if(value != null)
{
object searchValue = null;
try
{
searchValue = TypeDescriptor.GetConverter(value).ConvertFromString(searchString);
} catch {}
if (searchValue != null && object.Equals(value, searchValue))
return true;
}
}
return false;
}
TypeDescriptor works well for most built-in types, but requires extra work if you're dealing with custom types.
I think the following should cover the most of the practical scenarios:
public static bool SearchProperties(object target, string searchString)
{
if (target == null) return false;
// Common types
var convertible = target as IConvertible;
if (convertible != null)
{
var typeCode = convertible.GetTypeCode();
if (typeCode == TypeCode.String) return target.ToString() == searchString;
if (typeCode == TypeCode.DBNull) return false;
if (typeCode != TypeCode.Object)
{
try
{
var value = Convert.ChangeType(searchString, typeCode);
return target.Equals(value);
}
catch { return false; }
}
}
if (target is DateTimeOffset)
{
DateTimeOffset value;
return DateTimeOffset.TryParse(searchString, out value) && value == (DateTimeOffset)target;
}
var enumerable = target as IEnumerable;
if (enumerable != null)
{
// Collection
foreach (var item in enumerable)
if (SearchProperties(item, searchString)) return true;
}
else
{
// Complex type
var properties = target.GetType().GetProperties();
foreach (var property in properties)
{
if (property.GetMethod == null || property.GetMethod.GetParameters().Length > 0) continue;
var value = property.GetValue(target);
if (SearchProperties(value, searchString)) return true;
}
}
return false;
}
I will give you one different idea to do it.
You could try something like that:
private bool SearchProperties<T, W>(T part, W searchValue) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
if (typeof(W) == prop.PropertyType)
{
var value = prop.GetValue(part, null);
if (searchValue.Equals(value))
return true;
}
}
return false;
}
You need to call the method like this:
private void button12_Click(object sender, EventArgs e)
{
Part p = new Part();
p.Id = 2;
p.Name = "test";
p.bla = new Bla();
SearchProperties<Part, int>(p, 2);
}
And if you need to compare the complex properties (Weight, ...) by a different way from GetHashCode you could override the method Equals or the == operator.
class Weight
{
public int Id { get; set; }
public override bool Equals(object obj)
{
return Id == ((Weight)obj).Id;
}
}

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();
}
}

Categories