How can I auto-update the int ModifiedBy property on a Entity with UserId in Entity Framework 4 when saving? - sql-server

I am using Simple Membership and a UserProfile table that maintains UserId and UserName:
public partial class UserProfile
{
public UserProfile()
{
this.webpages_Roles = new List<webpages_Roles>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<webpages_Roles> webpages_Roles { get; set; }
}
With Entity Framework I am running the following which is inside my Context:
public partial class UowContext : DbContext
// code to set up DbSets here ...
public DbSet<Content> Contents { get; set; }
private void ApplyRules()
{
var r1 = new Random();
var r2 = new Random();
foreach (var entry in this.ChangeTracker.Entries()
.Where(
e => e.Entity is IAuditableTable &&
(e.State == EntityState.Added) ||
(e.State == EntityState.Modified)))
{
IAuditableTable e = (IAuditableTable)entry.Entity;
if (entry.State == EntityState.Added)
{
e.CreatedBy = // I want to put the integer value of UserId here
e.CreatedDate = DateTime.Now;
}
e.ModifiedBy = // I want to put the integer value of UserId here
e.ModifiedDate = DateTime.Now;
}
}
Here is the schema showing how user information is stored. Note that I store the integer UserId and not the UserName in the tables:
public abstract class AuditableTable : IAuditableTable
{
public virtual byte[] Version { get; set; }
public int CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public int ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
}
Here's an example of a controller action that I use:
public HttpResponseMessage PostContent(Content content)
{
try
{
_uow.Contents.Add(content);
_uow.Commit();
var response = Request.CreateResponse<Content>(HttpStatusCode.Created, content);
return response;
}
catch (DbUpdateException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.Conflict, ex);
}
}
I then have:
public class UowBase : IUow, IDisposable
{
public UowBase(IRepositoryProvider repositoryProvider)
{
CreateDbContext();
repositoryProvider.DbContext = DbContext;
RepositoryProvider = repositoryProvider;
}
public IRepository<Content> Contents { get { return GetStandardRepo<Content>(); } }
and:
public class GenericRepository<T> : IRepository<T> where T : class
{
public GenericRepository(DbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("An instance of DbContext is required to use this repository", "context");
DbContext = dbContext;
DbSet = DbContext.Set<T>();
}
public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Detached)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbSet.Add(entity);
}
}
How can I determine the UserId from inside of my Context so I can populate the Id in my tables?

In Code you will have UserName with you through:
HttpContext.Current.User.Identity.Name
you can than query UserProfile table against that Name and get the UserId from there and than assign it to ModifiedBy attribute.
Make sure that you query UserProfile table outside the foreach loop :)

Related

How to log database table value changes programmatically in terms of auditing?

