CurrentUser in .Net Core Microservice CQRS - c#

namespace LED.OM.Core.Base
{
public abstract class BaseContext : DbContext
{
public BaseContext(DbContextOptions options) : base(options)
{ }
public override int SaveChanges()
{
UpdateAuditEntities();
return base.SaveChanges();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
UpdateAuditEntities();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
UpdateAuditEntities();
return base.SaveChangesAsync(cancellationToken);
}
private void UpdateAuditEntities()
{
var CurrentUser = Thread.CurrentPrincipal?.Identity?.Name ?? "";
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is Entity &&
(x.State == EntityState.Added || x.State == EntityState.Modified || x.State == EntityState.Deleted));
foreach (var entry in modifiedEntries)
{
var entity = (Entity)entry.Entity;
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entity.CreatedDate = now;
entity.CreatedBy = CurrentUser;
}
else if (entry.State == EntityState.Deleted && entry.Entity is not IHardDelete)
{
// Varlığı değiştirilmedi olarak ayarlıyoruz.
// (tüm varlığı Değiştirildi olarak işaretlersek, her alan güncelleme olarak Db'ye gönderilir)
entry.State = EntityState.Unchanged;
// Yalnızca IsDeleted alanını güncelleyin - yalnızca bu Db'ye gönderilir
entity.IsDelete = true;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
}
}
}
user.cs
entity.cs
I am working with microservice. The entity is the common field in my entire table. I want to fill in the fields created and updated by those who want to log in. But I couldn't find how to fill this user info in BaseContext.

