Different behaviour between bridging and standalone model/class
We are trying to streamline our automatic updating fields for DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy and worked OK by adding routine OnBeforeSaving in the ApplicationDBContext.cs and we also do the SOFT-DELETE for retaining the records (flagged as IsDeleted
approach instead) when we deleted:
ApplicationDBContext.cs -
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Models;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
namespace AthlosifyWebArchery.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string,
IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
IHttpContextAccessor httpContextAccessor
)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
public DbSet<AthlosifyWebArchery.Models.TournamentBatchItem> TournamentBatchItem { get; set; }
public DbSet<AthlosifyWebArchery.Models.TournamentBatch> TournamentBatch { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Host> Host { get; set; }
public DbSet<AthlosifyWebArchery.Models.HostApplicationUser> HostApplicationUser { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Club> Club { get; set; }
public DbSet<AthlosifyWebArchery.Models.ClubApplicationUser> ClubApplicationUser { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var entityType in builder.Model.GetEntityTypes())
{
// 1. Add the IsDeleted property
entityType.GetOrAddProperty("IsDeleted", typeof(bool));
// 2. Create the query filter
var parameter = Expression.Parameter(entityType.ClrType);
// EF.Property<bool>(post, "IsDeleted")
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
// EF.Property<bool>(post, "IsDeleted") == false
BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
// post => EF.Property<bool>(post, "IsDeleted") == false
var lambda = Expression.Lambda(compareExpression, parameter);
builder.Entity(entityType.ClrType).HasQueryFilter(lambda);
}
// Many to Many relationship - HostApplicationUser
builder.Entity<HostApplicationUser>()
.HasKey(bc => new { bc.HostID, bc.Id });
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.Host)
.WithMany(b => b.HostApplicationUsers)
.HasForeignKey(bc => bc.HostID);
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.HostApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ClubApplicationUser
builder.Entity<ClubApplicationUser>()
.HasKey(bc => new { bc.ClubID, bc.Id });
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.Club)
.WithMany(b => b.ClubApplicationUsers)
.HasForeignKey(bc => bc.ClubID);
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.ClubApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ApplicationUserRole
builder.Entity<ApplicationUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.ApplicationUserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
OnBeforeSaving();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
OnBeforeSaving();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void OnBeforeSaving()
{
if (_httpContextAccessor.HttpContext != null)
{
var userName = _httpContextAccessor.HttpContext.User.Identity.Name;
var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
// Added
var added = ChangeTracker.Entries().Where(v => v.State == EntityState.Added &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
added.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateCreated = DateTime.UtcNow;
((IBaseEntity)entry.Entity).CreatedBy = userId;
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Modified
var modified = ChangeTracker.Entries().Where(v => v.State == EntityState.Modified &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
modified.ForEach(entry =>
{
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
foreach (var entry in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Deleted &&
e.Metadata.GetProperties().Any(x => x.Name == "IsDeleted")))
{
switch (entry.State)
{
case EntityState.Added:
entry.CurrentValues["IsDeleted"] = false;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
}
else
{
// DbInitializer kicks in
}
}
}
}
Worked OK with standalone model/class ie. Club, Host, ApplicationUser ** BUT not bridging model/class ie. **ClubApplicationUser and HostApplicationUser
So we have to do manually on this (AssignClub.cshtml.cs for instance) by adding manually for creating/deleting as you can see below:
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
... the deleted.Count() below returning 0 ... and the same with creating etc:
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
AssignClub.cshtml.cs -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Data;
using AthlosifyWebArchery.Models;
using AthlosifyWebArchery.Pages.Administrators.TournamentBatches;
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using static AthlosifyWebArchery.Pages.Administrators.Users.AssignClubUserModel;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace AthlosifyWebArchery.Pages.Administrators.Users
{
//public class AssignClubUserModel : ClubNamePageModel
public class AssignClubUserModel : UserViewPageModel
{
private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public AssignClubUserModel(AthlosifyWebArchery.Data.ApplicationDbContext context,
IHttpContextAccessor httpContextAccessor
)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
}
public class AssignClubUserViewModel<ApplicationUser>
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public Guid ClubID { get; set; }
public Guid OriginalClubID { get; set; }
public byte RowVersion { get; set; }
}
[BindProperty]
public AssignClubUserViewModel<ApplicationUser> AssignClubUser { get; set; }
//public SelectList ClubNameSL { get; set; }
public async Task<IActionResult> OnGetAsync(Guid? id)
{
if (id == null)
return NotFound();
AssignClubUser = await _context.Users
.Include(u => u.ClubApplicationUsers)
.Where(t => t.Id == id.ToString())
.Select(t => new AssignClubUserViewModel<ApplicationUser>
{
Id = t.Id,
FirstName = t.FirstName,
LastName = t.LastName,
UserName = t.UserName,
ClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
OriginalClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
RowVersion = t.ClubApplicationUsers.ElementAt(0).RowVersion
}).SingleAsync();
if (AssignClubUser == null)
return NotFound();
// Use strongly typed data rather than ViewData.
//ClubNameSL = new SelectList(_context.Club, "ClubID", "Name");
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
public async Task<IActionResult> OnPostAsync(Guid id)
{
if (!ModelState.IsValid)
return Page();
// 1st approach:
// Modify the bridge model directly
/*var clubApplicationUserToUpdate = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
if (clubApplicationUserToUpdate == null)
return await HandleDeletedUser();
_context.Entry(clubApplicationUserToUpdate)
.Property("RowVersion").OriginalValue = AssignClubUser.RowVersion;
// This slightly tweek for this particular
// As the modified Change Track is not triggered
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastDateModified").CurrentValue = DateTime.UtcNow;
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastModifiedBy").CurrentValue = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (await TryUpdateModelAsync<ClubApplicationUser>(
clubApplicationUserToUpdate,
"AssignClubUser",
s => s.Id, s => s.ClubID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (ClubApplicationUser)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The club application user was deleted by another user.");
return Page();
}
var dbValues = (ClubApplicationUser)databaseEntry.ToObject();
await setDbErrorMessage(dbValues, clientValues, _context);
AssignClubUser.RowVersion = (byte)dbValues.RowVersion;
ModelState.Remove("User.RowVersion");
}
}
*/
// 2nd approach:
// Soft -Delete and Add
// Did the soft-deleting and managed to add a new one BUT then die the roll back (adding the old one)
// Result: Violation of PRIMARY KEY constraint 'PK_ClubApplicationUser'.
// Cannot insert duplicate key in object
// Due to duplicate key
/*var clubApplicatonUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString());
ClubApplicationUser clubApplicatonUserToAdd = new ClubApplicationUser();
clubApplicatonUserToAdd.Id = id.ToString();
clubApplicatonUserToAdd.ClubID = AssignClubUser.SelectedClubID;
//_context.Entry(clubApplicatonUserToRemove)
// .Property("RowVersion").OriginalValue = User.RowVersion;
if (clubApplicatonUserToRemove != null)
{
_context.ClubApplicationUser.Remove(clubApplicatonUserToRemove);
await _context.SaveChangesAsync();clubApplicationUserDeleted
_context.ClubApplicationUser.Add(clubApplicatonUserToAdd);
await _context.SaveChangesAsync();
}*/
//delete all club memberships and add new one
//var clubApplicationUserDeleted = await _context.ClubApplicationUser
// .FirstOrDefaultAsync(m => m.Id == id.ToString()
// && m.ClubID == AssignClubUser.ClubID && m.IsDeleted == );
var userID = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (AssignClubUser.ClubID != AssignClubUser.OriginalClubID)
{
var deletedClubApplicationUsers = _context.ClubApplicationUser.IgnoreQueryFilters()
.Where(post => post.Id == id.ToString()
&& post.ClubID == AssignClubUser.ClubID && EF.Property<bool>(post, "IsDeleted") == true);
if (deletedClubApplicationUsers.Count() > 0)
{
// Undo the deleted one
foreach (var deletedClubApplicationUser in deletedClubApplicationUsers)
{
var postEntry = _context.ChangeTracker.Entries<ClubApplicationUser>().First(entry => entry.Entity == deletedClubApplicationUser);
postEntry.Property("IsDeleted").CurrentValue = false;
postEntry.Property("LastDateModified").CurrentValue = DateTime.UtcNow;
postEntry.Property("LastModifiedBy").CurrentValue = userID;
postEntry.Property("DateDeleted").CurrentValue = null;
postEntry.Property("DeletedBy").CurrentValue = null;
}
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
else
{
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
_context.Entry(clubApplicationUserToRemove).State = EntityState.Deleted;
await _context.SaveChangesAsync();
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
// Added the new one
var newClubApplicationUser = new ClubApplicationUser()
{
Id = id.ToString(),
ClubID = AssignClubUser.ClubID,
DateCreated = DateTime.UtcNow,
CreatedBy = userID,
LastDateModified = DateTime.UtcNow,
LastModifiedBy = userID
};
_context.ClubApplicationUser.Add(newClubApplicationUser);
try
{
_context.Entry(newClubApplicationUser).State = EntityState.Added;
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
}
return RedirectToPage("./Index");
}
private async Task<IActionResult> HandleDeletedUser()
{
ClubApplicationUser deletedClubApplicationUser = new ClubApplicationUser();
ModelState.AddModelError(string.Empty,
"Unable to save. The club was deleted by another user.");
return Page();
}
private async Task setDbErrorMessage(ClubApplicationUser dbValues,
ClubApplicationUser clientValues, ApplicationDbContext context)
{
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
}
}
ClubApplicationUser.cs model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ClubApplicationUser
{
public Guid ClubID { get; set; }
public Club Club { get; set; }
public string Id { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public DateTime LastDateModified { get; set; }
public string LastModifiedBy { get; set; }
public DateTime? DateDeleted { get; set; }
public string DeletedBy { get; set; }
public bool IsDeleted { get; set; }
[Timestamp]
public byte RowVersion { get; set; }
[ForeignKey("CreatedBy")]
public ApplicationUser ClubApplicationCreatedUser { get; set; }
[ForeignKey("LastModifiedBy")]
public ApplicationUser ClubApplicationLastModifiedUser { get; set; }
}
}
Any ideas? The current solution is working OK BUT it just has extra routines to add these DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy manually for this bridging model/class.
Environment:
- .NET Core 2.2
- SQL Server
Updates 1:
We even add this before the saving and it didn't trigger the automatic ChangeTracker.Entries():
_context.Entry(newClubApplicationUser).State = EntityState.Deleted;
await _context.SaveChangesAsync();
Updates 2:
Added the ClubApplicationUser model above.
asp.net-core .net-core razor-pages
add a comment |
We are trying to streamline our automatic updating fields for DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy and worked OK by adding routine OnBeforeSaving in the ApplicationDBContext.cs and we also do the SOFT-DELETE for retaining the records (flagged as IsDeleted
approach instead) when we deleted:
ApplicationDBContext.cs -
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Models;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
namespace AthlosifyWebArchery.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string,
IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
IHttpContextAccessor httpContextAccessor
)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
public DbSet<AthlosifyWebArchery.Models.TournamentBatchItem> TournamentBatchItem { get; set; }
public DbSet<AthlosifyWebArchery.Models.TournamentBatch> TournamentBatch { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Host> Host { get; set; }
public DbSet<AthlosifyWebArchery.Models.HostApplicationUser> HostApplicationUser { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Club> Club { get; set; }
public DbSet<AthlosifyWebArchery.Models.ClubApplicationUser> ClubApplicationUser { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var entityType in builder.Model.GetEntityTypes())
{
// 1. Add the IsDeleted property
entityType.GetOrAddProperty("IsDeleted", typeof(bool));
// 2. Create the query filter
var parameter = Expression.Parameter(entityType.ClrType);
// EF.Property<bool>(post, "IsDeleted")
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
// EF.Property<bool>(post, "IsDeleted") == false
BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
// post => EF.Property<bool>(post, "IsDeleted") == false
var lambda = Expression.Lambda(compareExpression, parameter);
builder.Entity(entityType.ClrType).HasQueryFilter(lambda);
}
// Many to Many relationship - HostApplicationUser
builder.Entity<HostApplicationUser>()
.HasKey(bc => new { bc.HostID, bc.Id });
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.Host)
.WithMany(b => b.HostApplicationUsers)
.HasForeignKey(bc => bc.HostID);
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.HostApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ClubApplicationUser
builder.Entity<ClubApplicationUser>()
.HasKey(bc => new { bc.ClubID, bc.Id });
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.Club)
.WithMany(b => b.ClubApplicationUsers)
.HasForeignKey(bc => bc.ClubID);
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.ClubApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ApplicationUserRole
builder.Entity<ApplicationUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.ApplicationUserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
OnBeforeSaving();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
OnBeforeSaving();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void OnBeforeSaving()
{
if (_httpContextAccessor.HttpContext != null)
{
var userName = _httpContextAccessor.HttpContext.User.Identity.Name;
var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
// Added
var added = ChangeTracker.Entries().Where(v => v.State == EntityState.Added &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
added.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateCreated = DateTime.UtcNow;
((IBaseEntity)entry.Entity).CreatedBy = userId;
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Modified
var modified = ChangeTracker.Entries().Where(v => v.State == EntityState.Modified &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
modified.ForEach(entry =>
{
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
foreach (var entry in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Deleted &&
e.Metadata.GetProperties().Any(x => x.Name == "IsDeleted")))
{
switch (entry.State)
{
case EntityState.Added:
entry.CurrentValues["IsDeleted"] = false;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
}
else
{
// DbInitializer kicks in
}
}
}
}
Worked OK with standalone model/class ie. Club, Host, ApplicationUser ** BUT not bridging model/class ie. **ClubApplicationUser and HostApplicationUser
So we have to do manually on this (AssignClub.cshtml.cs for instance) by adding manually for creating/deleting as you can see below:
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
... the deleted.Count() below returning 0 ... and the same with creating etc:
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
AssignClub.cshtml.cs -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Data;
using AthlosifyWebArchery.Models;
using AthlosifyWebArchery.Pages.Administrators.TournamentBatches;
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using static AthlosifyWebArchery.Pages.Administrators.Users.AssignClubUserModel;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace AthlosifyWebArchery.Pages.Administrators.Users
{
//public class AssignClubUserModel : ClubNamePageModel
public class AssignClubUserModel : UserViewPageModel
{
private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public AssignClubUserModel(AthlosifyWebArchery.Data.ApplicationDbContext context,
IHttpContextAccessor httpContextAccessor
)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
}
public class AssignClubUserViewModel<ApplicationUser>
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public Guid ClubID { get; set; }
public Guid OriginalClubID { get; set; }
public byte RowVersion { get; set; }
}
[BindProperty]
public AssignClubUserViewModel<ApplicationUser> AssignClubUser { get; set; }
//public SelectList ClubNameSL { get; set; }
public async Task<IActionResult> OnGetAsync(Guid? id)
{
if (id == null)
return NotFound();
AssignClubUser = await _context.Users
.Include(u => u.ClubApplicationUsers)
.Where(t => t.Id == id.ToString())
.Select(t => new AssignClubUserViewModel<ApplicationUser>
{
Id = t.Id,
FirstName = t.FirstName,
LastName = t.LastName,
UserName = t.UserName,
ClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
OriginalClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
RowVersion = t.ClubApplicationUsers.ElementAt(0).RowVersion
}).SingleAsync();
if (AssignClubUser == null)
return NotFound();
// Use strongly typed data rather than ViewData.
//ClubNameSL = new SelectList(_context.Club, "ClubID", "Name");
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
public async Task<IActionResult> OnPostAsync(Guid id)
{
if (!ModelState.IsValid)
return Page();
// 1st approach:
// Modify the bridge model directly
/*var clubApplicationUserToUpdate = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
if (clubApplicationUserToUpdate == null)
return await HandleDeletedUser();
_context.Entry(clubApplicationUserToUpdate)
.Property("RowVersion").OriginalValue = AssignClubUser.RowVersion;
// This slightly tweek for this particular
// As the modified Change Track is not triggered
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastDateModified").CurrentValue = DateTime.UtcNow;
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastModifiedBy").CurrentValue = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (await TryUpdateModelAsync<ClubApplicationUser>(
clubApplicationUserToUpdate,
"AssignClubUser",
s => s.Id, s => s.ClubID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (ClubApplicationUser)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The club application user was deleted by another user.");
return Page();
}
var dbValues = (ClubApplicationUser)databaseEntry.ToObject();
await setDbErrorMessage(dbValues, clientValues, _context);
AssignClubUser.RowVersion = (byte)dbValues.RowVersion;
ModelState.Remove("User.RowVersion");
}
}
*/
// 2nd approach:
// Soft -Delete and Add
// Did the soft-deleting and managed to add a new one BUT then die the roll back (adding the old one)
// Result: Violation of PRIMARY KEY constraint 'PK_ClubApplicationUser'.
// Cannot insert duplicate key in object
// Due to duplicate key
/*var clubApplicatonUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString());
ClubApplicationUser clubApplicatonUserToAdd = new ClubApplicationUser();
clubApplicatonUserToAdd.Id = id.ToString();
clubApplicatonUserToAdd.ClubID = AssignClubUser.SelectedClubID;
//_context.Entry(clubApplicatonUserToRemove)
// .Property("RowVersion").OriginalValue = User.RowVersion;
if (clubApplicatonUserToRemove != null)
{
_context.ClubApplicationUser.Remove(clubApplicatonUserToRemove);
await _context.SaveChangesAsync();clubApplicationUserDeleted
_context.ClubApplicationUser.Add(clubApplicatonUserToAdd);
await _context.SaveChangesAsync();
}*/
//delete all club memberships and add new one
//var clubApplicationUserDeleted = await _context.ClubApplicationUser
// .FirstOrDefaultAsync(m => m.Id == id.ToString()
// && m.ClubID == AssignClubUser.ClubID && m.IsDeleted == );
var userID = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (AssignClubUser.ClubID != AssignClubUser.OriginalClubID)
{
var deletedClubApplicationUsers = _context.ClubApplicationUser.IgnoreQueryFilters()
.Where(post => post.Id == id.ToString()
&& post.ClubID == AssignClubUser.ClubID && EF.Property<bool>(post, "IsDeleted") == true);
if (deletedClubApplicationUsers.Count() > 0)
{
// Undo the deleted one
foreach (var deletedClubApplicationUser in deletedClubApplicationUsers)
{
var postEntry = _context.ChangeTracker.Entries<ClubApplicationUser>().First(entry => entry.Entity == deletedClubApplicationUser);
postEntry.Property("IsDeleted").CurrentValue = false;
postEntry.Property("LastDateModified").CurrentValue = DateTime.UtcNow;
postEntry.Property("LastModifiedBy").CurrentValue = userID;
postEntry.Property("DateDeleted").CurrentValue = null;
postEntry.Property("DeletedBy").CurrentValue = null;
}
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
else
{
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
_context.Entry(clubApplicationUserToRemove).State = EntityState.Deleted;
await _context.SaveChangesAsync();
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
// Added the new one
var newClubApplicationUser = new ClubApplicationUser()
{
Id = id.ToString(),
ClubID = AssignClubUser.ClubID,
DateCreated = DateTime.UtcNow,
CreatedBy = userID,
LastDateModified = DateTime.UtcNow,
LastModifiedBy = userID
};
_context.ClubApplicationUser.Add(newClubApplicationUser);
try
{
_context.Entry(newClubApplicationUser).State = EntityState.Added;
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
}
return RedirectToPage("./Index");
}
private async Task<IActionResult> HandleDeletedUser()
{
ClubApplicationUser deletedClubApplicationUser = new ClubApplicationUser();
ModelState.AddModelError(string.Empty,
"Unable to save. The club was deleted by another user.");
return Page();
}
private async Task setDbErrorMessage(ClubApplicationUser dbValues,
ClubApplicationUser clientValues, ApplicationDbContext context)
{
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
}
}
ClubApplicationUser.cs model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ClubApplicationUser
{
public Guid ClubID { get; set; }
public Club Club { get; set; }
public string Id { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public DateTime LastDateModified { get; set; }
public string LastModifiedBy { get; set; }
public DateTime? DateDeleted { get; set; }
public string DeletedBy { get; set; }
public bool IsDeleted { get; set; }
[Timestamp]
public byte RowVersion { get; set; }
[ForeignKey("CreatedBy")]
public ApplicationUser ClubApplicationCreatedUser { get; set; }
[ForeignKey("LastModifiedBy")]
public ApplicationUser ClubApplicationLastModifiedUser { get; set; }
}
}
Any ideas? The current solution is working OK BUT it just has extra routines to add these DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy manually for this bridging model/class.
Environment:
- .NET Core 2.2
- SQL Server
Updates 1:
We even add this before the saving and it didn't trigger the automatic ChangeTracker.Entries():
_context.Entry(newClubApplicationUser).State = EntityState.Deleted;
await _context.SaveChangesAsync();
Updates 2:
Added the ClubApplicationUser model above.
asp.net-core .net-core razor-pages
What is the defination for model? It would be helpful if you could share us a mini demo to reproduce your issue.
– Tao Zhou
Jan 14 at 5:35
@TaoZhou I've added the ClubApplicationUser model as per request.
– dcpartners
Jan 18 at 21:46
add a comment |
We are trying to streamline our automatic updating fields for DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy and worked OK by adding routine OnBeforeSaving in the ApplicationDBContext.cs and we also do the SOFT-DELETE for retaining the records (flagged as IsDeleted
approach instead) when we deleted:
ApplicationDBContext.cs -
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Models;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
namespace AthlosifyWebArchery.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string,
IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
IHttpContextAccessor httpContextAccessor
)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
public DbSet<AthlosifyWebArchery.Models.TournamentBatchItem> TournamentBatchItem { get; set; }
public DbSet<AthlosifyWebArchery.Models.TournamentBatch> TournamentBatch { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Host> Host { get; set; }
public DbSet<AthlosifyWebArchery.Models.HostApplicationUser> HostApplicationUser { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Club> Club { get; set; }
public DbSet<AthlosifyWebArchery.Models.ClubApplicationUser> ClubApplicationUser { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var entityType in builder.Model.GetEntityTypes())
{
// 1. Add the IsDeleted property
entityType.GetOrAddProperty("IsDeleted", typeof(bool));
// 2. Create the query filter
var parameter = Expression.Parameter(entityType.ClrType);
// EF.Property<bool>(post, "IsDeleted")
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
// EF.Property<bool>(post, "IsDeleted") == false
BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
// post => EF.Property<bool>(post, "IsDeleted") == false
var lambda = Expression.Lambda(compareExpression, parameter);
builder.Entity(entityType.ClrType).HasQueryFilter(lambda);
}
// Many to Many relationship - HostApplicationUser
builder.Entity<HostApplicationUser>()
.HasKey(bc => new { bc.HostID, bc.Id });
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.Host)
.WithMany(b => b.HostApplicationUsers)
.HasForeignKey(bc => bc.HostID);
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.HostApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ClubApplicationUser
builder.Entity<ClubApplicationUser>()
.HasKey(bc => new { bc.ClubID, bc.Id });
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.Club)
.WithMany(b => b.ClubApplicationUsers)
.HasForeignKey(bc => bc.ClubID);
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.ClubApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ApplicationUserRole
builder.Entity<ApplicationUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.ApplicationUserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
OnBeforeSaving();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
OnBeforeSaving();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void OnBeforeSaving()
{
if (_httpContextAccessor.HttpContext != null)
{
var userName = _httpContextAccessor.HttpContext.User.Identity.Name;
var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
// Added
var added = ChangeTracker.Entries().Where(v => v.State == EntityState.Added &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
added.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateCreated = DateTime.UtcNow;
((IBaseEntity)entry.Entity).CreatedBy = userId;
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Modified
var modified = ChangeTracker.Entries().Where(v => v.State == EntityState.Modified &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
modified.ForEach(entry =>
{
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
foreach (var entry in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Deleted &&
e.Metadata.GetProperties().Any(x => x.Name == "IsDeleted")))
{
switch (entry.State)
{
case EntityState.Added:
entry.CurrentValues["IsDeleted"] = false;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
}
else
{
// DbInitializer kicks in
}
}
}
}
Worked OK with standalone model/class ie. Club, Host, ApplicationUser ** BUT not bridging model/class ie. **ClubApplicationUser and HostApplicationUser
So we have to do manually on this (AssignClub.cshtml.cs for instance) by adding manually for creating/deleting as you can see below:
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
... the deleted.Count() below returning 0 ... and the same with creating etc:
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
AssignClub.cshtml.cs -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Data;
using AthlosifyWebArchery.Models;
using AthlosifyWebArchery.Pages.Administrators.TournamentBatches;
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using static AthlosifyWebArchery.Pages.Administrators.Users.AssignClubUserModel;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace AthlosifyWebArchery.Pages.Administrators.Users
{
//public class AssignClubUserModel : ClubNamePageModel
public class AssignClubUserModel : UserViewPageModel
{
private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public AssignClubUserModel(AthlosifyWebArchery.Data.ApplicationDbContext context,
IHttpContextAccessor httpContextAccessor
)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
}
public class AssignClubUserViewModel<ApplicationUser>
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public Guid ClubID { get; set; }
public Guid OriginalClubID { get; set; }
public byte RowVersion { get; set; }
}
[BindProperty]
public AssignClubUserViewModel<ApplicationUser> AssignClubUser { get; set; }
//public SelectList ClubNameSL { get; set; }
public async Task<IActionResult> OnGetAsync(Guid? id)
{
if (id == null)
return NotFound();
AssignClubUser = await _context.Users
.Include(u => u.ClubApplicationUsers)
.Where(t => t.Id == id.ToString())
.Select(t => new AssignClubUserViewModel<ApplicationUser>
{
Id = t.Id,
FirstName = t.FirstName,
LastName = t.LastName,
UserName = t.UserName,
ClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
OriginalClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
RowVersion = t.ClubApplicationUsers.ElementAt(0).RowVersion
}).SingleAsync();
if (AssignClubUser == null)
return NotFound();
// Use strongly typed data rather than ViewData.
//ClubNameSL = new SelectList(_context.Club, "ClubID", "Name");
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
public async Task<IActionResult> OnPostAsync(Guid id)
{
if (!ModelState.IsValid)
return Page();
// 1st approach:
// Modify the bridge model directly
/*var clubApplicationUserToUpdate = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
if (clubApplicationUserToUpdate == null)
return await HandleDeletedUser();
_context.Entry(clubApplicationUserToUpdate)
.Property("RowVersion").OriginalValue = AssignClubUser.RowVersion;
// This slightly tweek for this particular
// As the modified Change Track is not triggered
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastDateModified").CurrentValue = DateTime.UtcNow;
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastModifiedBy").CurrentValue = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (await TryUpdateModelAsync<ClubApplicationUser>(
clubApplicationUserToUpdate,
"AssignClubUser",
s => s.Id, s => s.ClubID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (ClubApplicationUser)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The club application user was deleted by another user.");
return Page();
}
var dbValues = (ClubApplicationUser)databaseEntry.ToObject();
await setDbErrorMessage(dbValues, clientValues, _context);
AssignClubUser.RowVersion = (byte)dbValues.RowVersion;
ModelState.Remove("User.RowVersion");
}
}
*/
// 2nd approach:
// Soft -Delete and Add
// Did the soft-deleting and managed to add a new one BUT then die the roll back (adding the old one)
// Result: Violation of PRIMARY KEY constraint 'PK_ClubApplicationUser'.
// Cannot insert duplicate key in object
// Due to duplicate key
/*var clubApplicatonUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString());
ClubApplicationUser clubApplicatonUserToAdd = new ClubApplicationUser();
clubApplicatonUserToAdd.Id = id.ToString();
clubApplicatonUserToAdd.ClubID = AssignClubUser.SelectedClubID;
//_context.Entry(clubApplicatonUserToRemove)
// .Property("RowVersion").OriginalValue = User.RowVersion;
if (clubApplicatonUserToRemove != null)
{
_context.ClubApplicationUser.Remove(clubApplicatonUserToRemove);
await _context.SaveChangesAsync();clubApplicationUserDeleted
_context.ClubApplicationUser.Add(clubApplicatonUserToAdd);
await _context.SaveChangesAsync();
}*/
//delete all club memberships and add new one
//var clubApplicationUserDeleted = await _context.ClubApplicationUser
// .FirstOrDefaultAsync(m => m.Id == id.ToString()
// && m.ClubID == AssignClubUser.ClubID && m.IsDeleted == );
var userID = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (AssignClubUser.ClubID != AssignClubUser.OriginalClubID)
{
var deletedClubApplicationUsers = _context.ClubApplicationUser.IgnoreQueryFilters()
.Where(post => post.Id == id.ToString()
&& post.ClubID == AssignClubUser.ClubID && EF.Property<bool>(post, "IsDeleted") == true);
if (deletedClubApplicationUsers.Count() > 0)
{
// Undo the deleted one
foreach (var deletedClubApplicationUser in deletedClubApplicationUsers)
{
var postEntry = _context.ChangeTracker.Entries<ClubApplicationUser>().First(entry => entry.Entity == deletedClubApplicationUser);
postEntry.Property("IsDeleted").CurrentValue = false;
postEntry.Property("LastDateModified").CurrentValue = DateTime.UtcNow;
postEntry.Property("LastModifiedBy").CurrentValue = userID;
postEntry.Property("DateDeleted").CurrentValue = null;
postEntry.Property("DeletedBy").CurrentValue = null;
}
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
else
{
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
_context.Entry(clubApplicationUserToRemove).State = EntityState.Deleted;
await _context.SaveChangesAsync();
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
// Added the new one
var newClubApplicationUser = new ClubApplicationUser()
{
Id = id.ToString(),
ClubID = AssignClubUser.ClubID,
DateCreated = DateTime.UtcNow,
CreatedBy = userID,
LastDateModified = DateTime.UtcNow,
LastModifiedBy = userID
};
_context.ClubApplicationUser.Add(newClubApplicationUser);
try
{
_context.Entry(newClubApplicationUser).State = EntityState.Added;
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
}
return RedirectToPage("./Index");
}
private async Task<IActionResult> HandleDeletedUser()
{
ClubApplicationUser deletedClubApplicationUser = new ClubApplicationUser();
ModelState.AddModelError(string.Empty,
"Unable to save. The club was deleted by another user.");
return Page();
}
private async Task setDbErrorMessage(ClubApplicationUser dbValues,
ClubApplicationUser clientValues, ApplicationDbContext context)
{
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
}
}
ClubApplicationUser.cs model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ClubApplicationUser
{
public Guid ClubID { get; set; }
public Club Club { get; set; }
public string Id { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public DateTime LastDateModified { get; set; }
public string LastModifiedBy { get; set; }
public DateTime? DateDeleted { get; set; }
public string DeletedBy { get; set; }
public bool IsDeleted { get; set; }
[Timestamp]
public byte RowVersion { get; set; }
[ForeignKey("CreatedBy")]
public ApplicationUser ClubApplicationCreatedUser { get; set; }
[ForeignKey("LastModifiedBy")]
public ApplicationUser ClubApplicationLastModifiedUser { get; set; }
}
}
Any ideas? The current solution is working OK BUT it just has extra routines to add these DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy manually for this bridging model/class.
Environment:
- .NET Core 2.2
- SQL Server
Updates 1:
We even add this before the saving and it didn't trigger the automatic ChangeTracker.Entries():
_context.Entry(newClubApplicationUser).State = EntityState.Deleted;
await _context.SaveChangesAsync();
Updates 2:
Added the ClubApplicationUser model above.
asp.net-core .net-core razor-pages
We are trying to streamline our automatic updating fields for DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy and worked OK by adding routine OnBeforeSaving in the ApplicationDBContext.cs and we also do the SOFT-DELETE for retaining the records (flagged as IsDeleted
approach instead) when we deleted:
ApplicationDBContext.cs -
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Models;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using Microsoft.AspNetCore.Identity;
namespace AthlosifyWebArchery.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string,
IdentityUserClaim<string>,
ApplicationUserRole, IdentityUserLogin<string>,
IdentityRoleClaim<string>, IdentityUserToken<string>>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
IHttpContextAccessor httpContextAccessor
)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
public DbSet<AthlosifyWebArchery.Models.TournamentBatchItem> TournamentBatchItem { get; set; }
public DbSet<AthlosifyWebArchery.Models.TournamentBatch> TournamentBatch { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Host> Host { get; set; }
public DbSet<AthlosifyWebArchery.Models.HostApplicationUser> HostApplicationUser { get; set; }
public virtual DbSet<AthlosifyWebArchery.Models.Club> Club { get; set; }
public DbSet<AthlosifyWebArchery.Models.ClubApplicationUser> ClubApplicationUser { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var entityType in builder.Model.GetEntityTypes())
{
// 1. Add the IsDeleted property
entityType.GetOrAddProperty("IsDeleted", typeof(bool));
// 2. Create the query filter
var parameter = Expression.Parameter(entityType.ClrType);
// EF.Property<bool>(post, "IsDeleted")
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
// EF.Property<bool>(post, "IsDeleted") == false
BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
// post => EF.Property<bool>(post, "IsDeleted") == false
var lambda = Expression.Lambda(compareExpression, parameter);
builder.Entity(entityType.ClrType).HasQueryFilter(lambda);
}
// Many to Many relationship - HostApplicationUser
builder.Entity<HostApplicationUser>()
.HasKey(bc => new { bc.HostID, bc.Id });
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.Host)
.WithMany(b => b.HostApplicationUsers)
.HasForeignKey(bc => bc.HostID);
builder.Entity<HostApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.HostApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ClubApplicationUser
builder.Entity<ClubApplicationUser>()
.HasKey(bc => new { bc.ClubID, bc.Id });
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.Club)
.WithMany(b => b.ClubApplicationUsers)
.HasForeignKey(bc => bc.ClubID);
builder.Entity<ClubApplicationUser>()
.HasOne(bc => bc.ApplicationUser)
.WithMany(c => c.ClubApplicationUsers)
.HasForeignKey(bc => bc.Id);
// Many to Many relationship - ApplicationUserRole
builder.Entity<ApplicationUserRole>(userRole =>
{
userRole.HasKey(ur => new { ur.UserId, ur.RoleId });
userRole.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
userRole.HasOne(ur => ur.User)
.WithMany(r => r.ApplicationUserRoles)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
});
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
OnBeforeSaving();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
OnBeforeSaving();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void OnBeforeSaving()
{
if (_httpContextAccessor.HttpContext != null)
{
var userName = _httpContextAccessor.HttpContext.User.Identity.Name;
var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
// Added
var added = ChangeTracker.Entries().Where(v => v.State == EntityState.Added &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
added.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateCreated = DateTime.UtcNow;
((IBaseEntity)entry.Entity).CreatedBy = userId;
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Modified
var modified = ChangeTracker.Entries().Where(v => v.State == EntityState.Modified &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
modified.ForEach(entry =>
{
((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
((IBaseEntity)entry.Entity).LastModifiedBy = userId;
});
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
foreach (var entry in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Deleted &&
e.Metadata.GetProperties().Any(x => x.Name == "IsDeleted")))
{
switch (entry.State)
{
case EntityState.Added:
entry.CurrentValues["IsDeleted"] = false;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
}
else
{
// DbInitializer kicks in
}
}
}
}
Worked OK with standalone model/class ie. Club, Host, ApplicationUser ** BUT not bridging model/class ie. **ClubApplicationUser and HostApplicationUser
So we have to do manually on this (AssignClub.cshtml.cs for instance) by adding manually for creating/deleting as you can see below:
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
... the deleted.Count() below returning 0 ... and the same with creating etc:
// Deleted
var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
deleted.ForEach(entry =>
{
((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
((IBaseEntity)entry.Entity).DeletedBy = userId;
});
AssignClub.cshtml.cs -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using AthlosifyWebArchery.Data;
using AthlosifyWebArchery.Models;
using AthlosifyWebArchery.Pages.Administrators.TournamentBatches;
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using static AthlosifyWebArchery.Pages.Administrators.Users.AssignClubUserModel;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace AthlosifyWebArchery.Pages.Administrators.Users
{
//public class AssignClubUserModel : ClubNamePageModel
public class AssignClubUserModel : UserViewPageModel
{
private readonly AthlosifyWebArchery.Data.ApplicationDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public AssignClubUserModel(AthlosifyWebArchery.Data.ApplicationDbContext context,
IHttpContextAccessor httpContextAccessor
)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
}
public class AssignClubUserViewModel<ApplicationUser>
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserName { get; set; }
public Guid ClubID { get; set; }
public Guid OriginalClubID { get; set; }
public byte RowVersion { get; set; }
}
[BindProperty]
public AssignClubUserViewModel<ApplicationUser> AssignClubUser { get; set; }
//public SelectList ClubNameSL { get; set; }
public async Task<IActionResult> OnGetAsync(Guid? id)
{
if (id == null)
return NotFound();
AssignClubUser = await _context.Users
.Include(u => u.ClubApplicationUsers)
.Where(t => t.Id == id.ToString())
.Select(t => new AssignClubUserViewModel<ApplicationUser>
{
Id = t.Id,
FirstName = t.FirstName,
LastName = t.LastName,
UserName = t.UserName,
ClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
OriginalClubID = t.ClubApplicationUsers.ElementAt(0).ClubID,
RowVersion = t.ClubApplicationUsers.ElementAt(0).RowVersion
}).SingleAsync();
if (AssignClubUser == null)
return NotFound();
// Use strongly typed data rather than ViewData.
//ClubNameSL = new SelectList(_context.Club, "ClubID", "Name");
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
public async Task<IActionResult> OnPostAsync(Guid id)
{
if (!ModelState.IsValid)
return Page();
// 1st approach:
// Modify the bridge model directly
/*var clubApplicationUserToUpdate = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
if (clubApplicationUserToUpdate == null)
return await HandleDeletedUser();
_context.Entry(clubApplicationUserToUpdate)
.Property("RowVersion").OriginalValue = AssignClubUser.RowVersion;
// This slightly tweek for this particular
// As the modified Change Track is not triggered
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastDateModified").CurrentValue = DateTime.UtcNow;
//_context.Entry(clubApplicationUserToUpdate)
// .Property("LastModifiedBy").CurrentValue = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (await TryUpdateModelAsync<ClubApplicationUser>(
clubApplicationUserToUpdate,
"AssignClubUser",
s => s.Id, s => s.ClubID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (ClubApplicationUser)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The club application user was deleted by another user.");
return Page();
}
var dbValues = (ClubApplicationUser)databaseEntry.ToObject();
await setDbErrorMessage(dbValues, clientValues, _context);
AssignClubUser.RowVersion = (byte)dbValues.RowVersion;
ModelState.Remove("User.RowVersion");
}
}
*/
// 2nd approach:
// Soft -Delete and Add
// Did the soft-deleting and managed to add a new one BUT then die the roll back (adding the old one)
// Result: Violation of PRIMARY KEY constraint 'PK_ClubApplicationUser'.
// Cannot insert duplicate key in object
// Due to duplicate key
/*var clubApplicatonUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString());
ClubApplicationUser clubApplicatonUserToAdd = new ClubApplicationUser();
clubApplicatonUserToAdd.Id = id.ToString();
clubApplicatonUserToAdd.ClubID = AssignClubUser.SelectedClubID;
//_context.Entry(clubApplicatonUserToRemove)
// .Property("RowVersion").OriginalValue = User.RowVersion;
if (clubApplicatonUserToRemove != null)
{
_context.ClubApplicationUser.Remove(clubApplicatonUserToRemove);
await _context.SaveChangesAsync();clubApplicationUserDeleted
_context.ClubApplicationUser.Add(clubApplicatonUserToAdd);
await _context.SaveChangesAsync();
}*/
//delete all club memberships and add new one
//var clubApplicationUserDeleted = await _context.ClubApplicationUser
// .FirstOrDefaultAsync(m => m.Id == id.ToString()
// && m.ClubID == AssignClubUser.ClubID && m.IsDeleted == );
var userID = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (AssignClubUser.ClubID != AssignClubUser.OriginalClubID)
{
var deletedClubApplicationUsers = _context.ClubApplicationUser.IgnoreQueryFilters()
.Where(post => post.Id == id.ToString()
&& post.ClubID == AssignClubUser.ClubID && EF.Property<bool>(post, "IsDeleted") == true);
if (deletedClubApplicationUsers.Count() > 0)
{
// Undo the deleted one
foreach (var deletedClubApplicationUser in deletedClubApplicationUsers)
{
var postEntry = _context.ChangeTracker.Entries<ClubApplicationUser>().First(entry => entry.Entity == deletedClubApplicationUser);
postEntry.Property("IsDeleted").CurrentValue = false;
postEntry.Property("LastDateModified").CurrentValue = DateTime.UtcNow;
postEntry.Property("LastModifiedBy").CurrentValue = userID;
postEntry.Property("DateDeleted").CurrentValue = null;
postEntry.Property("DeletedBy").CurrentValue = null;
}
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
else
{
// Removed the current one
var clubApplicationUserToRemove = await _context.ClubApplicationUser
.FirstOrDefaultAsync(m => m.Id == id.ToString()
&& m.ClubID == AssignClubUser.OriginalClubID);
clubApplicationUserToRemove.DateDeleted = DateTime.UtcNow;
clubApplicationUserToRemove.DeletedBy = userID;
_context.ClubApplicationUser.Remove(clubApplicationUserToRemove);
try
{
_context.Entry(clubApplicationUserToRemove).State = EntityState.Deleted;
await _context.SaveChangesAsync();
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
// Added the new one
var newClubApplicationUser = new ClubApplicationUser()
{
Id = id.ToString(),
ClubID = AssignClubUser.ClubID,
DateCreated = DateTime.UtcNow,
CreatedBy = userID,
LastDateModified = DateTime.UtcNow,
LastModifiedBy = userID
};
_context.ClubApplicationUser.Add(newClubApplicationUser);
try
{
_context.Entry(newClubApplicationUser).State = EntityState.Added;
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch
{
PopulateClubsDropDownList(_context, AssignClubUser.ClubID);
return Page();
}
}
}
return RedirectToPage("./Index");
}
private async Task<IActionResult> HandleDeletedUser()
{
ClubApplicationUser deletedClubApplicationUser = new ClubApplicationUser();
ModelState.AddModelError(string.Empty,
"Unable to save. The club was deleted by another user.");
return Page();
}
private async Task setDbErrorMessage(ClubApplicationUser dbValues,
ClubApplicationUser clientValues, ApplicationDbContext context)
{
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
}
}
ClubApplicationUser.cs model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace AthlosifyWebArchery.Models
{
public class ClubApplicationUser
{
public Guid ClubID { get; set; }
public Club Club { get; set; }
public string Id { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public DateTime DateCreated { get; set; }
public string CreatedBy { get; set; }
public DateTime LastDateModified { get; set; }
public string LastModifiedBy { get; set; }
public DateTime? DateDeleted { get; set; }
public string DeletedBy { get; set; }
public bool IsDeleted { get; set; }
[Timestamp]
public byte RowVersion { get; set; }
[ForeignKey("CreatedBy")]
public ApplicationUser ClubApplicationCreatedUser { get; set; }
[ForeignKey("LastModifiedBy")]
public ApplicationUser ClubApplicationLastModifiedUser { get; set; }
}
}
Any ideas? The current solution is working OK BUT it just has extra routines to add these DateCreated, CreatedBy, LastDateModified, LastModifiedBy, DateDeleted and DeletedBy manually for this bridging model/class.
Environment:
- .NET Core 2.2
- SQL Server
Updates 1:
We even add this before the saving and it didn't trigger the automatic ChangeTracker.Entries():
_context.Entry(newClubApplicationUser).State = EntityState.Deleted;
await _context.SaveChangesAsync();
Updates 2:
Added the ClubApplicationUser model above.
asp.net-core .net-core razor-pages
asp.net-core .net-core razor-pages
edited Jan 18 at 21:46
dcpartners
asked Jan 12 at 23:16
dcpartnersdcpartners
1,52783246
1,52783246
What is the defination for model? It would be helpful if you could share us a mini demo to reproduce your issue.
– Tao Zhou
Jan 14 at 5:35
@TaoZhou I've added the ClubApplicationUser model as per request.
– dcpartners
Jan 18 at 21:46
add a comment |
What is the defination for model? It would be helpful if you could share us a mini demo to reproduce your issue.
– Tao Zhou
Jan 14 at 5:35
@TaoZhou I've added the ClubApplicationUser model as per request.
– dcpartners
Jan 18 at 21:46
What is the defination for model? It would be helpful if you could share us a mini demo to reproduce your issue.
– Tao Zhou
Jan 14 at 5:35
What is the defination for model? It would be helpful if you could share us a mini demo to reproduce your issue.
– Tao Zhou
Jan 14 at 5:35
@TaoZhou I've added the ClubApplicationUser model as per request.
– dcpartners
Jan 18 at 21:46
@TaoZhou I've added the ClubApplicationUser model as per request.
– dcpartners
Jan 18 at 21:46
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54164758%2fdifferent-behaviour-between-bridging-and-standalone-model-class%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54164758%2fdifferent-behaviour-between-bridging-and-standalone-model-class%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
What is the defination for model? It would be helpful if you could share us a mini demo to reproduce your issue.
– Tao Zhou
Jan 14 at 5:35
@TaoZhou I've added the ClubApplicationUser model as per request.
– dcpartners
Jan 18 at 21:46