May be this could be accomplished using a trigger and an audit log table in SQL server. Or perhaps it could be accomplished by overriding the SaveChanges() method in Entity Framework. My concern is how to write the code to get it done and which one will be efficient. Can anybody help me?
If you log changes through the code, alongside data, you can add additional information to the audit log such as IP, user info, client info, ... and it is really helpful. The downside would be if someone changes data directly via the database you cannot find out what data has changed and the performance of logging data change by audit log table is better. If you just need to capture data change and don't need to find out who and from where data has changed choose the database approach.
Here is an implementation to capture data changes by EF Core:
public interface IAuditableEntity
{
}
public class AuditLogEntity
{
public int Id { get; set; }
public string UserName { get; set; }
public DateTime CreateDate { get; set; }
public ChangeType ChangeType { get; set; }
public string EntityId { get; set; }
public string EntityName { get; set; }
public IEnumerable<EntityPropertyChange> Changes { get; set; }
}
public class EntityPropertyChange
{
public string PropertyName { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
}
public enum ChangeType
{
Add = 1,
Edit = 2,
Remove = 3
}
In DbContext:
public class RegistryDbContext : DbContext
{
private readonly IHttpContextAccessor _contextAccessor;
public RegistryDbContext(DbContextOptions<RegistryDbContext> options, IHttpContextAccessor contextAccessor) :
base(options)
{
_contextAccessor = contextAccessor;
}
public DbSet<AuditLogEntity> AuditLogs { get; set; }
public override int SaveChanges()
{
CaptureChanges();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
CaptureChanges();
return await base.SaveChangesAsync(cancellationToken);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AuditLogEntity>().Property(p => p.UserId).IsUnicode(false).HasMaxLength(36).IsRequired(false);
modelBuilder.Entity<AuditLogEntity>().Property(p => p.EntityId).IsUnicode(false).HasMaxLength(36).IsRequired();
modelBuilder.Entity<AuditLogEntity>().Property(p => p.EntityName).IsUnicode(false).HasMaxLength(256).IsRequired(false);
builder.Ignore(p => p.Changes);
modelBuilder.Entity<AuditLogEntity>()
.Property(p => p.Changes).IsUnicode().HasMaxLength(int.MaxValue).IsRequired(false)
.HasConversion(
changes => JsonConvert.SerializeObject(changes, Formatting.None, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Include,
TypeNameHandling = TypeNameHandling.Auto
}),
changes => JsonConvert.DeserializeObject<IList<EntityPropertyChange>>(changes, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Include,
TypeNameHandling = TypeNameHandling.Auto
}));
}
private void CaptureChanges()
{
var changes = ChangeTracker
.Entries<IAuditableEntity>()
.Where(e =>
e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.Select(GetAuditLogItems)
.ToList();
AuditLogs.AddRange(changes);
}
private AuditLogEntity GetAuditLogItems(EntityEntry entry)
{
var auditEntity = new AuditLogEntity
{
CreateDate = DateTime.Now,
EntityId = entry.Properties.FirstOrDefault(f => f.Metadata.IsPrimaryKey())?.CurrentValue?.ToString(),
EntityName = entry.Metadata.Name,
UserName = _contextAccessor?.HttpContext?.User?.Name.ToString(),
};
switch (entry.State)
{
case EntityState.Added:
auditEntity.ChangeType = ChangeType.Add;
auditEntity.Changes = GetChanges(entry.Properties, e => true).ToList();
foreach (var entityChange in auditEntity.Changes)
entityChange.OldValue = null;
break;
case EntityState.Modified:
auditEntity.ChangeType = ChangeType.Edit;
auditEntity.Changes = GetChanges(entry.Properties, e => e.IsModified).ToList();
break;
case EntityState.Deleted:
auditEntity.ChangeType = ChangeType.Remove;
break;
}
return auditEntity;
}
private IEnumerable<EntityPropertyChange> GetChanges(IEnumerable<PropertyEntry> properties,
Func<PropertyEntry, bool> predicate) => properties
.Where(predicate)
.Select(property =>
new EntityPropertyChange
{
PropertyName = property.Metadata.Name,
OldValue = property.OriginalValue?.ToString(),
NewValue = property.CurrentValue?.ToString()
});
}
I use IAuditableEntity (an empty interface) to mark entities that I want to capture changes.
public class CustomerEntity : IAuditableEntity
{
...
}
You can also use Audit.NET library to capture changes.

INSERT statement conflicted with foreign key constraint - SQL Server/Entity Framework Core

