In my web project I am trying to use Entity Framework 6 model first approach. Model is generated from existing database in separate project. For model generation connection string is saved in app.config of data access project. In main web project (ASP.NET Core MVC)
I am trying to inject context creation in Startup.cs class.
Below is a code for context partial class. Partial class is used because context is auto generated from template.
[DbConfigurationType(typeof(EntityFrameworkConfig))]
public partial class MyEntities
{
public MyEntities(String name) : base(name)
{
}
}
For using entity framework configuration from code instead app.config that cannot be used with asp.net core projects I have config class inherited from System.Data.Entity.DbConfiguration
public class EntityFrameworkConfig : DbConfiguration
{
public EntityFrameworkConfig()
{
this.SetDefaultConnectionFactory(new SqlConnectionFactory());
this.SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
}
}
In config.json in web project i have connection string:
{
"MailSettings": {
"ToAddress": "foo#bar.com"
},
"Connection": {
"ConnectionString": "data source=SERVER;initial catalog=DB;user id=user;password=pwd;MultipleActiveResultSets=True;App=EntityFramework;"
}
}
In Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
...
String connStr = _config["Connection:ConnectionString"];
services.AddScoped((_) => new MyEntities(connStr));
...
}
I am experiencing UnintentionalCodeFirstException thrown from OnModelCreating event of auto generated context class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
Is this proper way to use Entity framework 6 model first approach with asp.net Core MVC project and what is the reason for this exception ?
These are the steps you should follow when you're working with the ASP.net Core and EF 6.This is a just an example.You have to change it according to your use case.
Step 1 : project.json
Specify a single target for the full .NET Framework:
"frameworks": {
"net46": {}
}
Step 2 : Setup connection strings and dependency injection
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(string nameOrConnectionString) : base(nameOrConnectionString)
{
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped((_) => new ApplicationDbContext(Configuration["Data:DefaultConnection:ConnectionString"]));
// Configure remaining services
}
Step 3 : Migrate configuration from config to code
Entity Framework 6 allows configuration to be specified in xml (in web.config or app.config) or through code. As of ASP.NET Core, all configuration is code-based.
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="mssqllocaldb" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
Then :
[DbConfigurationType(typeof(CodeConfig))] // point to the class that inherit from DbConfiguration
public class ApplicationDbContext : DbContext
{
[...]
}
public class CodeConfig : DbConfiguration
{
public CodeConfig()
{
SetProviderServices("System.Data.SqlClient",
System.Data.Entity.SqlServer.SqlProviderServices.Instance);
}
}
Reference : ASP.NET Core and Entity Framework 6
After digging I found that solution is to provide connection string in entity framework format that enables DbContext class to load model metadata.
In Startup.cs:
String connStr = _config["Connection:ConnectionString"];
String efConnString = BuildConnectionString(connStr);
services.AddScoped((_) => new MyEntities(efConnString));
....
....
private String BuildConnectionString(String cs)
{
EntityConnectionStringBuilder entityBuilder = new EntityConnectionStringBuilder();
entityBuilder.Provider = "System.Data.SqlClient";
entityBuilder.ProviderConnectionString = cs;
entityBuilder.Metadata = #"res://*/XXXWebModel.csdl|
res://*/XXXWebModel.ssdl|
res://*/XXXWebModel.msl";
return entityBuilder.ToString();
}
When connection string is provided in format that doesn't contain metadata information where to look for generated model entity framework finds that code first approach is used and that is why method OnModelCreating is called that throws exception :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
Sending connection string formated as entity framework connection string that contains metadata information solves problem.
Related
I want to develop a structure that will support generic DbContexts in the .Net Core Web API project and can be used in the repository pattern. Mysql and PostreSql databases are sufficient for now. Can you help with this?
Create a new .Net Core Web API project.
Add a new folder in the project called DataAccess and create a new class called BaseDbContext that inherits from DbContext. This class will contain the common properties and methods for all your DbContexts.
public class BaseDbContext : DbContext
{
public BaseDbContext(DbContextOptions options) : base(options) { }
//...
}
Create a new class called MySqlDbContext that inherits from BaseDbContext. This class will contain the properties and methods specific to the MySQL database.
public class MySqlDbContext : BaseDbContext
{
public MySqlDbContext(DbContextOptions<MySqlDbContext> options) : base(options) { }
//...
}
Create a new class called PostgreSqlDbContext that inherits from BaseDbContext. This class will contain the properties and methods specific to the PostgreSQL database.
public class PostgreSqlDbContext : BaseDbContext
{
public PostgreSqlDbContext(DbContextOptions<PostgreSqlDbContext> options) :
base(options) { }
//...
}
Create a new folder in the project called Repositories and create a new class called BaseRepository that will contain the common methods for all your repositories.
public class BaseRepository<T> where T : class
{
protected readonly DbContext _context;
public BaseRepository(DbContext context)
{
_context = context;
}
//...
}
Create new classes for each repository that inherits from BaseRepository and pass the appropriate DbContext to the base constructor.
public class MySqlRepository : BaseRepository<MySqlDbContext>
{
public MySqlRepository(MySqlDbContext context) : base(context) { }
//...
}
and
public class PostgreSqlRepository : BaseRepository<PostgreSqlDbContext>
{
public PostgreSqlRepository(PostgreSqlDbContext context) : base(context) { }
//...
}
In your controllers you can now inject the appropriate repository and use it to interact with the database.
You can also use dependency injection to inject the appropriate DbContext based on the configuration.
Additional:
Here is an example of how you can do this:
In your appsettings.json file, add a section for the database connection information, such as:
{
"ConnectionStrings": {
"MySqlConnection": "Server=localhost;Database=mydb;User=user;Password=password;",
"PostgreSqlConnection": "Host=localhost;Database=mydb;Username=user;Password=password;"
},
"DatabaseProvider": "MySql"
}
Here the DatabaseProvider field indicate the database that user wants to use.
2. In your Startup.cs file, create a new method called ConfigureDbContext that will configure the DbContext based on the configuration in the appsettings file
public void ConfigureDbContext(IServiceCollection services)
{
var connectionString = Configuration.GetConnectionString("MySqlConnection");
var provider = Configuration.GetValue<string>("DatabaseProvider");
if(provider == "MySql")
{
services.AddDbContext<MySqlDbContext>(options => options.UseMySql(connectionString));
}
else if (provider == "PostgreSql")
{
services.AddDbContext<PostgreSqlDbContext>(options => options.UseNpgsql(connectionString));
}
}
In the ConfigureServices method in Startup.cs, call the ConfigureDbContext method to configure the DbContext.
public void ConfigureServices(IServiceCollection services)
{
ConfigureDbContext(services);
//...
}
In your controllers, you can now inject the appropriate DbContext using dependency injection.
public class MyController : Controller
{
private readonly IDbContext _context;
public MyController(IDbContext context)
{
_context = context;
}
//...
}
I have DataContext and StartUp class in different projects and to add a new migration in Data project I used the below command:
dotnet ef migrations add IdentityAdded -s ..\API\API.csproj
And here is project structure:
I just added ASP.Net Core Identity to the project based on .Net 5 and configured it as below:
public class DataContext : IdentityDbContext<AppUser, AppRole, int,
IdentityUserClaim<int>, AppUserRole, IdentityUserLogin<int>,
IdentityRoleClaim<int>, IdentityUserToken<int>>
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
ChangeTracker.LazyLoadingEnabled = false;
}
... DbSets
... protected override void OnModelCreating(ModelBuilder modelBuilder)
{ ... }
}
IdentityServiceExtension.cs:
public static class IdentityServiceExtension
{
public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddIdentityCore<AppUser>(opt =>
{
opt.Password.RequireNonAlphanumeric = false;
})
.AddRoles<AppRole>()
.AddRoleManager<RoleManager<AppRole>>()
.AddSignInManager<SignInManager<AppUser>>()
.AddRoleValidator<RoleValidator<AppUser>>()
.AddEntityFrameworkStores<DataContext>();
}
}
I just inherited some classes such as AppUser, AppRole and AppUserRole from Identity Classes like this:
public class AppRole : IdentityRole<int>
{
public ICollection<AppUserRole> TheUserRolesList { get; set; }
}
After running the migration I get the following error:
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Identity.RoleManager1[Core.Models.Entities.User.AppRole] Lifetime: Scoped ImplementationType: Microsoft.AspNetCore.Identity.RoleManager1[Core.Models.Entities.User.AppRole]': Implementation type 'Microsoft.AspNetCore.Identity.RoleValidator1[Core.Models.Entities.User.AppUser]' can't be converted to service type 'Microsoft.AspNetCore.Identity.IRoleValidator1[Core.Models.Entities.User.AppRole]')
What's wrong with this implementation?
You didn't register properly, instead of:
.AddRoleValidator<RoleValidator<AppUser>>()
add:
.AddRoleValidator<RoleValidator<AppRole>>()
Your error points out that it can't instantiate Microsoft.AspNetCore.Identity.RoleValidator with the Core.Models.Entities.User.AppUser, instead it requires Core.Models.Entities.User.AppRole.
I " ve got a project under .net core. I want to register Ef Core Context with Castle windosr But I couldn 't find a solution to EfCore Wireup context in .net core. Thank you.
If you want to do this , first you need to know that you have a Context that has a DbContextOptionsBuilder parameter that has a DbContextOptionsBuilder parameter if you have added these constructor , you need to register this too , and now the code I " ve written below makes you less self - sufficient to use the OnConfiguring method.
public static class DbContextFactoryBuilder
{
public static IDbContext Create(string connectionString)
{
var result = new MyDbContext(new DbContextOptionsBuilder().UseSqlServer(connectionString).Options);
return result;
}
}
and code for register in castle.
container.Register(Component.For<MyDbContext>().UsingFactoryMethod(c => DbContextFactoryBuilder.Create(#"---your connection string---")));
Another alternative is:
public class MyContext : DbContext
{
private readonly string connectionString;
public MyContext (string connectionString)
{
this.connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(this.connectionString);
}
}
Component.For<MyContext>().DependsOn(Property.ForKey<string>().Eq("your connection string")));
With EF Core, DbContext is registered as Scoped by EF service extension. This is desirable because DbContext is not thread-safe and therefore it should be created per request.
ServiceStack IOC treats any Scoped registration in Startup as singleton, which contradicts with the point above.
One possible solution is to not use EF Core's service extension, but that seems to bring a lot of boilerplate code and reduce maintainability. Is there any better way?
--
UPDATE
I'd like to provide sample code for clarity
I added a private Guid to the DbContext class so that I can tell whether we have the new instance.
public class BloggingContext : DbContext
{
private readonly Guid _instance;
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
_instance = Guid.NewGuid();
}
public DbSet<Blog> Blogs { get; set; }
}
With .NET Core MVC, the controller code looks like
public class BlogsController : Controller
{
private readonly BloggingContext _context;
public BlogsController(BloggingContext context)
{
_context = context;
}
// skip for readability
}
For each request hitting the controller, the _instance inside BloggingContext returns an unique value. However, when using within a ServiceStack service, _instance always returns the same value.
public class BlogService : ServiceStack.Service
{
private readonly BloggingContext _context;
public BlogService(BloggingContext context)
{
_context = context;
}
// skip for readability
}
This behaviour is consistent with ServiceStack documentation about .NET Core Container Adapter that scoped dependencies registered in .NET Core Startup is singleton within ServiceStack. However, it is not desirable because we want DbContext to be created per request.
My solution is that I move the DbContext registration into AppHost code as below
public override void Configure(Container container)
{
container.AddScoped(c =>
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
return new BloggingContext(optionsBuilder.Options);
});
}
This code works as I expect. Every instance of BloggingContext injected into my BlogService is now unique. However, I find myself unable to use any service collection extension which is very handy in .Net Core Startup anymore. For example, I want to use Entity Framework Unit Of Work and I couldn't call
services
.AddUnitOfWork<BloggingContext>();
Instead, I have to wire up all dependencies of that library myself like
public override void Configure(Container container)
{
container.AddScoped(c =>
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
return new BloggingContext(optionsBuilder.Options);
});
container.AddScoped<IRepositoryFactory, UnitOfWork<BloggingContext>>();
container.AddScoped<IUnitOfWork, UnitOfWork<BloggingContext>>();
container.AddScoped<IUnitOfWork<BloggingContext>, UnitOfWork<BloggingContext>>();
}
You should be able to register it in .NET Core's IOC like any .NET Core App:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BloggingContext>(options =>
options.UseSqlite("Data Source=blog.db"));
}
Then reference like a normal dependency in your ServiceStack Services:
public class MyServices : Service
{
public BloggingContext BloggingContext { get; set; }
}
Which uses ServiceStack's .NET Core Container Adapter to resolve any dependencies not in ServiceStack's IOC, in .NET Core's IOC.
I'm creating an webapi application in dotnet core with entity framework. When I gone through documentation. I can use DI to inject the dbcontext object in dotnet core. But when I'm doing this the whole application using one dbcontext object. How do I make the dbcontext as transient? If any example is there it will really help me.
Please find my existing code below.
This is the code I wrote in ConfigureService
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataAccess.XXXXContext>(options => options.UseMySQL(Configuration["ConnectionStrings:DefaultConnection"]),ServiceLifetime.Transient);
}
This is the code i wrote in DBcontext class
public partial class XXXXContext : DbContext
{
private readonly Logger logger = LogManager.GetCurrentClassLogger();
public XXXXContext(DbContextOptions<XXXXContext> options) :base(options)
{
logger.Debug("XXXXContext created");
}
}
If you see i have written already Transient in the AddDbContext method.So everytime if it create object. My constructor should call. But i'm getting call only once.
I`ve already done this using a separated statement.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DataAccess.XXXXContext>(options => options.UseMySQL(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddTransient<DataAccess.XXXXContext>();
}