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 :)
Related
I have an asp.net core 2.2 web application. There is an Interface which is implemented by multiple classes.
services.AddTransient<IHandler, HandlerA>();
services.AddTransient<IHandler, HandlerB>();
Each implementation of IHandler injects scoped EF DbContext and if the same DbContext is shared between different threads, then periodically a floating exception will occur when trying to access the same entity. So I get each handler in a separate scope.
using (var scope = _serviceProvider.CreateScope())
{
var handlers = scope.ServiceProvider.GetServices<IHandler>();
await Task.WhenAll(handlers.Select(async h =>
{
using var internalScope = scope.ServiceProvider.CreateScope();
var handler = internalScope.ServiceProvider
.GetServices<IHandler>()
.First(f => f.Name == h.Name);
await handler.Handle(cancellationToken);
}));
}
This solution seems to work, but I'm not sure if it's optimal. Maybe there is a better way to get multiple implementations of the same service interface in separate scopes?
You do not need to access same entity using different DBContext.
Define services and inject in controllers explained above link. Each service will access DB using same db context through Manager Classes.
You can use service this way as you seeking best practices. And use dependency injection to access the services from controllers.
Define Service, Domain classes and interfaces.
public class Service : Attribute
{
}
//Domain Classes
public class Entity
{
string str {get; set;}
int numb {get; set;}
}
//DBContext
public class DbContext : IdentityDbContext
{
public DbContext(DbContextOptions<DbContext> options)
: base(options)
{
}
public DbSet<Entity> Entities { set; get; }
}
//Interfaces
interface IHandler
{
string method1(Entity entity);
int method2();
}
Implement Interfaces using manager classes
public class HandlerA: IHandler
{
private readonly DbContext _dbContext;
public HandlerA(DbContext dbContext)
{
_dbContext = dbContext;
}
string method1(Entity entity)
{
//access db or business stuff
_dbContext.Entities.Add(entity);
_dbContext.SaveChanges();
}
int method2(){}
}
public class HandlerB: IHandler
{
string method1(Entity entity)
{
//access db or business stuffs
_dbContext.Entities.Remove(entity);
_dbContext.SaveChanges();
}
int method2(){}
int method3(){}
}
//Services
[Service]
public class DoService1Stuff()
{
private readonly HandlerA _handlerA;
public DoService1Stuff(HandlerA handlerA)
{
_handlerA= handlerA;
}
//Implement your task
public Do(Entity entity)
{
_handlerA.method1(entity);
}
}
[Service]
public class DoService2Stuff()
{
private readonly HandlerB _handlerB;
public DoService2Stuff(HandlerB handlerB)
{
_handlerB= handlerB;
}
//Implement your task
public Do(Entity entity)
{
_handlerA.method1(entity);
}
}
Register services through dependency injection in startup.cs
services.AddTransient<IHandler, HandlerA>();
services.AddTransient<IHandler, HandlerB>();
Access services in controllers
[HttpPost]
public async Task<IActionResult> DoStuff1([FromServicec] DoService1Stuff doService1Stuff)
{
var entity= new Entity
{
str="hello",
numb=2020;
};
return Ok(doService1Stuff.Do(entity))
}
//Implement second service as needed.
I'm currently developing a web application with ASP.NET Core and handling the database with Entity Framework Core. I have two projects in my VS Solution; WebApp (the main application) and DatabaseHandler (the EF Core handler). I have installed Entity Framework Core with the Pomelo package, since I'm using a MySQL database.
I've been following the Microsoft documentation to setup EF Core, connection strings and all that, and it works fine. I'm able to make migrations, make updates and do stuff with the database. I'm however not sure if I'm doing it correctly, since the latest EF Core tutorials use dependency injection and I'm not familiar with it.
Right now I'm passing the DbContext object as an argument from WebApp to DatabaseHandler, since I want all database-related stuff to only exist in DatabaseHandler. This works, but is it possible to call functions from another project and also share the DbContext object without passing it as an argument? I'm probably not explaining it well, I hope my code explains it better.
WebApp/Startup.cs:
This is where I load the connection string from appsettings.json.
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<DataContext>(
options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddRouting(options => options.LowercaseUrls = true);
services.AddControllersWithViews();
}
WebApp/HomeController.cs:
This is where I call the GetAllChallenges() function from the DatabaseHandler project, and I also pass the DataContext object as an argument. This is what I'm trying to avoid!
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly DataContext db;
public HomeController(ILogger<HomeController> logger, DataContext _db)
{
_logger = logger;
db = _db;
}
public IActionResult Challenges()
{
List<Challenge> ChallengesList = DatabaseHandler.HandleChallenges.GetAllChallenges(db);
return View(ChallengesList);
}
}
DatabaseHandler/DataContext.cs:
This is where I initialize the entity classes and so on.
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { }
// Tables
public DbSet<User> Users { get; set; }
public DbSet<Challenge> Challenges { get; set; }
// Data seeding
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Seed();
}
}
DatabaseHandler/HandleChallenges.cs:
This is where I have all my database functions. The results are returned back to the controller within the WebApp project.
public class HandleChallenges
{
public static List<Challenge> GetAllChallenges(DataContext db)
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
I have looked into dependency injection, but I'm not sure how I can use this between two projects. Is there a less complicated way of achieving this, perhaps without using DI at all? I'm satisfied as long as I don't need to pass the DataContext object as an argument every time I need to call a function from DatabaseHandler.
Can someone help me understand? Thanks a lot in advance!
You could use Options pattern, which I have already used many times. Its working very well despite of database you use. Thanks to dependency injection you are able to access if from multiple projects. Reading documentation about Option pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1) is useful but I will also provide you with my own example :
First you create model to store you connection string, dbName etc. Remember to add it in a library outside your main project(eg. Web Api) :
public class NameOfYourProject_ApiDbSettings : IIMTTApiDbSettings
{
public NameOfYourProject_ApiDbSettings()
{
}
public string CollectionName { get; set; }
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public interface I_NameOfYourProject_ApiDbSettings
{
string CollectionName { get; set; }
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
Secondly you make it available for all you projects :
services.Configure<NameOfYourProjectApiDbSettings>(options =>
{
options.ConnectionString
= Configuration.GetSection("NameOfYourProjectDbSettings:ConnectionString").Value;
options.DatabaseName
= Configuration.GetSection("NameOfYourProjectDbSettings:DatabaseName").Value;
});
Then you can use it in multiple projects. (Rememebr to add referance to you model -> point 1. I keep the model always with repository) I will give you my example where I use MongoDb :
private readonly IMongoDatabase _database = null;
public SomeObjectContext(IOptions<IMyProjectDbSettings> settings)
{
var client = new MongoClient(settings.Value.ConnectionString);
if (client != null)
_database = client.GetDatabase(settings.Value.DatabaseName);
}
public IMongoCollection<MyModel> MyModels
{
get
{
return _database.GetCollection<MyModel>("MyModels");
}
}
You need to extract an interface from the class (note the method is no longer static) and add a constructor for the context:
public interface IHandleChallenges
{
List<Challenge> GetAllChallenges();
}
public class HandleChallenges : IHandleChallenges
{
public HandleChallenges(DataContext context)
{
db = context;
}
private DataContext db;
public List<Challenge> GetAllChallenges()
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
Then register it as a service:
services.AddScoped<IHandleChallenges, HandleChallenges>();
Your controller now receives this class in it's constructor instead of the context:
private IHandleChallenges _challengeHandler;
public HomeController(ILogger<HomeController> logger, IHandleChallenges challengeHandler)
{
_logger = logger;
_challengeHandler = challengeHandler;
}
And calls it from the action:
public IActionResult Challenges()
{
List<Challenge> ChallengesList = _challengeHandler.GetAllChallenges();
return View(ChallengesList);
}
I have a shared project that I put my shared and base logic in to reuse in other projects and a Normal asp.net core project on top of it.
In the Shared base project I have:
class DbContextBase : DbContext
{
public DbSet<ActivityLog> ActivityLogs { get; set; }
public DbSet<Event> Events { get; set; }
SaveChanges(...)
}
class MyEventProcessor
{
private readonly DbContextBase _databaseContext;
public MyEventProcessor(DbContextBase databaseContext)
{
_databaseContext=databaseContext;
}
public void AddEvent(Event e){}
public void ProcessEvent(Event e){}
}
In the .net core webApi one I have my domain specific logic
class MyDomainDbContext : DbContextBase
{
public DbSet<User> Users{ get; set; }
}
class UserRepository
{
private readonly MyDomainDbContext _databaseContext;
public UserRepository(MyDomainDbContext databaseContext){_databaseContext=databaseContext;}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
services.AddDbContext<MyDomainDbContext>([options]);
services.AddScoped<UserRepository>();
services.AddScoped<EventProcessor>();
}
}
The problem is, this way it fails on startup not being able to find DbContextBase because it is not registered.
I thought of changing the registeration to an interface like below, but then obviously I won't have access to my domain's DbSets in my repositories.
services.AddDbContext<IDbContextBase,MyDomainDbContext>([options]);
So the question is, how do I set this so that MyEventProcessor and UserRepository can read and write into the same Database context.
You need to register both types: DbContextBase and MyDomainDbContext, however the point is to reuse the same instance for both cases, in order to achieve that you can register them in a following way:
services.AddDbContext<MyDomainDbContext>([options]);
services.AddScoped<DbContextBase, MyDomainDbContext>(sp => sp.GetRequiredService<MyDomainDbContext>());
That way, you'll be able to inject both types, and both types will actually be the same instance, which you can easily check using GetHashCode or similar.
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.
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.