I get this error in my .NET Core 3.1 app:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_DiaryDiaryEntry". The conflict occurred in database "xxxxxx", table "dbo.Diaries", column 'Id'.
I can't see anything wrong with the tables themselves.
public partial class Diaries
{
public long Id { get; set; }
public string CoverImage { get; set; }
public short Year { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public long ChildId { get; set; }
public Children Child { get; set; }
public ICollection<DiaryEntries> DiaryEntries { get; set; }
}
public partial class DiaryEntries
{
public long Id { get; set; }
public DateTime Date { get; set; }
public string Content { get; set; }
public long DiaryId { get; set; }
public Diaries Diary { get; set; }
public ICollection<Images> Images { get; set; }
}
My code? Probably an entirely different matter.
This is the code that generates the error.
[HttpPost("CreateYear/{id}")]
public async Task<IActionResult> CreateYearOfEntries([FromRoute] int id)
{
// The id is the ID of an existing diary
// Make sure the diary does exist first and that it belongs to the current logged-in user
var diary = _diaryRepository.Find(id);
if (diary == null) return NotFound();
var year = diary.Result.Year;
if (await _diaryEntryRepository.OwnerIsLoggedIn(LoggedInUser.ParentId, id))
{
var noOfDays = DateTime.IsLeapYear(year) ? 366 : 365;
var i = 0;
for (; i < noOfDays; i++)
{
var date = new DateTime(year, 1, 1).AddDays(i);
var newDiaryEntry = new DiaryEntries()
{
Content = " ",
Date = date,
DiaryId = diary.Id
};
await _diaryEntryRepository.Add(newDiaryEntry);
}
return Ok();
}
return NotFound();
}
public class DiaryEntryRepository : IDiaryEntryRepository
{
private readonly ApplicationDbContext _context;
public DiaryEntryRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<DiaryEntries> Add(DiaryEntries diaryEntry)
{
await _context.DiaryEntries.AddAsync(diaryEntry);
await _context.SaveChangesAsync();
return diaryEntry;
}
}

Dapper custom SqlMapper.TypeHandler Parse method not called

I created a SqlMapper.TypeHandler to map a Customer object into a CreditAccount class as follows:
public class CustomerTypeHandler : SqlMapper.TypeHandler<Customer>
{
public override Customer Parse(object value)
{
throw new NotImplementedException();
}
public override void SetValue(IDbDataParameter parameter, Customer
value)
{
throw new NotImplementedException();
}
}
public class CreditAccount
{
public int AccountId { get; set; }
public Customer Customer{ get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
When I connect to the DB and call a sproc, the CustomerTypeHandler Parse method is never called and my CreditAccount object is populated with the AccountId only. The Customer object is null.
I am calling it as follows:
public async Task<CreditAccount> GetCreditAccount(int accountId)
{
var sql = "MY PROC NAME HERE";
var parameters = new DynamicParameters();
parameters.Add("#AccountId", accountId);
SqlMapper.AddTypeHandler(new CustomerTypeHandler());
using (IDbConnection connection = Connection)
{
connection.Open();
var account = await connection.QueryFirstAsync<CreditAccount>(sql, parameters, commandType: CommandType.StoredProcedure);
return account;
}
}
}
I placed a breakpoint in the Parse method and it is never called.
The database connection works, and I am getting the AccountId.
My environment;
.NET Core 2.2
Dapper 1.50.5
The code is simple enough. I get not exceptions. Any ideas?
A year has passed and now there is no this error in Dapper 2.0.30.
I checked it on jsonb columns in Postgres.
using Dapper;
using Newtonsoft.Json;
using Npgsql;
using System;
using System.Data;
public class CreditAccount
{
public int AccountId { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class CustomerJsonObjectTypeHandler : SqlMapper.TypeHandler<Customer>
{
public override void SetValue(IDbDataParameter parameter, Customer value)
{
parameter.Value = (value == null)
? (object)DBNull.Value
: JsonConvert.SerializeObject(value);
parameter.DbType = DbType.String;
}
public override Customer Parse(object value)
{
return JsonConvert.DeserializeObject<Customer>(value.ToString());
}
}
Example using this classes - all work fine.
static void Main(string[] args)
{
using (var connection = GetDefaultConnection())
{
connection.Open();
var customer = new Customer
{
FirstName = "Gaday",
LastName = "Ivanova",
MiddleName = "Petrovich"
};
var jsonData = JsonConvert.SerializeObject(customer);
var strQuery = $"SELECT 10500 as AccountId,'{jsonData}'::jsonb as Customer";
SqlMapper.AddTypeHandler(new CustomerJsonObjectTypeHandler());
try
{
var data = connection.QueryFirst<CreditAccount>(strQuery);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}

DatabaseContext delay to update database

I have an issue when I use Code First EF. When I want to insert an User with its Permissions, the user insert well, but in inserting Permissions just after insert user, I see this exception :
SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.UserPermissions_dbo.Users_UserId". The conflict occurred in database "DATABASE", table "dbo.Users", column 'UserId'.
Here my code :
public class User
{
public short UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public ObservableCollection<UserPermission> UserPermissions { get; set; }
}
public class UserPermission
{
public int UserPermissionId { get; set; }
public short UserId { get; set; }
public short PermissionEnum { get; set; }
public bool IsPermitted { get; set; }
}
public class DatabaseContext : DbContext
{
public DatabaseContext()
{
}
public DbSet<User> Users { get; set; }
public DbSet<UserPermission> UserPermissions { get; set; }
}
And final code to insert is :
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
var user = new User { Username = "myUsername", Password = "12"};
using (var dbContext = new DatabaseContext())
{
dbContext.Users.AddOrUpdate(user);
dbContext.SaveChanges();
}
SaveUserPermissions(user);
}
private void SaveUserPermissions(User user)
{
var userPermissionsToSave = new ObservableCollection<UserPermission>();
foreach (var permission in ThisUserPermissionData.UserPermissions)
{
var newUserPermission = new UserPermission();
newUserPermission.UserId = PageModel.UserId;
newUserPermission.UserPermissionTypeEnum = 4;
userPermissionsToSave.Add(newUserPermission);
}
using (var dbContext = new DatabaseContext())
{
dbContext.UserPermissions.AddOrUpdate(userPermissionsToSave.ToArray());
dbContext.SaveChanges();
}
}
Note that when I set breakpoint after save user, the code perform correctly. But in normal mode, my code has that exception.
Thanks in advance.

Ria Service: Navigation Property is null

For example I have two entities
Class A
{
public Guid Id {get;set;}
public Guid BId {get;set;}
public B InstanceB {get;set;}
}
Class B
{
public Guid Id {get;set}
}
B is related to A, on my silver light application I am creating a new instance of A, and also a new instance of B. The new instance of B does not exist yet. But I need the instance of B on my service.
Can I do this without Entity or Association with Ria Service?
Edit:
My Class A :
public partial class lSync{
// Metadata classes are not meant to be instantiated.
private lSync() {
}
public string ConflictMessage { get; set; }
public DateTime DateInserted { get; set; }
public Guid vValuesId { get; set; }
public Guid ID { get; set; }
public bool IsConflict { get; set; }
public bool IsReadyToSync { get; set; }
public Guid SyncSet { get; set; }
public vValues vValues { get; set; }
}
My Ria Service:
[Invoke]
public lSync[] SynchvValuesFromClient(lSync[] syncs) {
bool noConflict = true;
foreach (lSync sync in syncs) {
var servervValue = GetvValuesByID(sync.vValuesId).FirstOrDefault();
var queuevValues = sync.vValues; //sync.vValues here is null, but my sync.vValuesId is not
if (servervValue== null) {
InsertvValues(queueValue);
}
else {
if (servervValue.IsServerConflict(queueValue)) {
sync.IsConflict = true;
sync.ConflictMessage = "Conflict";
noConflict = false;
break;
}
if (!servervValue.AreValuesEqual(queueValue)) {
UpdatevValues(queueValue);
}
}
}
if (noConflict) {
this.ObjectContext.SaveChanges();
}
return syncs;
}
public IQueryable<vValues> GetvValuesByID(Guid ID) {
return ObjectContext.vValues.Where(t => t.ID == ID);
}
public void InsertvValues(vValues model) {
model.ServerDate = DateTime.UtcNow;
if ((model.EntityState != EntityState.Detached)) {
this.ObjectContext.ObjectStateManager.ChangeObjectState(model, EntityState.Added);
}
else {
this.ObjectContext.vValues.AddObject(model);
}
}
public void UpdatevValues(vValuesmodel) {
model.ServerDate = DateTime.UtcNow;
this.ObjectContext.vValues.AttachAsModified(model, this.ChangeSet.GetOriginal(model));
}
:(
Edit
The order is wrong of your method :)
Make an instance of the service before creating instances of the Entities.
It should be:
public void SyncToServer() {
ContextService service = new ContextService();
var instanceA = new A();
instanceA.InstanceB = new B();
service.SubmitChanges(); //service.SaveChanges() for LinqToEntities
}
Are you reloading after a submit because only adding the [Include] attribute in the DomainService MetaData won't work. You need to do this in the DomainService for LinqToSql
public A GetA()
{
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<A>(a => a.InstanceB);
this.DataContext.LoadOptions = dlo;
return this.DataContext.APlural.FirstOrDefault( ); //don't know the plural of A.
}
LinqToEntities:
public A GetA()
{
return this.MyEntitiesContext.APlural.Include( "instanceB" ).FirstOrDefault( ); //don't know the plural of A.
}
var a = new A(){
B = new B(); //or (B)selectedItem
}
now a.Id and a.BId is 0 until you SaveChanged and return saved A

Resources