Issues with DbContexts causing Errors - c#

I currently have multiple projects with multiple DbContexts:
Projects (DbContext):
Model
DataAccessLayer (EntityContext)
ServiceLayer
Web (IdentityContext)
I have a Model in the Model's project called Department that is created by the EntityContext and i reference to it in the ApplicationUser.cs in the Web Project.
When the IdentityContext is trying to create the AspNet Tables it also tries to recreate the Department Table and i get the following error:
"There is already an object named 'Departments' in the database."
Here's my Identity Files:
The IdentityContext is my Web Project:
ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("EntityContext", throwIfV1Schema: false)
{
Configuration.LazyLoadingEnabled = false;
Configuration.AutoDetectChangesEnabled = false;
Configuration.ProxyCreationEnabled = false;
Configuration.ValidateOnSaveEnabled = false;
}
static ApplicationDbContext()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, ApplicationDbInitializer>());
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
ApplicationDbInitializer.cs
public class ApplicationDbInitializer : DbMigrationsConfiguration<ApplicationDbContext>
{
public ApplicationDbInitializer()
{
AutomaticMigrationsEnabled = false;
#if DEBUG
AutomaticMigrationDataLossAllowed = true;
AutomaticMigrationsEnabled = true;
#endif
}
protected override void Seed(ApplicationDbContext context)
{
base.Seed(context);
}
ApplicationUser.cs
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
/// <summary>
/// Linked Department Id
/// </summary>
public Guid? DepartmentId { get; set; }
/// <summary>
/// Linked Department Object
/// </summary>
[ForeignKey("DepartmentId")]
public virtual Department Department { get; set; }
}
I then commented out the "public DbSet Department { get; set; }" in the EntityContext and now it builds but when i try to access it i get this error:
"The entity type Department is not part of the model for the current context."
I understand both errors, but i can't figure out how to get them to be on the same context, as they are in different projects.

Traditionally in a situation like this I would rather you make your Context inside of the DataAccessLayer be the IdentityContext. Since you have ties between the IdentityContext and that particular context anyway, it gives you a single context to represent your application and doesn't introduce a shared context issue.
This would result in removing the DBContext from your web project, and changing the base class of your Context inside of the DataAccessLayer to become IdentityDbContext<ApplicationUser> rather than the current DbContext base class.
Most likely, this will result in you moving a few entities into the lower DLL, but again it makes that DLL more representative of the entire solution.

Related

Overriding property in shared project class

Ok team,
Quick summary so you understand my question. We have an MVC app (Proj.MVC) that references a shared project (Shared.MVC). The shared project contains Controllers (BaseController & ApiController), Context, and Session Manager as those will be used by other projects. BaseController & ApiController have a reference to SharedContext and SharedSessionManager.
However, some project may need to add additional calls to Context and Session Manager. Now my problem is, I can use Interfaces so that new classes in Proj.MVC can swap out SharedContext with ProjContext and any inherited controllers can interchange the context type, but BaseController is still using SharedContext. Further all the Shared Controllers are using the SharedContext. So now 'new' logic is using the ProjContext & ProjSessionManager, while all the shared logic is using SharedContext & SharedSessionManager.
So my question is. How do I 'force' ProjContext/Session down to my Shared Controller? Pulling the Controllers out to Proj.MVC defeats the reuse.
namespace Shared.MVC
{
public abstract class BaseMVCController: Controller
{
protected SharedContext DB;
protected SharedSessionManager SessionManager;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
DB = new SharedContext(); //This is being triggered on startup. Likely because HomeController inherits from BaseMVCController
SessionManager = new SharedSessionManager(DB);
}
}
public abstract class SharedApiController : ApiController
{
protected ISharedContext DB;
protected SharedSessionManager SessionManager;
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
DB = new SharedContext();
SessionManager = new SharedSessionManager(DB);
}
}
public class SharedSessionManager : ISessionManager
{
protected readonly ISharedContext DB;
public SharedSessionManager()
{
DB = new SharedContext();
}
public SharedSessionManager(ISharedContext myContext)
{
DB = myContext; //This is being called from the BaseMVCController.Initialize method
}
}
public class SharedContext : DbContext, ISharedContext
{
public SharedContext() : base("contextName")
{
}
public virtual DbSet<MyType> MyList { get; set;}
}
}

Using ApplicationDbContext with Dependency Injection in an n-layered architecture

