Different behaviour between bridging and standalone model/class - asp.net-core

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.

Related

IdentityResult validation error not showing to UI

I am kinda new to this, so my apologizes in advance.
so after (result = await _userManager.UpdateAsync(user);
I get in the logging (DuplicateUserName), be cause the name already exist in another user, So I want to return the validation error to the blazor component and show it to the UI, same validation error that is in the logger
warn: Microsoft.AspNetCore.Identity.UserManager[13]
User validation failed: DuplicateUserName.
[HttpPost("EditSaveUser")]
public async Task<ActionResult>EditSaveUser(EditUserModel model)
{
var user = await _userManager.FindByIdAsync(model.Id);
if (user == null)
{
return BadRequest($"User with Id = {model.Id} cannot be found");
}
else
{
user.Email = model.Email;
user.UserName = model.UserName;
user.City = model.City;
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return Ok(result);
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
return BadRequest(ModelState);
}
}
}
this is the model that has the UserName that i want to apply the validation from the API to it
public class EditUserModel
{
public string Id { get; set; }
[Required]
public string UserName { get; set; } = string.Empty;
[Required]
public string Email { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public List<string> Claims { get; set; } = new();
public IList<string> Roles { get; set; }
}
The model inherent from IdentityUser
public class ApplicationUser : IdentityUser
{
public string City { get; set; } = string.Empty;
}
here i get the response from the API Post
public async Task EditSaveUser(EditUserModel model)
{
var result = await _httpClient.PostAsJsonAsync($"https://localhost:7023/api/administration/EditSaveUser/", model);
if (result.IsSuccessStatusCode == false)
{
//TODO
}
}
here is the code on the blazor component
private EditUserModel model = new();
protected async override Task OnInitializedAsync()
{
var result = await AdminService.EditUser(Id);
if (result != null)
{
model = result;
}
}
private async Task HandleSubmit()
{
await AdminService.EditSaveUser(model);
}
ofc i use
<EditForm Model="model" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator/>
<ValidationSummary />
<ValidationMessage For="#(()=> model.Email)" />
in Program.cs
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<ApplicationUser>>();
builder.Services.AddScoped<IAdminService,AdminService>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddHttpClient();
builder.Services.AddControllers();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProvider>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["JwtIssuer"],
ValidAudience = builder.Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSecurityKey"]))
};
});
The model for ApplicationDbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
foreach (var foreignKey in builder.Model.GetEntityTypes().SelectMany(x=> x.GetForeignKeys()))
{
foreignKey.DeleteBehavior = DeleteBehavior.Restrict;
}
}
}
I was expecting the UI to get the validation error be cause I am using validation

Why I can only update a user's data in ASP.Net Core with Identity?

