I have this db configuration
public class AppDbContext : DbContext
{
public AppDbContext(string connectionStringOrName)
: base(connectionStringOrName)
{
Database.SetInitializer(new AppDbInitializer());
}
public AppDbContext()
: this("name=AppDbContext")
{
}
public DbSet<User> Users { get; set; }
public DbSet<Log> Logs { get; set; }
}
and I have this migration configuration
public class AppDbInitializer : MigrateDatabaseToLatestVersion<AppDbContext,AppDbMigrationConfiguration>
{
}
public class AppDbMigrationConfiguration : DbMigrationsConfiguration<AppDbContext>
{
public AppDbMigrationConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(AppDbContext context)
{
if (context.Users.Any()) return;
AddAdmin(context, "Admin", "admin#test.com");
}
}
And I added another field to Log entity.
Can Entity Framework automatically detect and apply changes?
If Automatic Migrations are enabled, it should auto detect any small changes in the model.
But for larger changes, eg addition of new entity, I have seen to manually apply migration, which you can do with "Add-Migration" and then running "Update-Database"
Related
I need to log certain user actions, such as Login, logout, CRUD, etc. and save to a table called AuditRecords. I have a DbContext called AuditContext:
I have learned that applying an ActionFilter and decorate a controllermethod with it is the way to go. The AuditContext is causing a red squiggly asking for the 'options', which baffles me. I have tried DI, but got the same results. Any thoughts? I am on DotNet 5.01, but I tried it withg DotNet 6 as well with the same results.
EDIT
I changed the AuditAttribute and Context to make it as straightforward as possible and by using Dependency Injection, as advised. But the problem is still not solved.
public class Audit
{
//Audit Properties
public Guid AuditID { get; set; }
public string UserName { get; set; }
public string IPAddress { get; set; }
public string AreaAccessed { get; set; }
public DateTime Timestamp { get; set; }
//Default Constructor
public Audit() { }
}
public class AuditContext : DbContext
{
public DbSet<Audit> AuditRecords { get; set; }
}
public class HomeController : Controller
{
//red squiggly under [Audit] decoration with error of no formal
//parameter 'context' in AuditAttribute.AuditAttribute(AuditContext)
[Audit]
public IActionResult Privacy()
{
return View();
}
//AuditAttribute class
using Microsoft.AspNetCore.Mvc.Filters;
using System;
namespace LoggingDemoMVC.Filters
{
public class AuditAttribute : ActionFilterAttribute
{
private readonly AuditContext _context;
public AuditAttribute(AuditContext context)
{
_context = context;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Stores the Request in an Accessible object
var request = filterContext.HttpContext.Request;
//Generate an audit
Audit audit = new Audit()
{
//Your Audit Identifier
AuditID = Guid.NewGuid(),
//Our Username (if available)
UserName = (filterContext.HttpContext.User.Identity.IsAuthenticated) ? filterContext.HttpContext.User.Identity.Name : "Anonymous",
//The IP Address of the Request
IPAddress = request.Path,
//The URL that was accessed
AreaAccessed = request.HttpContext.Request.Path,
//Creates our Timestamp
Timestamp = DateTime.UtcNow
};
//Stores the Audit in the Database
_context.AuditRecords.Add(audit);
_context.SaveChanges();
//Finishes executing the Action as normal
base.OnActionExecuting(filterContext);
}
}
}
WebAppContext expects a DbContext<WebAppContext> to be passed into its constructor . However , you are calling the constructor without providing anything :
WebAppContext _context = new WebAppContext()
To fix the issue , you can just plug into the Dependency Injection system that you've set up
public class CustomFilter : IActionFilter
{
private readonly WebAppContext _context;
public CustomFilter(WebAppContext context)
{
_context = context;
}
public void OnActionExecuting(ActionExecutingContext context)
{
//.......
_context.AuditRecords.Add(...);
_context.SaveChanges();
}
}
Simple Demo :
model
public class Audit
{
public int ID { get; set; }
public string Name { get; set; }
}
DataContext
public class WebAppContext : DbContext
{
public WebAppContext(DbContextOptions<WebAppContext> options) : base(options)
{
}
public DbSet<Audit> AuditRecords { get; set; }
}
Custom Filter
public class CustomFilter :Attribute,IActionFilter
{
private readonly WebAppContext _context;
public CustomFilter(WebAppContext context)
{
_context = context;
}
public void OnActionExecuted(ActionExecutedContext context)
{
Console.WriteLine("end");
}
public void OnActionExecuting(ActionExecutingContext context)
{
Audit audit = new Audit();
audit.Name = "mike";
_context.AuditRecords.Add(audit);
_context.SaveChanges();
}
}
Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//config database
services.AddDbContext<WebAppContext>(option=>option.UseSqlServer(Configuration.GetConnectionString("default")));
//add filter
services.AddScoped<CustomFilter>();
}
controller
[TypeFilter(typeof(CustomFilter))]
public IActionResult Privacy()
{
return View();
}
when I access Privacy method , The database will add a row of data
First, add the Nuget package Microsoft.EntityFrameworkCore.SqlServer to your project.
Then, add the following code to the ConfigureServices method of your Startup class:
services.AddDbContext<WebAppContext>(options =>
options.UseSqlServer("[your connectionstring here]"));
That's the bare minimum and this will enable you to inject the DbContext into the ActionFilter.
I've problem with seeding data to database. Eariler I tried way from this tut: Seed Data in EF 6 Code-First
and then the seed method is never called
DBSchool.cs
namespace SchoolTest.DAL
{
public class DBSchool : DbContext
{
public DBSchool() : base("DBSchool")
{
Database.SetInitializer(new Seeder());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public DbSet<Guest> Guests { get; set; }
}
}
Seeder.cs
public class Seeder : DropCreateDatabaseAlways<DBSchool>
{
protected override void Seed(DBSchool context)
{
IList<Guest> GuestList = new List<Guest>();
GuestList.Add(new Guest()
{
Name = "Dexter",
Surname = "Dexter",
Email = "test#test.com"
});
context.Guests.AddRange(GuestList);
context.SaveChanges();
base.Seed(context);
}
}
Guest.cs
public class Guest
{
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
[Key]
public int GuestId { get; set; }
}
App.config
<appSettings>
<add key="DatabaseInitializerForType SchoolTest.DAL.DBSchool, SchoolTest"
value="SchoolTest.Data.Seeder, SchoolTest" />
</appSettings>
Is there any way to call the Seed() method or just through the Configuration.cs?
Try changing your code like this.
public class DBSchool : DbContext
{
public DBSchool() : base("name=<database-name>")
{
Database.SetInitializer<DBSchool>(new Seeder());
}
// Rest of your implementation
}
Replace <database-name> with the name of your database.
If that didn't work, you can give a Generic Type Parameter to the context class and change your code as follows.
Seeder.cs -> public class Seeder<T> : DropCreateDatabaseAlways<DBSchool>
DBSchool.cs -> Database.SetInitializer<DBSchool>(new Seeder<DBSchool>());
Read more on that here.
If that didn't work either, you can use migrations and seed data using custom sql using Sql().
I've recently started messing with the Clean Architecture concept while using .NET 5. The problem I am running into is that although Identity is well isolated in the Infrastructure layer, if I want to use additional properties on the user without mixing that data with the AspNetUsers table, the problems start.
I will have to create a new entity in the Domain layer, for example UserProfile, and figure out a way to relate that entity to the ApplicationUser entity without
including an ApplicationUser reference in my new entity
moving the ApplicationUser entity to the Domain layer and reference Identity namespaces in there.
In all Clean Architecture projects this situation is either not addressed (they simply add new properties into ApplicationUser) or the solution given is to include a reference to the Identity User Id in the new entity.
I don't like the first solution since it forces me to add custom fields to an Identity table. The second solution brings new and complex problems since it will force me to manually synchronize 2 tables in case of creation or deletion of data, also forcing the use of transactions to avoid orphan data in case of synchronization failure. It also brings an abstraction issue since deleting a user using only Identity will not take care automatically of unknown entities (from future plugins) that might depend or relate somehow to AspNetUsers table.
Let me be more concrete. Here's my simplified project structure (using .NET core 5):
Application
Interface
IApplicationDbContext.cs
Core
Domain
UserProfile.cs (my custom user class with additional properties)
Infrastructure
Data
ApplicationDbContext.cs (Interface implementation)
Identity
ApplicationRole.cs
ApplicationUser.cs
DependencyInjection.cs
Web
my web application
Here's my code:
IApplicationDbContext.cs
namespace Application.Common.Interfaces
{
public interface IApplicationDbContext
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}
}
UserProfile.cs
namespace Core.Domain
{
public class UserProfile : AuditEntity
{
// Don't want to reference ApplicationUser in domain.
// [ForeignKey("Id")]
//public virtual ApplicationUser User { get; set; }
[StringLength(50)]
public string FirstName { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
public string JobPosition { get; set; }
[StringLength(50)]
public string Photo { get; set; }
[DefaultValue(true)]
public bool IsListed { get; set; }
[DefaultValue(false)]
public bool IsDisabled { get; set; }
[DefaultValue(false)]
public bool IsLocked { get; set; }
public DateTime? LastLoginDate { get; set; }
public DateTime? LastActivityDate { get; set; }
[StringLength(100)]
public string LastSessionId { get; set; }
[StringLength(256)]
public string PrivateFolder { get; set; }
public bool IsOnApproval { get; set; } = true;
public bool IsDeleted { get; set; } = false;
}
}
ApplicationDbContext.cs
namespace Infrastructure.Data
{
public partial class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>, IApplicationDbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new Configurations.UserConfiguration());
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
var entries = ChangeTracker.Entries().Where(x => x.Entity is AuditEntity && (x.State == EntityState.Added || x.State == EntityState.Modified));
foreach (var entry in entries)
{
if (entry.State == EntityState.Added)
{
((AuditEntity)entry.Entity).CreatedBy = _currentUserService.UserId;
((AuditEntity)entry.Entity).CreatedDate = DateTime.UtcNow;
}
((AuditEntity)entry.Entity).LastModifiedBy = _currentUserService.UserId;
((AuditEntity)entry.Entity).LastModifiedDate = DateTime.UtcNow;
}
return base.SaveChangesAsync(cancellationToken);
}
}
}
ApplicationRole.cs
namespace Infrastructure.Identity
{
public class ApplicationRole : IdentityRole<int> //, ISoftDeletable
{
public ApplicationRole() : base() { }
public ApplicationRole(string name) : base(name) { }
}
}
ApplicationUser.cs
namespace Infrastructure.Identity
{
public class ApplicationUser: IdentityUser<int>
{
}
}
DependencyInjection.cs
namespace Infrastructure
{
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
config.GetConnectionString("eCMConnection"),
context => context.MigrationsAssembly(Assembly.GetExecutingAssembly().FullName)));
services.AddIdentity<ApplicationUser, ApplicationRole>(
options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.SignIn.RequireConfirmedAccount = false;
}
)
.AddRoleManager<RoleManager<ApplicationRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders(); // two factor authentication
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
services.AddTransient<IEmailService, EmailService>();
return services;
}
}
}
So, my question is, what is the best approach to be able to have a custom UserProfile entity with data stored in a table related to AspNetUsers table without having to reference ApplicationUser and Identity in UserProfile domain entity. What is the best approach to be able to create and delete Identity users but also related data from my UserProfile table. As anyone seen an example of this implemented?
Thanks! Hope someone can throw some light into this.
I think first you have to decide whether you finally want to store your additional properties in the same table or in a different table as the Identity entity. As you already pointed out both approaches have advantages and disadvantages but I don't see any other option than to choose one.
Once decided you could still encapsulate original Identity entity and the additional properties in one domain object without creating a dependency to infrastructure layer. One option would be repository pattern where the interface lives on domain layer and implementation on infrastructure layer. In the repository you map the domain object to one or multiple tables. This way you could also encapsulate transaction handling if needed.
I have this edmx, database first, in project "DAL".
But the tt file from edmx is in another project "DomainModel".
Both projects are in the same solution.
Now, whenever I created new table in database, update model from database, I have to manually re-insert IEntity and public EntityState EntityState { get; set; } in every table class generated from "Run Custom Tool" on tt file.
public partial class newTable : IEntity
{
public EntityState EntityState { get; set; }
}
public partial class oldTable : IEntity
{
public EntityState EntityState { get; set; }
}
I also have to manually re-insert Configuration.LazyLoadingEnabled = false; and Configuration.ProxyCreationEnabled = false; in the following code
public partial class myEntities : DbContext
{
public myEntities()
: base("name=myEntities")
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
}
is there anyway for me to automate these? especially the first part, with hundred of tables.
You may try to tweak tt, however I'd recommend you to utilize partial feature, it is there for purpose :). Put put all amendments to the separate file which is not replaced on every model update. This is still manual or semi-manual work, but you need to do that only once and then you'll need to update it only for new tables.
// DbModelPatches.cs
public partial class newTable : IEntity
{
public EntityState EntityState { get; set; }
}
public partial class oldTable : IEntity
{
public EntityState EntityState { get; set; }
}
If default constructor is generate by tt you cannot replace it in partial file, but you can define another one, parametreized, and put all changes there.
public partial class myEntities : DbContext
{
public myEntities(string name)
: base("name={name}")
{
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
// or static factory method:
public static myEntities CreateContext()
{
var res = new myEntities();
res.Configuration.LazyLoadingEnabled = false;
res.Configuration.ProxyCreationEnabled = false;
return res;
}
}
I have a DbContext like this,
public class EPDContext : TrackerContext
{
public EPDContext()
: base("name=DevelopmentApplicationServices")
{
Database.SetInitializer<EPDContext>(new EPDDBInitializer());
this.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}
public DbSet<TaskRevision> TaskRevisions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TaskRevision>().HasMany(x => x.Models).WithMany().Map(x => x.MapLeftKey("TaskRevisionID").MapRightKey("ModelId").ToTable("TaskRevision2Models"));
}
}
public class EPDDBInitializer : CreateDatabaseIfNotExists<EPDContext>
{
protected override void Seed(EPDContext context)
{
//// My Seeding data goes here
base.Seed(context);
}
}
And my Entity:
[TrackChanges]
public class TaskRevision
{
#region properties
[Key]
public Guid TaskRevisionID { get; set; }
public virtual List<Model> Models { get; set; }
}
and my migration configuration class looks like:
internal sealed class Configuration : DbMigrationsConfiguration<PW.EPD.Data.EPDContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
protected override void Seed(PW.EPD.Data.EPDContext context)
{
}
}
I got this error "There is already an object named 'TaskRevisions' in the database entity framework." when I execute my application. DB has created successfully and there is no seeding data.
At the same time when I execute the same code after removing the onModelCreating() override method, db has created with seed data.
What I did wrong here, kindly correct me.
Thanks in advance