The problem is not about microservices or CQRS. The problem is about Entity Framework Core. So you need to detect the modified entities in UpdateAuditEntities() method then you can fill the UpdatedDate and
UpdatedBy properties.
Also, this method already fills the CreatedDate and CreatedBy properties in creating (EntityState.Added) operations.
You can get an exception when casting entry.Entity to LED.OM.Core.Base.Entities.Entity. You should use this code for retrieving modified entries: var modifiedEntries = ChangeTracker.Entries<LED.OM.Core.Base.Entities.Entity>()...
You should handle the updated entities (EntityState.Modified) like below:
private void UpdateAuditEntities()
{
var CurrentUser = Thread.CurrentPrincipal?.Identity?.Name ?? "";
var modifiedEntries = ChangeTracker.Entries<LED.OM.Core.Base.Entities.Entity>()
.Where(x => x.Entity is Entity &&
(x.State == EntityState.Added || x.State == EntityState.Modified || x.State == EntityState.Deleted));
foreach (var entry in modifiedEntries)
{
var entity = (Entity)entry.Entity;
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entity.CreatedDate = now;
entity.CreatedBy = CurrentUser;
}
else if (entry.State == EntityState.Modified)
{
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
else if (entry.State == EntityState.Deleted && entry.Entity is not IHardDelete)
{
// Varlığı değiştirilmedi olarak ayarlıyoruz.
// (tüm varlığı Değiştirildi olarak işaretlersek, her alan güncelleme olarak Db'ye gönderilir)
entry.State = EntityState.Unchanged;
// Yalnızca IsDeleted alanını güncelleyin - yalnızca bu Db'ye gönderilir
entity.IsDelete = true;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
}

Related

Eager loading - How can I update entity includes some related entities?

I have a parent table (from oracle dataBase) and some child tables, and I can successfully update the parent entity using the Update method below, but not the child ones.
By the way, can I use with generic entities here?
public void UpdateEnt<T>(T entity) where T : parent_table
{
if (entity == null)
{
throw new ArgumentException("Cannot add a null entity.");
}
using (var _context = (DataModelDetach)d.GetContextForRead(Module))
{
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
var set = _context.Set<T>();
T attachedEntity = set.Include(x => x.children_1).Include(x => x.children_2).SingleOrDefault(e => e.idSeq == entity.idSeq);
if (attachedEntity != null)
{
var attachedEntry = _context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
}
}
}

Entity Framework error - New transaction is not allowed

I am currently getting this error in an EF method that is being called (having got Elmah workking)
New transaction is not allowed because there are other threads running in the session.
I have looked at this :
SqlException from Entity Framework - New transaction is not allowed because there are other threads running in the session
and similar questions, but these all refer to savechanges being called within a foreach loop. My code doesn't have a foreach loop, so i'm struggling to find the issue.
Controller (inherited from ApiController)
static readonly IMyRepository myRepository = new MyRepository();
public HttpResponseMessage PutObject(int id, int id2)
{
if(!myRepository.Update(id,id2))
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Repository
MyEntities _myEntities;
public MyRepository(MyEntities context)
{
_myEntities = context;
}
public MyRepository()
{
}
public bool Update(int id,int id2, string id3, int id4)
{
_myEntities = _myEntities ?? new MyEntities();
//update by id if id2,id3 and id4 are zero
if (id2 == 0 && id3 == "0" && id4 == 0)
{
var myobject = _myEntities.MyObjects.Where(x => x.id == id);
if (myobject.Count() > 0)
{
MyObject temp = myobject.SingleOrDefault();
temp.Processed = true;
_myEntities.SaveChanges();
return true;
}
else
{
return false;
}
}
else
{
var myobject = _myEntities.MyObjects.Where(x => x.SourceID == id && x.ExternalID == id2 && x.InternalID == id3 && x.Code == id4 && x.Processed == false);
if (myobject.Count() > 0)
{
myobject temp = myobject.SingleOrDefault();
temp.Processed = true;
_myEntities.SaveChanges();
return true;
}
else
{
return false;
}
}
}
is this because the IQueryable returned by the linq, still holds an open connection?

EF ChangeTracker SoftDelete

I'm looking for a better way for implementing the following logic (HasAnyRelation and SetDeleteMarks) into ChangeTracker in order to avoid rewriting this logic for all my entities.
The problem is that once we call Remove() it will drop all relations with child/parent entities and I can't check the relations into ChangeTracker anymore.
In this post I got a suggestion to implement a copy of my previous entity to keep my collections intact after Remove(). However it works, I also would have to implement the logic many times.
Does anybody would have any other approach to achieve it?
Regards
CONTROLLER DELETE ACTION
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(Sector sector)
{
if (ModelState.IsValid)
{
var currentEntity = db.Sector.Find(sector.SectorId);
var hasAnyRelation = EntityHelper.HasAnyRelation(currentEntity);
if (hasAnyRelation)
{
currentEntity = (Sector)EntityHelper.SetDeleteMarks(currentEntity);
db.Sector.Attach(currentEntity);
db.Entry(currentEntity).State = EntityState.Modified;
}
else
{
db.Sector.Attach(currentEntity);
db.Sector.Remove(currentEntity);
}
db.SaveChanges();
}
}
ENTITY HELPER
public static class EntityHelper
{
public static object SetDeleteMarks(object entityObj)
{
var deletedProperty = entityObj.GetType().GetProperties().Where(p => p.Name == "Deleted").FirstOrDefault();
var nameProperties = entityObj.GetType().GetProperties().Where(p => p.Name.Contains("Name"));
if (deletedProperty != null)
{
deletedProperty.SetValue(entityObj, true);
foreach (var nameProperty in nameProperties)
{
var deletedMark = "(*)";
var currentValue = nameProperty.GetValue(entityObj).ToStringNullSafe();
if (!currentValue.Contains(deletedMark) && !String.IsNullOrEmpty(currentValue))
{
nameProperty.SetValue(entityObj, String.Format("{0} {1}", currentValue, deletedMark).Trim());
}
}
}
return entityObj;
}
public static bool HasAnyRelation(object entityObj)
{
var collectionProps = GetManyRelatedEntityNavigatorProperties(entityObj);
foreach (var item in collectionProps)
{
var collectionValue = GetEntityFieldValue(entityObj, item.Name);
if (collectionValue != null && collectionValue is IEnumerable)
{
var col = collectionValue as IEnumerable;
if (col.GetEnumerator().MoveNext())
{
return true;
}
}
}
return false;
}
private static object GetEntityFieldValue(this object entityObj, string propertyName)
{
var pro = entityObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).First(x => x.Name == propertyName);
return pro.GetValue(entityObj, null);
}
private static IEnumerable<PropertyInfo> GetManyRelatedEntityNavigatorProperties(object entityObj)
{
var props = entityObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanWrite && x.GetGetMethod().IsVirtual && x.PropertyType.IsGenericType == true);
return props;
}
}

Making AddOrUpdate change only some properties

This might be a simple question, however I'm new to Code First and Migrations so bear with me. I'll keep sample code to a minimum to show the problem:
I have a BaseAuditableEntity which includes this (among other things, but let's simplify):
public abstract class BaseAuditableEntity : BaseEntity, IAuditableEntity
{
public DateTime CreatedOn { get; set; }
public DateTime LastModified { get; set; }
}
Now a (for example) User POCO inherits from it:
public class User : BaseAuditableEntity
{
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public bool Active { get; set; }
public DateTime? LastLogin { get; set; }
}
I have this on my context's SaveChanges method, to fill in the CreatedOn and LastModified dates (simplified):
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditableEntity>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(p => p.State != EntityState.Unchanged))
{
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
entry.Entity.CreatedOn = now;
entry.Entity.LastModified = now;
}
}
return base.SaveChanges();
}
And now I have a migration in place that seeds some users, like this:
protected override void Seed(MyContext context)
{
context.Users.AddOrUpdate(
p => p.UserName,
new User { Active = true,
FullName = "My user name",
UserName = "ThisUser",
PasswordHash = "",
Email = "my#email",
LastLogin = null,
}
// etc.
);
}
Now I have a problem on seeding with AddOrUpdate after the migration. When the entity is new (it's being added), CreatedOn gets filled correctly and everything works as expected. However when the entity is modified (it already exists on the database and UserName matches), it tries to update it with the new entity I'm creating... this fails because CreatedOn has an invalid DateTime (in this case, DateTime.MinValue).
Is there any way to use the AddOrUpdate method so that it actually retrieves the matching entity from the database and just update the non-default fields? Or maybe some way to tell it which fields NOT to update? For this specific case, I'd like the CreatedOn field to be unchanged, but a generic solution would be appreciated.
Maybe I should do my own AddOrUpdate method which includes a predicate with the fields I want to change, instead of passing it a completely new entity?
This is EF 6.1
Update
I know I can easily solve this for the CreatedOn date, this is what I'm currently doing for this specific case:
foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entry.Entity.CreatedOn = now;
}
else
{
if (entry.Property(p => p.CreatedOn).CurrentValue == DateTime.MinValue)
{
var original = entry.Property(p => p.CreatedOn).OriginalValue;
entry.Property(p => p.CreatedOn).CurrentValue = original != SqlDateTime.MinValue ? original : now;
entry.Property(p => p.CreatedOn).IsModified = true;
}
}
entry.Entity.LastModified = now;
}
I am looking for a more generic solution though
The implementation of AddOrUpdate uses CurrentValues.SetValues so that all scalar properties will be modified.
I have extended the functionality to accept properties to be modified when it's an update, otherwise it's a creation, just use DbSet<T>::Add.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public static class SeedExtension
{
public static void Upsert<T>(this DbContext db, Expression<Func<T, object>> identifierExpression, Expression<Func<T, object>> updatingExpression, params T[] entities)
where T : class
{
if (updatingExpression == null)
{
db.Set<T>().AddOrUpdate(identifierExpression, entities);
return;
}
var identifyingProperties = GetProperties<T>(identifierExpression).ToList();
Debug.Assert(identifyingProperties.Count != 0);
var updatingProperties = GetProperties<T>(updatingExpression).Where(pi => IsModifiedable(pi.PropertyType)).ToList();
Debug.Assert(updatingProperties.Count != 0);
var parameter = Expression.Parameter(typeof(T));
foreach (var entity in entities)
{
var matches = identifyingProperties.Select(pi => Expression.Equal(Expression.Property(parameter, pi.Name), Expression.Constant(pi.GetValue(entity, null))));
var matchExpression = matches.Aggregate<BinaryExpression, Expression>(null, (agg, v) => (agg == null) ? v : Expression.AndAlso(agg, v));
var predicate = Expression.Lambda<Func<T, bool>>(matchExpression, new[] { parameter });
var existing = db.Set<T>().SingleOrDefault(predicate);
if (existing == null)
{
// New.
db.Set<T>().Add(entity);
continue;
}
// Update.
foreach (var prop in updatingProperties)
{
var oldValue = prop.GetValue(existing, null);
var newValue = prop.GetValue(entity, null);
if (Equals(oldValue, newValue)) continue;
db.Entry(existing).Property(prop.Name).IsModified = true;
prop.SetValue(existing, newValue);
}
}
}
private static bool IsModifiedable(Type type)
{
return type.IsPrimitive || type.IsValueType || type == typeof(string);
}
private static IEnumerable<PropertyInfo> GetProperties<T>(Expression<Func<T, object>> exp) where T : class
{
Debug.Assert(exp != null);
Debug.Assert(exp.Body != null);
Debug.Assert(exp.Parameters.Count == 1);
var type = typeof(T);
var properties = new List<PropertyInfo>();
if (exp.Body.NodeType == ExpressionType.MemberAccess)
{
var memExp = exp.Body as MemberExpression;
if (memExp != null && memExp.Member != null)
properties.Add(type.GetProperty(memExp.Member.Name));
}
else if (exp.Body.NodeType == ExpressionType.Convert)
{
var unaryExp = exp.Body as UnaryExpression;
if (unaryExp != null)
{
var propExp = unaryExp.Operand as MemberExpression;
if (propExp != null && propExp.Member != null)
properties.Add(type.GetProperty(propExp.Member.Name));
}
}
else if (exp.Body.NodeType == ExpressionType.New)
{
var newExp = exp.Body as NewExpression;
if (newExp != null)
properties.AddRange(newExp.Members.Select(x => type.GetProperty(x.Name)));
}
return properties.OfType<PropertyInfo>();
}
}
Usage.
context.Upsert(
p => p.UserName,
p => new { p.Active, p.FullName, p.Email },
new User
{
Active = true,
FullName = "My user name",
UserName = "ThisUser",
Email = "my#email",
}
);
I ran into an issue with Expression.Equals in #Yuliam's answer where a type was nullable and had to add the following.
var matches = identifyingProperties.Select(pi =>
Expression.Equal(Expression.Property(parameter, pi.Name),
Expression.Convert(Expression.Constant(pi.GetValue(entity, null)),
Expression.Property(parameter, pi.Name).Type)));
I also updated this to fetch all records first so "SingleOrDefault" doesn't execute a sql query in each for loop iteration.
I also set AddRange which gets a little better performance.
Here is a gist of my solution. Thanks for posting this Yuliam! I've been looking for something like this for a while.
https://gist.github.com/twilly86/eb6b61a22b66b4b33717aff84a31a060