I have an application with 3 layers (Presentation - Business - Data) built with Asp.Net MVC Core 2.1
In my Presentation layer I have an ApplicationDbContext class which instantiates and fills a test database:
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
SeedData(builder);
}
// Database Tables
public DbSet<Customer> Customers { get; set; }
public DbSet<Ingredient> Ingredients { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderDetail> OrderDetails { get; set; }
public DbSet<Pizza> Pizzas { get; set; }
public DbSet<PizzaIngredient> PizzaIngredients { get; set; }
// Fill Database with sample data
private void SeedData(ModelBuilder builder)
{
// Seed data
}
Said class is injected within the Startup.cs class (also in presentation layer):
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>().AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest);
I now want to use this ApplicationDbContext class in the datalayer to keep code seperated. How would I best go about this? Injecting the class via constructor does not seem to work (Severity Code Description Project File Line Suppression State
Error CS0246 The type or namespace name 'ApplicationDbContext' could not be found (are you missing a using directive or an assembly reference?))
namespace PizzaShop.Data.Repositories
{
public class PizzaRepo : IPizzaRepo
{
private readonly ApplicationDbContext _context;
public PizzaRepo(ApplicationDbContext context)
{
_context = context;
}
public async Task<int> AddEntityAsync(Pizza entity)
{
_context.Pizzas.Add(entity);
return await _context.SaveChangesAsync();
}
//...
}
}
Architecture:
If you want to keep all database-related stuff in the PizzaShop.Data project, then your ApplicationDbContext doesn't belong in your web project. It belongs in your PizzaShop.Data project.
You then reference your PizzaShop.Data project from the web project.
Your ApplicationDbContext needs to be in the DataLayer.
References come from bottom to top which means from Presentation Layer References Business Layer References Data Layer. If you try to reference Presentation Layer in the Data Layer, cross reference problems occur. (it doesn't even makes sense).
As a result, move your ApplicationDbContext to where it belongs, which is the Data Layer and everything will be sorted out :)

extend asp.net identity user cause database error

I tried to extend the asp.net identity user with a "Name" property, I therefore followed the description in this post How to extend available properties of User.Identity
But after I did that and tried to login I get this error "The model backing the 'ApplicationDbContext' context has changed since the database was created. Consider using Code First Migrations to update the database"
Can I fix this or can you only extend the asp.net ueser identity before the database is created the first time?
Based on Asp.Net template database will generate the bellow structure:
In order to extend asp.net identity user and keep the things simple, please update IdentityModels.cs with following code:
//CODE
using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Data.Entity.Migrations;
namespace WebApplication.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, ApplicationDbContextConfiguration>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityRole>().ToTable("AspNetRoles");
modelBuilder.Entity<IdentityUserRole>().ToTable("AspNetUserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("AspNetUserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("AspNetUserClaims");
modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers");
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
internal sealed class ApplicationDbContextConfiguration : DbMigrationsConfiguration<ApplicationDbContext>
{
public ApplicationDbContextConfiguration()
{
ContextKey = "WebApplication.Models.ApplicationDbContext"; //Retrieved from the database table dbo.__MigrationHistory
#if DEBUG
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
#else
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
#endif
}
protected override void Seed(ApplicationDbContext context)
{
base.Seed(context);
}
}
}
The output is:
PS: You can rename default names for asp.net generated tables, personally I prefer to remove AspNet suffix

Why dont my tables get created with this MVC5 configuration inheriting from IdentityDbContext

public class MyContext: IdentityDbContext<ApplicationUser>
{
public MyContext()
: base("MyContext", throwIfV1Schema: false)
{
}
public static MyContext Create()
{
return new MyContext();
}
public System.Data.Entity.DbSet<xxxx> xxxxx { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Startup.Auth:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(MyContext.Create); // ALTERED THIS
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); // ALTERED THIS
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
}
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
// this is the old code, no longer referenced in startup.auth:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("MyContext", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
So basically, Ive set my new context to inherit from IdentityDbContext, and replaced any reference of the old ApplicationDbContext with my new one. It creates the Identity tables, but none of my DbSet tables I specify (omitted, left one crossed out for sample
I'm just guessing here because I can't see your code, but I'm reasonably sure I'm correct. When you start a new project, you have to enable migrations. How this works is that it inspects your project for objects derived from DbContext and then creates a Migrations folder with a Configuration.cs file inside. This file explicitly references the context that is used for migrations. If you do this with more than one object derived from DbContext in your project (here, you have two, MyContext and ApplicationDbContext), it will fuss at you and tell you that you need to specify which one to use. This probably didn't happen because you had previously enabled migrations with the generated context from Identity, and only later added your own context.
Long and short, check this configuration file and change any offending references there. Or, you can just delete the Migrations folder completely and run Enable-Migrations again in the package manager console, making sure to specify MyContext as the context to be used.
Annoyingly small changes:
no throwIfV1Schema: false
public class MyContext: IdentityDbContext<ApplicationUser>
{
public MyContext() : base("MyContext")
{
}
public static MyContextCreate()
{
return new MyContext();
}
public System.Data.Entity.DbSet<xxx> xxx{ get; set; }
}

Single Context in ASP.NET MVC5 Entity Framework application

As demonstrated in my last question here: "Define the key for this EntityType." when including attribute of type ApplicationUser
I need to use a single database context, whereas my current setup is one context (defined within IdentityModel.cs) for the login stuff and another context (defined externally) for all other database operations in the application.
How would I go about using a single context? I do not care about existing data.
You can certainly use single context.
Below is what i did:
public class DataContext : IdentityDbContext<ApplicationUser>
{
public DataContext()
: base("DefaultConnection")
{
}
public IDbSet<Project> Projects { set; get; }
public IDbSet<Task> Tasks { set; get; }
}
Then in account controller:
public class AccountController : Controller
{
public AccountController()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new DataContext())))
{
}
when you create a new project:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
}
you get this which doesnt have much information, you can use this as well and add your dbsets. It has a reference from AccountController.
Just sub-class the data context used for ASP.NET Identity.

Categories

Resources