How to modify DbContext base constructor parameter as its being passed? - c#

In this project I am trying to migrate from .NET to .NET Core. Here I have a code that I want to implement in .NET Core.
public partial class CompanyFormsContext : DbContext
{
public CompanyFormsContext()
: base("name=CompanyFormsContext")
{
}
public CompanyFormsContext(string connName)
: base("name=" + connName)
{
}
...
}
In .NET Core, string is not accepted as a parameter to DbContext. Instead, you can pass DbContextOptions as a parameter. For example, in the following link: http://ef.readthedocs.io/en/latest/miscellaneous/configuring-dbcontext.html
You can see the example:
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
}
and
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlite("Filename=./blog.db");
using (var context = new BloggingContext(optionsBuilder.Options))
{
// do stuff
}
what I want to do is similar to this. I could possibly create an instance of DbContextOptions and pass it but I do not know how I would modify the SqlServer connection as I am passing it through the base class constructor.
The most important thing that I cannot figure out is that I want to be able to keep my empty constructor which would trigger a default "CompanyFormsContext" connection. It would also be great if I could simply change the connection name while passing it as a parameter in the CompanyFormsContext constructors.
I was thinking of the following as an alternative way instead of the base constructors but I would prefer to keep the base constructor functionality.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (connName == null)
{
optionsBuilder.UseSqlServer(#"Server=.\;Integrated Security=True;Database=CompanyFormsContext");
}
else
{
optionsBuilder.UseSqlServer(#"Server=.\;Integrated Security=True;Database=" + connName);
}
}

You could create a static method which returns a DbContextOptions<BloggingContext> which will be created from a passed connection string.
So your class could look something like this:
public partial class CompanyFormsContext : DbContext
{
public CompanyFormsContext()
: base(CreateOptions(null))
{
}
public CompanyFormsContext(string connName)
: base(CreateOptions(connName))
{
}
private static DbContextOptions<BloggingContext> CreateOptions(string connName)
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlite("Filename=./blog.db");
if (connName == null)
{
optionsBuilder.UseSqlServer(#"Server=.\;Integrated Security=True;Database=CompanyFormsContext");
}
else
{
optionsBuilder.UseSqlServer(#"Server=.\;Integrated Security=True;Database=" + connName);
}
return optionsBuilder.Options;
}

Related

Can I use SetDefaultConnectionFactory for Database first or Model first

I'd like to use DbConfiguration.SetDefaultConnectionFactory feature, which is to set the connection string "used to create connections by convention if no other connection string or connection is given to or can be discovered by DbContext".
But it seems like this can be used with Code-first only. If used in my project it goes to DbContext.OnModelCreating and throws UnintentionalCodeFirstException.
Are there any similar solution that can be used with DB-first or Model-first?
My code:
public class DbContext : System.Data.Entity.DbContext
{
public DbContext() : base("ConnStrName") {}
public DbContext(string connStr) : base(connStr) {}
protected override void OnModelCreating(DbModelBuidler modelBuilder)
{
throw new UnintenionalCodeFirstException();
}
}
public class EF6CodeConfig : DbConfiguration
{
public EF6CodeConfig(IConfiguration configuration)
{
this.SetDefaultConnectionFactory(new ConnectionFactory(configuration));
}
}
public class ConnectionFactory : IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOfConnStr)
{
var str = GetConnStr(nameOfConnStr);
return new System.Data.Entity.Core.EntityClient.EntityConnection(str);
}
}
And config EF:
DbConfiguration.SetConfiguration(new EF6CodeConfig(configuration));
Background about my situation: I'm creating a new .NET Core microservice referencing some old DLLs that used EF6. But EF6 cannot find the connection string from .NET Core appsettings file. So I need that fallback that SetDefaultConnectionFactory provides.
I have a work-around, but doesn't look good:
public class DbContext : System.Data.Entity.DbContext
{
public static string ConnectionString { get; set; }
private static string GetConnectionString(string connStrName)
{
return string.IsNullOrWhiteSpace(ConnectionString) ? connStrName : ConnectionString;
}
public DbContext : base(GetConnectionString("name=ConnStrName")) {}
...
}
And set the connection string myself:
DbContext.ConnectionString = configuration.GetConnectionString("");

Dependency injection between two ASP.NET Core projects

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);
}