Use ContinueWith to make async method

I want to make async method in redis by StackExchange.Redis follow code :
public bool Insert<T>(T entity) where T : IBaseEntity
{
long entityCounter = _redisClient.StringIncrement(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name));
if (entity.Id == 0)
{
entity.Id = ((int)GetLastId<T>()) + 1;
}
_redisClient.StringSet(CacheProcessPatterns.MakeLastId(entity.GetType().Name), entity.Id);
string itemRedisKey = CacheProcessPatterns.MakeItemById(entity.GetType().Name, entity.Id);
bool setStatus = _redisClient.StringSet(itemRedisKey, JsonSerializer.SerializeToString<T>(entity));
if (setStatus)
{
_redisClient.StringSet(CacheProcessPatterns.MakeIdByKey(entity.GetType().Name, entity.Id), entity.Key.ToString());
_redisClient.StringSet(CacheProcessPatterns.MakeKeyById(entity.GetType().Name, entity.Key.ToString()), entity.Id);
_redisClient.SetAdd(CacheProcessPatterns.MakeItemKeysByType(entity.GetType().Name), entity.Id);
}
else
{
entityCounter = _redisClient.StringDecrement(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name));
}
return setStatus;
}
and the other hands i trying make async but I have a problem on second ContinueWith() Method .
Error : Cannot implicity convert type 'Task' to 'Task'.An
explicit conversation exists(are you missing a cast?).
Follow code :
public Task<bool> Insert<T>(T entity) where T : IBaseEntity
{
return _redisClient.StringIncrementAsync(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name))
.ContinueWith(entityCounter =>
{
if (entity.Id == 0)
{
entity.Id = ((int)GetLastId<T>().Result);
}
}).ContinueWith(_ =>
{
_redisClient.StringSetAsync(CacheProcessPatterns.MakeLastId(entity.GetType().Name), entity.Id).ContinueWith(status =>
{
string itemRedisKey = CacheProcessPatterns.MakeItemById(entity.GetType().Name, entity.Id);
_redisClient.StringSetAsync(itemRedisKey, JsonSerializer.SerializeToString<T>(entity)).ContinueWith( setStatus =>
{
if (setStatus.Result)
{
ITransaction tran = _redisClient.CreateTransaction();
tran.StringSetAsync(CacheProcessPatterns.MakeIdByKey(entity.GetType().Name, entity.Id), entity.Key.ToString());
tran.StringSetAsync(CacheProcessPatterns.MakeKeyById(entity.GetType().Name, entity.Key.ToString()), entity.Id);
tran.SetAddAsync(CacheProcessPatterns.MakeItemKeysByType(entity.GetType().Name), entity.Id);
return tran.ExecuteAsync();
}
else
{
_redisClient.StringDecrementAsync(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name));
}
return setStatus;
});
});
});
}
What is my problem ? and how to fix that ?Thanks ...
I think the problem is that your second ContinueWith returns a Task and not a Task<bool>. Try changing the code as follows:
public Task<bool> Insert<T>(T entity) where T : IBaseEntity
{
return _redisClient.StringIncrementAsync(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name))
.ContinueWith(entityCounter =>
{
if (entity.Id == 0)
{
entity.Id = ((int)GetLastId<T>().Result);
}
})
// Explicitly specify task type to be bool
.ContinueWith<bool>(_ =>
{
_redisClient.StringSetAsync(CacheProcessPatterns.MakeLastId(entity.GetType().Name), entity.Id).ContinueWith(status =>
{
string itemRedisKey = CacheProcessPatterns.MakeItemById(entity.GetType().Name, entity.Id);
_redisClient.StringSetAsync(itemRedisKey, JsonSerializer.SerializeToString<T>(entity)).ContinueWith( setStatus =>
{
if (setStatus.Result)
{
ITransaction tran = _redisClient.CreateTransaction();
tran.StringSetAsync(CacheProcessPatterns.MakeIdByKey(entity.GetType().Name, entity.Id), entity.Key.ToString());
tran.StringSetAsync(CacheProcessPatterns.MakeKeyById(entity.GetType().Name, entity.Key.ToString()), entity.Id);
tran.SetAddAsync(CacheProcessPatterns.MakeItemKeysByType(entity.GetType().Name), entity.Id);
return tran.ExecuteAsync();
}
else
{
_redisClient.StringDecrementAsync(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name));
}
return setStatus;
});
});
return true; // since this is a Task<bool> we need a bool return value

Categories

Resources