I was testing my application and I saw that I can only update the information of a single user and not that of the others, the application does not give me an error message or anything, it only reloads the page and the data does not reach the database data, whereas with a single user this does not happen.
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UniJobs.Data;
using UniJobs.Models;
namespace UniJobs.Areas.Identity.Pages.Account.Manage
{
public partial class IndexModel : PageModel
{
private readonly UserManager<Usuarios> _userManager;
private readonly SignInManager<Usuarios> _signInManager;
private readonly ApplicationDbContext _context;
private readonly IWebHostEnvironment _webHostEnvironment;
public IndexModel(
UserManager<Usuarios> userManager,
SignInManager<Usuarios> signInManager,
ApplicationDbContext context,
IWebHostEnvironment webHostEnvironment)
{
_userManager = userManager;
_signInManager = signInManager;
this._context = context;
this._webHostEnvironment = webHostEnvironment;
}
public string Username { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Display(Name = "Imagen de perfil")]
public IFormFile InputImagen { get; set; }
[Display(Name = "¿Quién Soy?")]
public string QuienSoy { get; set; }
[Display(Name = "Sobre mis habilidades")]
public string SobreHabilidades { get; set; }
[Display(Name = "Sobre mis aptitudes")]
public string SobreAptitudes { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Nombre")]
public string FirstName { get; set; }
[Display(Name = "Provincia")]
public int? Provincia { get; set; }
[Display(Name = "Mi universidad")]
public int? MiUni { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Apellido")]
public string LastName { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Fecha de nacimiento")]
public DateTime? BirthDate { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
private async Task LoadAsync(Usuarios user)
{
var userName = await _userManager.GetUserNameAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
user.ID_Uni = _context.ListaEstudiantesUniversidades.Where(U => U.FK_UsuarioID == user.Id)
.Select(U => U.FK_UniversidadID).FirstOrDefault();
Username = userName;
Input = new InputModel
{
FirstName = user.FirstName,
LastName = user.LastName,
BirthDate = user.BirthDate,
Provincia = user.FK_ProvinciaID,
PhoneNumber = phoneNumber,
MiUni = user.ID_Uni,
QuienSoy = user.QuienSoy,
SobreHabilidades = user.SobreHabilidades,
SobreAptitudes = user.SobreAptitudes
};
}
public async Task<IActionResult> OnGetAsync()
{
ViewData["FK_ProvinciaID"] = new SelectList(_context.Provincias, "ProvinciasID", "Provincia");
ViewData["Universidad"] = new SelectList(_context.Universidades, "UniversidadesID", "NombreUniversidad");
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
StatusMessage = "Unexpected error when trying to set phone number.";
return RedirectToPage();
}
}
if (Input.InputImagen != null)
{
//Save image to wwwroot/image
string wwwRootPath = _webHostEnvironment.WebRootPath;
string ImageName = Path.GetFileNameWithoutExtension(Input.InputImagen.FileName);
string ExtensionImage = Path.GetExtension(Input.InputImagen.FileName);
user.ProfileImage = ImageName = ImageName + DateTime.Now.ToString("yyyyMMddHHmmssfff") + ExtensionImage;
string path = Path.Combine(wwwRootPath + "/UsersImages/Users/", ImageName);
using (var fileStream = new FileStream(path, FileMode.Create))
{
await Input.InputImagen.CopyToAsync(fileStream);
}
//Insert record
}
if (Input.MiUni != _context.ListaEstudiantesUniversidades.Where(U => U.FK_UsuarioID == user.Id)
.Select(U => U.FK_UniversidadID).FirstOrDefault())
{
var UniID = _context.ListaEstudiantesUniversidades.Where(U => U.FK_UsuarioID == user.Id)
.Select(U => U.ListaEstudiantesUniversidadesID).FirstOrDefault();
var StudentUni = await _context.ListaEstudiantesUniversidades.FindAsync(UniID);
_context.ListaEstudiantesUniversidades.Remove(StudentUni);
await _context.SaveChangesAsync();
var Uni = _context.ListaEstudiantesUniversidades.Add(new ListaEstudiantesUniversidades
{
FK_UniversidadID = Input.MiUni.Value,
FK_UsuarioID = user.Id
});
await _context.SaveChangesAsync();
}
if (Input.FirstName != user.FirstName)
{
user.FirstName = Input.FirstName;
}
if (Input.LastName != user.LastName)
{
user.LastName = Input.LastName;
}
if (Input.BirthDate != user.BirthDate)
{
user.BirthDate = Input.BirthDate;
}
if (Input.Provincia != user.FK_ProvinciaID)
{
user.FK_ProvinciaID = Input.Provincia;
}
if (Input.QuienSoy != user.QuienSoy)
{
user.QuienSoy = Input.QuienSoy;
}
if (Input.SobreHabilidades != user.SobreHabilidades)
{
user.SobreHabilidades = Input.SobreHabilidades;
}
if (Input.SobreAptitudes != user.SobreAptitudes)
{
user.SobreAptitudes = Input.SobreAptitudes;
}
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
}
}
This is the code of the Identity Index class

How to create audit log / audit trail in asp.net mvc

When we are used code first or entity framework then there is easiest way to audit trail the actions like add , update and delete.
Create a class for capture the changes or track the changes when entity added, modifies or deleted.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Web;
namespace MVC_AuditTrail.Models
{
public class AuditTrailFactory
{
private DbContext context;
public AuditTrailFactory(DbContext context)
{
this.context = context;
}
public Audit GetAudit(DbEntityEntry entry)
{
Audit audit = new Audit();
// var user = (User)HttpContext.Current.Session[":user"];
audit.UserId = "swapnil";// user.UserName;
audit.TableName = GetTableName(entry);
audit.UpdateDate = DateTime.Now;
audit.TableIdValue = GetKeyValue(entry);
//entry is Added
if (entry.State == EntityState.Added)
{
var newValues = new StringBuilder();
SetAddedProperties(entry, newValues);
audit.NewData = newValues.ToString();
audit.Actions = AuditActions.I.ToString();
}
//entry in deleted
else if (entry.State == EntityState.Deleted)
{
var oldValues = new StringBuilder();
SetDeletedProperties(entry, oldValues);
audit.OldData = oldValues.ToString();
audit.Actions = AuditActions.D.ToString();
}
//entry is modified
else if (entry.State == EntityState.Modified)
{
var oldValues = new StringBuilder();
var newValues = new StringBuilder();
SetModifiedProperties(entry, oldValues, newValues);
audit.OldData = oldValues.ToString();
audit.NewData = newValues.ToString();
audit.Actions = AuditActions.U.ToString();
}
return audit;
}
private void SetAddedProperties(DbEntityEntry entry, StringBuilder newData)
{
foreach (var propertyName in entry.CurrentValues.PropertyNames)
{
var newVal = entry.CurrentValues[propertyName];
if (newVal != null)
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
}
}
if (newData.Length > 0)
newData = newData.Remove(newData.Length - 3, 3);
}
private void SetDeletedProperties(DbEntityEntry entry, StringBuilder oldData)
{
DbPropertyValues dbValues = entry.GetDatabaseValues();
foreach (var propertyName in dbValues.PropertyNames)
{
var oldVal = dbValues[propertyName];
if (oldVal != null)
{
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
if (oldData.Length > 0)
oldData = oldData.Remove(oldData.Length - 3, 3);
}
private void SetModifiedProperties(DbEntityEntry entry, StringBuilder oldData, StringBuilder newData)
{
DbPropertyValues dbValues = entry.GetDatabaseValues();
foreach (var propertyName in entry.OriginalValues.PropertyNames)
{
var oldVal = dbValues[propertyName];
var newVal = entry.CurrentValues[propertyName];
if (oldVal != null && newVal != null && !Equals(oldVal, newVal))
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
if (oldData.Length > 0)
oldData = oldData.Remove(oldData.Length - 3, 3);
if (newData.Length > 0)
newData = newData.Remove(newData.Length - 3, 3);
}
public long? GetKeyValue(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
long id = 0;
if (objectStateEntry.EntityKey.EntityKeyValues != null)
id = Convert.ToInt64(objectStateEntry.EntityKey.EntityKeyValues[0].Value);
return id;
}
private string GetTableName(DbEntityEntry dbEntry)
{
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
return tableName;
}
}
public enum AuditActions
{
I,
U,
D
}
}
Then create audit table entity and context class.
And Override savechanges method in this method get audit changes and save before base entity saved.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Web;
namespace MVC_AuditTrail.Models
{
public class Student
{
public int StudentID { get; set; }
public string Name { get; set; }
public string mobile { get; set; }
}
public class Audit
{
public long Id { get; set; }
public string TableName { get; set; }
public string UserId { get; set; }
public string Actions { get; set; }
public string OldData { get; set; }
public string NewData { get; set; }
public Nullable<long> TableIdValue { get; set; }
public Nullable<System.DateTime> UpdateDate { get; set; }
}
public class StdContext : DbContext
{
private AuditTrailFactory auditFactory;
private List<Audit> auditList = new List<Audit>();
private List<DbEntityEntry> objectList = new List<DbEntityEntry>();
public StdContext() : base("stdConnection")
{
Database.SetInitializer<StdContext>(new CreateDatabaseIfNotExists<StdContext>());
}
public DbSet<Student> Student { get; set; }
public DbSet<Audit> Audit { get; set; }
public override int SaveChanges()
{
auditList.Clear();
objectList.Clear();
auditFactory = new AuditTrailFactory(this);
var entityList = ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified);
foreach (var entity in entityList)
{
Audit audit = auditFactory.GetAudit(entity);
bool isValid = true;
if (entity.State == EntityState.Modified && string.IsNullOrWhiteSpace(audit.NewData) && string.IsNullOrWhiteSpace(audit.OldData))
{
isValid = false;
}
if (isValid)
{
auditList.Add(audit);
objectList.Add(entity);
}
}
var retVal = base.SaveChanges();
if (auditList.Count > 0)
{
int i = 0;
foreach (var audit in auditList)
{
if (audit.Actions == AuditActions.I.ToString())
audit.TableIdValue = auditFactory.GetKeyValue(objectList[i]);
this.Audit.Add(audit);
i++;
}
base.SaveChanges();
}
return retVal;
}
}
}

directive to be used to include viewmodel in mvc4

I have gone through the MVC3 tutorial for displaying the related data in asp.net page.
It has a viewmodel:
using System;
using System.Collections.Generic;
using ContosoUniversity.Models;
namespace ContosoUniversity.ViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
and a controller, with actions:
using ContosoUniversity.ViewModels;
public ActionResult Index(Int32? id, Int32? courseID)
{
var viewModel = new InstructorIndexData();
viewModel.Instructors = db.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses.Select(c => c.Department))
.OrderBy(i => i.LastName);
if (id != null)
{
ViewBag.InstructorID = id.Value;
viewModel.Courses = viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses;
}
if (courseID != null)
{
ViewBag.CourseID = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(x => x.CourseID == courseID).Single ().Enrollments;
}
return View(viewModel);
}
when i tried in MVC4 it is such that using ContosoUniversity.ViewModels; is not existing
and at
viewModel.Courses = viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses;
it is getting error as cannot convert viewmodel.instructors to IEnumerable<viewmodel.Instructors>

Fluent Nhibernate: Trying to create entity with composite key that is also the keys for two references

The references are unidirectional. The table (StoreProduct) for this entity is actually a join table that has these fields:
Store_id
Product_id
ExtraBit
So I went with an entity having a compoundID (store_id and product_id) and the ExtraBit is just a string:
public class StoreProduct
{
protected StoreProduct():this(null,null,null){ }
public StoreProduct(Store c_Store, Product c_Product, String c_ExtraBit)
{
Store = c_Store;
Product = c_Product;
ExtraBit = c_ExtraBit;
}
public virtual int Product_id { get; set; }
public virtual int Store_id { get; set; }
public virtual Store Store { get; set; }
public virtual Product Product { get; set; }
public virtual String ExtraBit { get; set; }
public override int GetHashCode()
{
return Store.GetHashCode() + Product.GetHashCode();
}
public override bool Equals(object obj)
{
StoreProduct obj_StoreProduct;
obj_StoreProduct = obj as StoreProduct;
if (obj_StoreProduct == null)
{
return false;
}
if (obj_StoreProduct.Product != this.Product && obj_StoreProduct.Store != this.Store)
{
return false;
}
return true;
}
}
And the mapping:
public class Order_DetailMap : ClassMap<StoreProduct>
{
public Order_DetailMap()
{
Table("StoreProduct");
LazyLoad();
CompositeId().KeyProperty(x => x.Store_id).KeyProperty(x => x.Product_id);
References(x => x.Store).ForeignKey("Store_id").Cascade.All();
References(x => x.Product).ForeignKey("Product_id").Cascade.All();
Map(x => x.ExtraBit);
}
}
It doesn't work though, when I tried saving the StoreProduct and its newly created Store and product. Can anyone help? Here is some output:
Unhandled Exception: System.ArgumentOutOfRangeException: Index was out of range.
Must be non-negative and less than the size of the collection.
Parameter name: index
at System.ThrowHelper.ThrowArgumentOutOfRangeException()
at System.Data.SQLite.SQLiteParameterCollection.GetParameter(Int32 index)
at System.Data.Common.DbParameterCollection.System.Collections.IList.get_Item
(Int32 index)
at NHibernate.Type.Int32Type.Set(IDbCommand rs, Object value, Int32 index) in
d:\CSharp\NH\NH\nhibernate\src\NHibernate\Type\Int32Type.cs:line 60
at NHibernate.Type.NullableType.NullSafeSet(IDbCommand cmd, Object value, Int
32 index) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Type\NullableType.cs:line
180
at NHibernate.Type.NullableType.NullSafeSet(IDbCommand st, Object value, Int3
2 index, ISessionImplementor session) in d:\CSharp\NH\NH\nhibernate\src\NHiberna
te\Type\NullableType.cs:line 139
at NHibernate.Type.ComponentType.NullSafeSet(IDbCommand st, Object value, Int
32 begin, ISessionImplementor session) in d:\CSharp\NH\NH\nhibernate\src\NHibern
ate\Type\ComponentType.cs:line 221
at NHibernate.Persister.Entity.AbstractEntityPersister.Dehydrate(Object id, O
bject[] fields, Object rowId, Boolean[] includeProperty, Boolean[][] includeColu
mns, Int32 table, IDbCommand statement, ISessionImplementor session, Int32 index
) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPe
rsister.cs:line 2418
Edit: Thanks to the help bellow I seem to have a decent solution:
Store Mapping and Class:
namespace compoundIDtest.Domain.Mappings
{
public class StoreMap : ClassMap<Store>
{
public StoreMap()
{
Id(x => x.Id).Column("Store_id");
Map(x => x.Name);
HasMany(x => x.Staff)
.Inverse()
.Cascade.All();
HasManyToMany(x => x.Products)
.Cascade.All()
.Table("StoreProduct");
}
}
}
namespace compoundIDtest.Domain.Entities
{
public class Store
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Product> Products { get; set; }
public virtual IList<Employee> Staff { get; set; }
public virtual IList<StoreProduct> StoreProducts { get; set; }
public Store()
{
Products = new List<Product>();
Staff = new List<Employee>();
}
public virtual void AddProduct(Product product)
{
product.StoresStockedIn.Add(this);
Products.Add(product);
}
public virtual void AddEmployee(Employee employee)
{
employee.Store = this;
Staff.Add(employee);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override bool Equals(object obj)
{
Store obj_Store;
obj_Store = obj as Store;
if (obj_Store == null)
{
return false;
}
if (obj_Store.Name != this.Name)
{
return false;
}
return true;
}
}
}
Product Mapping And Class
namespace compoundIDtest.Domain.Mappings
{
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id).Column("Product_id");
Map(x => x.Name);
Map(x => x.Price);
HasManyToMany(x => x.StoresStockedIn)
.Cascade.All()
.Inverse()
.Table("StoreProduct");
}
}
}
namespace compoundIDtest.Domain.Entities
{
public class Product
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual double Price { get; set; }
public virtual IList<Store> StoresStockedIn { get; set; }
public virtual IList<StoreProduct> StoreProducts { get; set; }
public Product()
{
StoresStockedIn = new List<Store>();
StoreProducts = new List<StoreProduct>();
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override bool Equals(object obj)
{
Product obj_Product;
obj_Product = obj as Product;
if (obj_Product == null)
{
return false;
}
if (obj_Product.Name != this.Name)
{
return false;
}
return true;
}
}
}
And StoreProduct
namespace compoundIDtest.Domain.Mappings
{
public class Order_DetailMap : ClassMap<StoreProduct>
{
public Order_DetailMap()
{
Table("StoreProduct");
LazyLoad();
CompositeId().KeyReference(x => x.Store, "Store_id").KeyReference(x => x.Product, "Product_id");
References(x => x.Store, "Store_id").Not.Update().Not.Insert().Cascade.All();
References(x => x.Product, "Product_id").Not.Update().Not.Insert().Cascade.All();
Map(x => x.ExtraBit);
}
}
}
namespace compoundIDtest.Domain.Entities
{
public class StoreProduct
{
public StoreProduct(){}
public virtual Store Store { get; set; }
public virtual Product Product { get; set; }
public virtual String ExtraBit { get; set; }
public override int GetHashCode()
{
if (this.ExtraBit != null)
{
return Store.GetHashCode() + Product.GetHashCode() + ExtraBit.GetHashCode();
}
return Store.GetHashCode() + Product.GetHashCode();
}
public override bool Equals(object obj)
{
StoreProduct obj_StoreProduct;
obj_StoreProduct = obj as StoreProduct;
if (obj_StoreProduct == null)
{
return false;
}
if (obj_StoreProduct.Product != this.Product && obj_StoreProduct.Store != this.Store && obj_StoreProduct.ExtraBit != this.ExtraBit)
{
return false;
}
return true;
}
}
}
And here is code for an app to test the above:
using System;
using System.IO;
using compoundIDtest.Domain.Entities;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Conventions;
namespace compoundIDtest
{
class Program
{
private const string DbFile = "firstProgram.db";
static void Main()
{
// create our NHibernate session factory
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
// populate the database
using (var transaction = session.BeginTransaction())
{
// create a couple of Stores each with some Products and Employees
var barginBasin = new Store { Name = "Bargin Basin" };
var superMart = new Store { Name = "SuperMart" };
var CornerShop = new Store { Name = "Corner Shop" };
var potatoes = new Product { Name = "Potatoes", Price = 3.60 };
var fish = new Product { Name = "Fish", Price = 4.49 };
var milk = new Product { Name = "Milk", Price = 0.79 };
var bread = new Product { Name = "Bread", Price = 1.29 };
var cheese = new Product { Name = "Cheese", Price = 2.10 };
var waffles = new Product { Name = "Waffles", Price = 2.41 };
var poison = new Product { Name = "Poison", Price = 1.50 };
var daisy = new Employee { FirstName = "Daisy", LastName = "Harrison" };
var jack = new Employee { FirstName = "Jack", LastName = "Torrance" };
var sue = new Employee { FirstName = "Sue", LastName = "Walkters" };
var bill = new Employee { FirstName = "Bill", LastName = "Taft" };
var joan = new Employee { FirstName = "Joan", LastName = "Pope" };
var storeproduct = new StoreProduct { Store = CornerShop, Product = poison, ExtraBit = "Extra Bit"};
//session.SaveOrUpdate(CornerShop);
//session.SaveOrUpdate(poison);
session.Save(storeproduct);
// add products to the stores, there's some crossover in the products in each
// store, because the store-product relationship is many-to-many
AddProductsToStore(barginBasin, potatoes, fish, milk, bread, cheese);
AddProductsToStore(superMart, bread, cheese, waffles);
// add employees to the stores, this relationship is a one-to-many, so one
// employee can only work at one store at a time
AddEmployeesToStore(barginBasin, daisy, jack, sue);
AddEmployeesToStore(superMart, bill, joan);
// save both stores, this saves everything else via cascading
session.SaveOrUpdate(barginBasin);
session.SaveOrUpdate(superMart);
//session.SaveOrUpdate(CornerShop);
//session.SaveOrUpdate(poison);
//session.SaveOrUpdate(storeproduct);
transaction.Commit();
}
}
using (var session = sessionFactory.OpenSession())
{
// retreive all stores and display them
using (var transaction = session.BeginTransaction())
{
var products = session.CreateCriteria(typeof(Product))
.List<Product>();
foreach (var product in products)
{
product.Price = 100;
session.SaveOrUpdate(product);
}
var storeproducts = session.CreateCriteria(typeof(StoreProduct)).List<StoreProduct>();
foreach (StoreProduct storeproduct in storeproducts)
{
if (storeproduct.Store.Name == "SuperMart")
{
storeproduct.ExtraBit = "Thank you, come again";
}
session.SaveOrUpdate(storeproduct);
}
transaction.Commit();
}
}
Console.ReadKey();
}
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SQLiteConfiguration.Standard
.UsingFile(DbFile))
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<Program>())
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
private static void BuildSchema(Configuration config)
{
// delete the existing db on each run
if (File.Exists(DbFile))
File.Delete(DbFile);
// this NHibernate tool takes a configuration (with mapping info in)
// and exports a database schema from it
new SchemaExport(config)
.Create(false, true);
}
private static void WriteStorePretty(Store store)
{
Console.WriteLine(store.Name);
Console.WriteLine(" Products:");
foreach (var product in store.Products)
{
Console.WriteLine(" " + product.Name);
}
Console.WriteLine(" Staff:");
foreach (var employee in store.Staff)
{
Console.WriteLine(" " + employee.FirstName + " " + employee.LastName);
}
Console.WriteLine();
}
public static void AddProductsToStore(Store store, params Product[] products)
{
foreach (var product in products)
{
store.AddProduct(product);
}
}
public static void AddEmployeesToStore(Store store, params Employee[] employees)
{
foreach (var employee in employees)
{
store.AddEmployee(employee);
}
}
}
}
I had a mapping pretty much identical to this and the way I ended up mapping it was like this:
public class Order_DetailMap : ClassMap<StoreProduct>
{
public Order_DetailMap()
{
Table("StoreProduct");
CompositeId()
.KeyReference(x => x.Store, "Store_id")
.KeyReference(x => x.Product, "Product_id");
Map(x => x.ExtraBit);
}
}
Inside of my Store and Product classes I have add and remove methods that make the creation of this middle class almost invisible. Example below:
public class Store
{
public IList<StoreProduct> StoreProducts { get; set; }
//Other properties and Constructors
public virtual void AddProduct(Product productToAdd, string extraBit)
{
StoreProduct newStoreProduct = new StoreProduct(this, productToAdd, extraBit);
storeProducts.Add(newStoreProduct);
}
}
In addition to the above I had HasMany's to a StoreProduct collection in my Store and Product classes that are set to Cascade.AllDeleteOrphan()
I was never able to be able to map the StoreProduct such that when it was saved by itself it would create a new Store and a new Product. I had to eventually map it like the above. So your Store or Product will need to exist before you actually create the relationship (StoreProduct) between them depending on which side you are creating your new StoreProduct from.
Edit:
You may also be able to map it like this to achieve what you are wanting:
public class Order_DetailMap : ClassMap<StoreProduct>
{
public Order_DetailMap()
{
Table("StoreProduct");
CompositeId()
.KeyReference(x => x.Store, "Store_id")
.KeyReference(x => x.Product, "Product_id");
References(x => x.Store, "Store_id")
.Not.Update()
.Not.Insert()
.Cascade.All();
References(x => x.Product, "Product_id")
.Not.Update()
.Not.Insert()
.Cascade.All();
Map(x => x.ExtraBit);
}
}