C#: Inherit from DbContext in Net Core, Constructor: No database provider has been configured for this DbContext

I currently have PropertyApplication DbContext as below,
public partial class PropertyContext : DbContext
{
public PropertyContext()
{
}
public PropertyContext(DbContextOptions<PropertyContext> options)
: base(options)
{
}
public virtual DbSet<Address> Address { get; set; }
public virtual DbSet<BoundaryChangeEvent> BoundaryChangeEvent { get; set; }
I would like to inheritance from this PropertyDbContext. Is this being done correctly in the constructor? Attempting to make unit test pass below, it overrides save changes to bring in auditing user information. Just curious if specifically the constructor statements below look correct? Or should I try to attempt option 2 below with AuditablePropertyContext options?
public class AuditablePropertyContext : PropertyContext
{
private int _user;
public AuditablePropertyContext()
{
}
public AuditablePropertyContext(DbContextOptions<PropertyContext> options, UserResolverService userService)
: base(options)
{
_user = userService.GetUser();
}
public void ApplyCreatedBy()
{
var modifiedEntities = ChangeTracker.Entries<ICreatedByUserId>().Where(e => e.State == EntityState.Added);
foreach (var entity in modifiedEntities)
{
entity.Property("CreatedByUserId").CurrentValue = _user;
}
}
public override int SaveChanges()
{
ApplyCreatedBy();
return base.SaveChanges();
}
}
Option 2:
I was receiving error trying to conduct this,
public AuditablePropertyContext(DbContextOptions<AuditablePropertyContext> options, UserResolverService userService)
: base(options)
{
_user = userService.GetUser();
}
Error:
Error CS1503 Argument 1: cannot convert from 'Microsoft.EntityFrameworkCore.DbContextOptions IPTS.PropertyManagement.Infrastructure.Auditable.Data.AuditablePropertyContext' to 'Microsoft.EntityFrameworkCore.DbContextOptions IPTS.PropertyManagement.Infrastructure.Data.PropertyContext '
*Sometimes company utilizes SQL Server, sometimes InMemory, or SQLite
Unit Test is failing:
services.AddSingleton(a =>
{
var mock = new Mock<IUserResolverService>();
mock.Setup(b => b.GetUser()).Returns(5);
return mock.Object;
});
services.AddDbContext<PropertyContext>(
options => options.UseInMemoryDatabase("Ipts").UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll),
ServiceLifetime.Singleton);
services.AddSingleton<DbContext, PropertyContext>();
services.AddDbContext<AuditablePropertyContext>(
options => options.UseInMemoryDatabase("Ipts").UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll),
ServiceLifetime.Singleton);
services.AddSingleton<AuditablePropertyContext>();
services.RegisterMappingProfiles(new ApplicationServicesMappingProfile(),
new PropertyManagementDataMappingProfile());
return services;
}
Unit Test: Error
Message:
System.InvalidOperationException : No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
Stack Trace:
DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
DbContext.get_InternalServiceProvider()
DbContext.get_DbContextDependencies()
Change the constructor PropertyContext class to the following code:
public PropertyContext(DbContextOptions options)
: base(options)
{
}
then change the constructor AuditablePropertyContext class to the following code:
public AuditablePropertyContext(DbContextOptions options, UserResolverService userService)
: base(options)
{
_user = userService.GetUser();
}
notice: Delete the default constructor in both classes when you don't need it.
You could also provide the specialized DbContextOptions<Repo> only on the concrete subtype.
eg
public abstract class BaseRepo: DbContext
{
public BaseRepo(DbContextOptions options) : base(options)
{
}
}
public sealed class Repo : BaseRepo
{
public Repo(DbContextOptions<Repo> options) : base(options)
{
}
}

Configuring DBContext in the constructor of my base repository class

I have a situation where I need to instantiate my DBContext after my solution has started up. I asked this question which indicated that I could do this with a constructor argument.
It was suggested that I implement as an example this:
var connection = #"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
using (var context = new BloggingContext(optionsBuilder.Options))
{
// do stuff
}
However I have implemented the repository pattern (for better or worst) and given my changed circumstances - not having a connection string until after the solution has run startup - I need to implement this into the base repository class and I am at a bit of a loss..
Currently I have this:
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
public JobsLedgerAPIContext _context;
#region Properties
public EntityBaseRepository(JobsLedgerAPIContext context)
{
_context = context;
}
#endregion
public virtual IQueryable<T> GetAll()
{
return _context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return _context.Set<T>().Count();
}
......
How do I implement this change both instantiating the DBContext in the constructor (there by bypassing the need to add the context as a service in startup) and then with the wrapping each of the virtual methods with "using" etc
EDIT.. Camilo indicated I had not identified when I have the database name.
The basic situation is that the system starts up (This is an Aurelia SPA project which is irrelevant to this issue) sends the package to the browser which shows a login screen. User logs in.. User is verified via a JWT controller.. Once verified in the controller (using a catalog database that has one table with 3 fields - username, password, database name) I use the database name to create a connection string and then instantiate my DBContext at that point.. so via a constructor.
The answers below need to be modified as the one with the factory answer (promising) has errors as discovered by this question.. Nkosi responded with an great answer to the error.
EDIT 2..
This is a response to the edited question below:
Here is my original Client Repository with :base(context) on the constructor.
using JobsLedger.DATA.Abstract;
using JobsLedger.MODEL.Entities;
namespace JobsLedger.DATA.Repositories
{
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private new JobsLedgerAPIContext _context;
public ClientRepository(JobsLedgerAPIContext context) : base(context)
{
_context = context;
}
public void RelatedSuburbEntities(Suburb _suburb)
{
_context.Entry(_suburb).Reference<State>(a => a.State).Load();
}
}
}
It has a reference to the base class "context". I am not sure how to modify this given that I believe I still need that ":base(context)" at the end. As well, I have a method in this that accesses _context as well which is part of the constructor...
Further I assume that I can no longer inject the service into the controller but instead new it up once I have secured the connection string and then pass that connection string to service.
Also, Given I have now added a singleton on the startup do I need to remove the original entry? :
services.AddDbContext<JobsLedgerAPIContext>(options => options.
UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("JobsLedger.API")));
effectively replacing it with my singleton reference as per below:
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
Edited
The answer has been edited to rectify the mistake spotted and
fixed by Nkosi. Thanks, #Nkosi.
Implement a factory pattern. You can create a factory, call it ContextFactory as below:
First, define the interface. Further modified, removed the connectionString parameter
public interface IContextFactory<T> where T : DbContext
{
T CreateDbContext();
}
Create a factory class that implements this interface (edited as per Nkosi answer). Further modified to inject IHttpContextAccessor
public class ContextFactory<T> : IContextFactory<T> where T : DbContext
{
private readonly HttpContext _httpContext;
public ContextFactory(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
public T CreateDbContext()
{
// retreive the connectionString from the _httpContext.Items
// this is saved in the controller action method
var connectionString = (string)_httpContext.Items["connection-string"];
var optionsBuilder = new DbContextOptionsBuilder<T>();
optionsBuilder.UseSqlServer(connectionString);
return (T)Activator.CreateInstance(typeof(T), optionsBuilder.Options);
}
}
Then modify your base repository and make the JobsLedgerAPIContext protected. This context is going to be set by the derived class. Further modified to remove the constructor. It will use the parameterless constructor.
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
protected JobsLedgerApiContext Context { get; set; }
public virtual IQueryable<T> GetAll()
{
return Context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return Context.Set<T>().Count();
}
}
Change your derived class to use IContextFactory. Further modified to use the _contextFactory.CreateDbContext() parameter less method
The IClientRepository should have SetContext method defined.
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private readonly IContextFactory<JobsLedgerApiContext> _contextFactory;
public ClientRepository(IContextFactory<JobsLedgerApiContext> factory)
{
_contextFactory = factory;
}
// this method will set the protected Context property using the context
// created by the factory
public void SetContext()
{
Context = _contextFactory.CreateDbContext();
}
public void RelatedSuburbEntities(Suburb suburb)
{
Context.Entry(suburb).Reference<State>(a => a.State).Load();
}
}
In the controller, that receives IClientRepository instance, you can set the connection in the HttpContext.Items, which will be valid for the request. This value will then be retrieved by the ContextFactory using IHttpContextAccessor. Then you simply call the _repository.SetContext(); method on the repository.
public class HomeController : Controller
{
private readonly IClientRepository _repository;
public HomeController(IClientRepository repository)
{
_repository = repository;
}
public IActionResult Index()
{
// save the connectionString in the HttpContext.Items
HttpContext.Items["connection-string"] = "test-connection";
// set the context
_repository.SetContext();
return View();
}
}
Make sure you register the IContextFactory in ConfigureServices as open generics and Singleton as below, also register the HttpContextAccessor and IClientRepository
services.AddHttpContextAccessor();
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
services.AddTransient<IClientRepository, ClientRepository>();
You may define your JobsLedgerAPIContext like this:
public class JobsLedgerAPIContext : DbContext
{
// public DbSet<Job> Jobs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=dotnetcore;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// may need to reflect entity classes and register them here.
base.OnModelCreating(modelBuilder);
}
}

Create generic repository for returning data from stored procedure with EFCore

I'm trying to create a generic custom repository using the aspnetboilerplate framework. I want the repository to be able to call a stored procedure in SQL Server and return a set of data for use with an explicit type.
Custom repository:
public class PMStoredProcedureRepository<T> : IPMStoredProcedureRepository<T> where T : class
{
private MyDbContext Context { get; set; }
public PMStoredProcedureRepository(IDbContextProvider<MyDbContext> dbContextProvider)
{
Context = dbContextProvider.GetDbContext();
}
// When you expect a model back (async)
public IQueryable<T> ExecuteSP(string query, params object[] parameters)
{
var type = Context.Set<T>().FromSql(query, parameters);
return type;
}
}
What my app service looks like:
public class DashboardAppService : MyAppServiceBase, IDashboardAppService
{
// Entity repositories
private readonly IPMStoredProcedureRepository<PMTestSP> _storedProcRepository;
public DashboardAppService(
IPMStoredProcedureRepository<PMTestSP> storedProcRepository
)
{
_storedProcRepository = storedProcRepository;
}
public List<PMTestSP> GetTestSP()
{
var ret = _storedProcRepository.ExecuteSP("exec pme_TestProcedure", new SqlParameter("inputString", "abcde"));
return ret.ToList();
}
}
I added the return type to the DbContext as:
public virtual DbSet<PMTestSP> PMTestSP { get; set; }
When I call GetTestSP, I get this error:
ArgumentNullException: Value cannot be null. Parameter name:
unitOfWork
Abp.EntityFrameworkCore.Uow.UnitOfWorkExtensions.GetDbContext(IActiveUnitOfWork
unitOfWork, Nullable multiTenancySide)
Abp.EntityFrameworkCore.Uow.UnitOfWorkDbContextProvider.GetDbContext(Nullable
multiTenancySide)
Abp.EntityFrameworkCore.Uow.UnitOfWorkDbContextProvider.GetDbContext()
Company.Name.EntityFrameworkCore.Repositories.PMStoredProcedureRepository..ctor(IDbContextProvider
dbContextProvider) in PMStoredProcedureRepository.cs
+
Context = dbContextProvider.GetDbContext(); Castle.Proxies.PMStoredProcedureRepository`1Proxy..ctor(IInterceptor[]
, IDbContextProvider )
Add [UnitOfWork] attribute and make it a virtual method:
[UnitOfWork]
public virtual List<PMTestSP> GetTestSP()
{
// ...
}
You can inject IUnitOfWorkManager to begin a UnitOfWork explicitly:
public List<PMTestType> GetTestSP()
{
using (var uow = UnitOfWorkManager.Begin())
{
var ret = _storedProcRepository.ExecuteSP("exec pme_TestProcedure", new SqlParameter("inputString", "abcde"));
uow.Complete();
return ret.ToList();
}
}
I think the issue is your custom repository don't get right dbcontext. Look at document Create custom repository
public class SimpleTaskSystemRepositoryBase<TEntity, TPrimaryKey> : EfRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey>
where TEntity : class, IEntity<TPrimaryKey>
{
public SimpleTaskSystemRepositoryBase(IDbContextProvider<SimpleTaskSystemDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
//add common methods for all repositories
}
Note: the custom repository must inherit EfRepositoryBase<SimpleTaskSystemDbContext, TEntity, TPrimaryKey>. You could try this.

Categories

Resources