Entity Framework Multiple Connections Error - asp.net-core-webapi

I have a scenario wherein I have multiple connection strings defined under appsettings.json like this:
"ConnectionString": {
"ConnectionZone1": "Server=(localdb)\\mssqllocaldb;Database=Blogging;Trusted_Connection=True;",
"ConnectionZone2": "Server=localhost;Database=Blogging;Trusted_Connection=True;"
},
This I have registered in my startup.cs file as well:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContextZone1>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ConnectionZone1")));
services.AddDbContext<DbContextZone2>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ConnectionZone2")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
I have created Model and context classes using database first approach, and registered my context classes as follows:
public partial class BloggingContext : DbContext
{
public BloggingContext()
{
}
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
}
public virtual DbSet<Blog> Blog { get; set; }
public virtual DbSet<Post> Post { get; set; }
and created two other context classes which inherits from the above main base class:
public class DbContextZone1 : BloggingContext
{
public DbContextZone1()
{
}
}
public class DbContextZone2 : BloggingContext
{
public DbContextZone2()
{
}
}
Now I have created my API controllers and am trying to call these context methods.
[HttpGet]
public async Task<ActionResult<IEnumerable<object>>> GetItems()
{
if (alternate)
{
alternate = false;
using (var context = new DbContextZone1())
{
return await context.Blog.ToListAsync();
}
}
using(var context = new DbContextZone2())
{
return await context.Post.ToListAsync();
}
}
The issue is when I run my application it throws error that my context class should have parameterized constructor in order to pass options.
So in the DbContextZone1 and DbContextZone2 constructor which context options parameter will come?. I tried putting like this, but it never works and throws error when I call the API controller:
public class DbContextZone1 : BloggingContext
{
public DbContextZone1(DbContextOptions<BloggingContext> options)
: base(options)
{
}
}
public class DbContextZone2 : BloggingContext
{
public DbContextZone2(DbContextOptions<BloggingContext> options)
: base(options)
{
}
}
And this the error:
So any help or code ideas or suggestions in how to achieve multiple connections or make my code right?.

From your appsettings.json,it seems that you want to connect to the same database in different server.You are no need to create a base DbContext,just inherits default DbContext like below:
public class DbContextZone1 : DbContext
{
public DbContextZone1(DbContextOptions<DbContextZone1> options)
: base(options)
{
}
public virtual DbSet<Blog> Blog { get; set; }
}
public class DbContextZone2 :DbContext
{
public DbContextZone2(DbContextOptions<DbContextZone2> options)
: base(options)
{
}
public virtual DbSet<Post> Post { get; set; }
}
And call the API Controller like below:
private readonly DbContextZone1 _context1;
private readonly DbContextZone2 _context2;
public ABCController(DbContextZone1 context1, DbContextZone2 context2)
{
_context1 = context1;
_context2 = context2;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<object>>> GetItems()
{
//....
if (alternate)
{
alternate = false;
return await _context1.Blog.ToListAsync();
}
return await _context2.Post.ToListAsync();
}

Change Your DbContext Cunstructors to this:
public class DbContextZone1 : BloggingContext
{
public DbContextZone1(DbContextOptions<DbContextZone1> options)
: base(options)
{
}
}
public class DbContextZone2 : BloggingContext
{
public DbContextZone2(DbContextOptions<DbContextZone2> options)
: base(options)
{
}
}
Update:
If you've got errors after changing your DbContext class is because you're trying to access default constructors like below:
using (var context = new DbContextZone1())
when there is no implemented default constructor in your classes. As you've registered your DbContext classes in .net core DI system, you just need to inject DbContextZone1 and DbContextZone2 in Controller's constructor, and then you can easily access to contexts. But before doing that you should add your DbSet to DbContext classes and change them to:
public class DbContextZone1 : BloggingContext
{
public DbContextZone1(DbContextOptions<DbContextZone1> options)
: base(options)
{ }
public virtual DbSet<Blog> Blogs { get; set;}
}
public class DbContextZone2 : BloggingContext
{
public DbContextZone2(DbContextOptions<DbContextZone2> options)
: base(options)
{ }
public virtual DbSet<Post> Posts { get; set;}
}
Note: You can keep your DbSets in BloggingContext and then access them via _context in your controller but moving them like above makes your contexts isolated and gives single responsibility to the Contexts.
Now your Controller should be like this:
private readonly DbContextZone1 _context1;
private readonly DbContextZone2 _context2;
public MyController(DbContextZone1 context1, DbContextZone2 context2)
{
_context1 = context1;
_context2 = context2;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<object>>> GetItems()
{
if (alternate)
{
alternate = false;
return await _context1.Blogs.ToListAsync();
}
return await _context2.Posts.ToListAsync();
}

Related

.NET Core identity multiple User types

I have multiple classes (A, B and C) each extends IdentityUser<Guid>. I also have a class called UserRole which extends IdentityRole<Guid>.
The following is my DbContext:
public sealed class EntityDbContext: DbContext
{
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
public DbSet<C> Cs { get; set; }
}
I added identities to IServiceCollection:
services
.AddIdentityCore<A>()
.AddEntityFrameworkStores<EntityDbContext>()
.AddRoles<UserRole>()
.AddUserStore<AUserStore>()
// .AddRoleStore<TRoleStore>()
.AddDefaultTokenProviders();
// Same for B, C
I also have the following stores:
public class AUserStore : UserStore<A, UserRole, EntityDbContext, Guid> { }
public class BUserStore : UserStore<B, UserRole, EntityDbContext, Guid> { }
public class CUserStore : UserStore<C, UserRole, EntityDbContext, Guid> { }
The following is the error I'm getting:
Specified argument was out of the range of valid values. (Parameter
'instance 'AUserStore' with ReturnType AUserStore cannot be cast to
IUserStore')
I don't know if what I'm doing is possible or not. Thanks for any help or hint.
Update
I think I got it working:
class GenericUserRoleStore : RoleStore<UserRole, EntityDbContext, Guid> { }
services.AddIdentity<A, UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<AUserStore>()
.AddRoleStore<GenericUserRoleStore>();
services.AddIdentityCore<B>()
.AddRoles<UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<BUserStore>()
.AddRoleStore<GenericUserRoleStore>();
services.AddIdentityCore<C>()
.AddRoles<UserRole>()
.AddDefaultTokenProviders()
.AddUserStore<CUserStore>()
.AddRoleStore<GenericUserRoleStore>();
Both comments on AddIdentity and AddIdentityCore have this:
Adds and configures the identity system for the specified User and Role types.
and,
Compare source code for AddIdentity<> and AddIdentityCore<>,
Review the default code from project template:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
....
}
I would say: IdentityFramework got confused when you register multiple identity types to it, but we do need it.
I believe what you are looking for are these posts:
Inheritance with EF Code First: Part 1 – Table per Hierarchy (TPH)
Inheritance with EF Code First: Part 2 – Table per Type (TPT)
Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC)
you have 3 above normal options to map your any UserType data to database. and the 1st options give you best performance, but give you very messive datatable when your usertypes are pretty complex. you would choose either of them for your real project as a balance.
Here is sample code with 1st approach:
public class ApplicationUser : IdentityUser<int>
{
public ApplicationUser() : base()
{
UserRoles = new HashSet<ApplicationUserRole>();
}
public int YearsOfExperience { get; set; }
[InverseProperty("User")]
public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
}
public class ProjectManager : ApplicationUser
{
public bool Talktive { get; set; }
}
public class Developer : ApplicationUser
{
public bool IsCSharper { get; set; }
}
public class Tester : Developer
{
public bool WhiteBox { get; set; }
}
public class Documenter : Tester
{
public List<string> Languages { get; set; } = new List<string>();
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, int>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
//get following users directly by following properties
public DbSet<ProjectManager> ProjectManagers { get; set; }
public DbSet<Developer> Developers { get; set; }
public DbSet<Tester> Testers { get; set; }
public DbSet<Documenter> Documenters { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
//prevent creating tables for following usertypes
builder.Ignore<ProjectManager>();
builder.Ignore<Developer>();
builder.Ignore<Tester>();
builder.Ignore<Documenter>();
base.OnModelCreating(builder);
builder.Entity<ApplicationUser>(entity =>
{
entity.HasMany(u => u.UserRoles).WithOne(x => x.User).HasForeignKey(c => c.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
//tell database to use this column as Discriminator
entity.HasDiscriminator<string>("UserType");
});
builder.Entity<ApplicationRole>(entity =>
{
entity.HasKey(x => x.Id);
});
builder.Entity<ApplicationUserRole>(entity =>
{
entity.HasKey(c => new { c.UserId, c.RoleId });
entity.HasOne(x => x.Role).WithMany(x => x.UserRoles).HasForeignKey(x => x.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade);
entity.HasOne(x => x.User).WithMany(x => x.UserRoles).HasForeignKey(x => x.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
});
}
}
when you need your users:
var allUsers = await _dbContext.Users.ToListAsync();
var allProjectManagers = await _dbContext.ProjectManagers.ToListAsync();
var allDevelopers = await _dbContext.Developers.ToListAsync();
var allTesters = await _dbContext.Testers.ToListAsync();
The next thing you want to configure is UserManager, instead of IUserStore.
public class ApplicationUserManager<TUser, TRole>
where TUser : ApplicationUser
where TRole : ApplicationRole
{
private readonly ApplicationDbContext _context;
private readonly UserManager<TUser> _userManager;
private readonly RoleManager<TRole> _roleManager;
public ApplicationUserManager(ApplicationDbContext context,
UserManager<TUser> userManager,
RoleManager<TRole> roleManager)
{
_context = context;
_userManager = userManager;
_roleManager = roleManager;
}
//customize your own base logics here.
}
public class DeveloperUserManager : ApplicationUserManager<Developer, ApplicationRole>
{
}
public class DocumenterUserManager : ApplicationUserManager<Documenter, ApplicationRole>
{
}
Enjoy it.

InvalidOperationException: Unable to resolve service for type with EF dbcontext

I am trying to use Dependency Injection for DB context. I am not sure what i am doing wrong but even after following all the steps i still get the error
Below are the steps that i follow ,suggest me where its going wrong. I am using multi tier project hence my repositories are in my DB access layer and controller in a mvc api application
My DB Context class
public partial class TestDbContext: DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options)
: base(options)
{
}
public virtual DbSet<Table1> Table1{ get; set; }
}
public interface IRepository<T> where T : class
{
IQueryable<T> GetDbSet();
}
public class Repository<T> : IRepository<T> where T : class
{
protected DbContext _entities;
protected readonly DbSet<T> _dbset;
public Repository(DbContext context)
{
_entities = context;
_dbset = context.Set<T>();
}
public virtual IQueryable<T> GetDbSet()
{
return _dbset;
}
}
pulbic interface IUserRepository
{
List<UsersInfo> GetUsers();
}
public class UserRepository:IUserRepository
{
private readonly IRepository<Table1> table1repo;
public UserRepository(IRepository<Table1> _table1Repo)
{
table1repo = _table1Repo;
}
public List<UsersInfo> GetUsers()
{
return table1repo.GetDbSet().ToList();
}
}
public class MyController : : ControllerBase
{
private readonly IUserRepository _UserRepo;
public MyController (IUserRepository UserRepo)
{
_UserRepo= clientInfo;
}
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
var result = _UserRepo.GetUsers();
return new JsonResult(result) { SerializerSettings = new JsonSerializerSettings() { Formatting = Formatting.Indented } };
}
catch(Exception e)
{
throw e;
}
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfiguration>(Configuration);
services.Configure<IISOptions>(options =>
{
options.AutomaticAuthentication = false;
});
services.AddDbContext<TestDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnectionString")));
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Your context type in your repository class should be TestDbContext instead of DbContext.
public class Repository<T> : IRepository<T> where T : class
{
protected TestDbContext _entities;
protected readonly DbSet<T> _dbset;
public Repository(TestDbContext context)
{
_entities = context;
_dbset = context.Set<T>();
}
public virtual IQueryable<T> GetDbSet()
{
return _dbset;
}
}

Entity Framework Core multiple connection strings on same DBContext?

I have an Asp.Net Core app with Entity Framework Core that I initialize as follows:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(sqlConnectionString));
This works fine, but I have a scenario where I need to read/write from the primary database for normal operations but for some operations I need to read from an alternate server (a replication target that's read only that we use for reporting).
With the way the new Core API does everything via Dependency Injection and configuration in StartUp.cs, how do I switch connection strings, but use the same ApplicationDbContext class?
I know that one option would be to have a duplicate of ApplicationDbContext class that I register with the DI system using a different connection string, but I'd like to avoid maintaining two identical DBContext objects just because sometimes I need to read from a different database server (but with the exact same schema).
Thanks in advance for any pointers!
You'll need two DbContexts.
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class MyBloggingContext : BloggingContext
{
}
public class MyBackupBloggingContext : BloggingContext
{
}
And you can register them like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyBloggingContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<MyBackupBloggingContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("BackupConnection")));
}
Can be done like this(tested with .net core 3.1):
public abstract partial class BloggingContext<T> : DbContext where T : DbContext
{
private readonly string _connectionString;
protected BloggingContext(string connectionString) { _connectionString = connectionString; }
protected BloggingContext(DbContextOptions<T> options) : base(options) { }
public virtual DbSet<Blog> Blogs { get; set; }
public virtual DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
}
}
public class MyBloggingContext : BloggingContext<MyBloggingContext>
{
public MyBloggingContext(string connectionString) : base(connectionString) { }
public MyBloggingContext(DbContextOptions<MyBloggingContext> options) : base(options) { }
}
public class MyBackupBloggingContext : BloggingContext<MyBackupBloggingContext>
{
public MyBackupBloggingContext(string connectionString) : base(connectionString) { }
public MyBackupBloggingContext(DbContextOptions<MyBackupBloggingContext> options) : base(options) { }
}
And in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyBloggingContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<MyBackupBloggingContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("BackupConnection")));
}
Connection string can be resolved using IServiceProvider. In the example below I map query parameter to configuration from appsettings.json, but you could inject any other logic you want.
services.AddDbContext<ApplicationDbContext>((services, optionsBuilder) =>
{
var httpContextAccessor = services.GetService<IHttpContextAccessor>();
var requestParam = httpContextAccessor.HttpContext.Request.Query["database"];
var connStr = Configuration.GetConnectionString(requestParam);
optionsBuilder.UseSqlServer(connStr);
});
?database=Connection1 and ?database=Connection2 in query will lead to using different connection strings. It is worth to provide default value, when parameter is missing.
It can be resolved in this way
public class AppDbContext : DbContext
{
private string _connectionString { get; }
public AppDbContext(string connectionString, DbContextOptions<AppDbContext> options) : base(options)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
Then create the DbContext manually
var appDbContext = new AppDbContext("server=localhost;database=TestDB;Trusted_Connection=true", new DbContextOptions<AppDbContext>());
Instead of hard coding the connection, read from the connection string factory.

ASP.NET MVC - Abstraction between Data and Object Layers

I am using ASP.NET EntityFramework MVC (All Latest) with Visual Studio 2013.
I am attempting to come up with a unifying means of standardizing the abstraction between my object and data layer.
I know that many people like to use the IRepository pattern with MVC. My biggest issue with this is that it forces you to create and maintain a second object type (The Repository) for each of the objects that you want to maintain. My solution has been to combing the IRepository method with a Static Factory pattern to make a Static Repository, for instance:
public class SiteDatabase : DbContext
{
// Singleton
private static SiteDatabase _Instance;
public static SiteDatabase Instance
{
get
{
if (_Instance == null)
{
_Instance = new SiteDatabase();
}
return _Instance;
}
}
public DbSet<User> Users { get; set; }
}
public class User : IUser
{
public static User Create(string UserName)
{
User item = new User();
item.UserName = UserName;
SiteDatabase.Instance.Users.Add(item);
return item;
}
public static User Find(string UserName)
{
return SiteDatabase.Instance.Users.SingleOrDefault(x => x.UserName == UserName);
}
public static User[] All()
{
return SiteDatabase.Instance.Users.ToArray();
}
public string Id { get; set; }
public string UserName { get; set; }
protected User()
{
Id = Guid.NewGuid().ToString();
}
public void Delete()
{
SiteDatabase.Instance.Users.Remove(this);
}
}
public class Page
{
public static Page Create(string PageName)
{
...
}
public static Page Find(string PageName)
{
...
}
public static Page[] All()
{
...
}
...
public void Delete()
{
...
}
}
My question is: will this pattern cause me to miss out on any built-in functionality that the normal repository pattern would allow me to capture?
The non-static methods could be handled with an interface, but what about the static methods? Is there any way to have a base class that ensures that static methods will exist in derived classes?

Why does this throw and exception?

I have a base controller:
Public MustInherit Class InjuredWorkerController(Of TManager As IInjuredWorkerManagerBase)
Then I have a home controller:
Public Class HomeController
Inherits InjuredWorkerController(Of IInjuredWorkerManager)
IInjuredWorkerManager inherits IInjuredWorkerManagerBase
Why does this throw a cast exception:
Dim manager = CType(filterContext.Controller, InjuredWorkerController(Of IInjuredWorkerManagerBase)).Manager
Unable to cast object of type 'MyClaim.Controllers.HomeController' to type 'MyClaim.Controllers.InjuredWorkerController`1[SAIF.Web.Mvc.MyClaim.IInjuredWorkerManagerBase]'.
You need to extract an interface for your InjuredWorkerController to make it work, since co- and contravariance only works with interfaces and delegates.
This code compiles and runs (C# console app, I'm not fluent in VB.Net...):
using System;
namespace TestApplication
{
public interface IInjuredWorkerController<out TManager>
where TManager : IInjuredWorkerManagerBase
{
TManager Manager { get; }
}
public abstract class InjuredWorkerController<TManager>
: IInjuredWorkerController<TManager>
where TManager : IInjuredWorkerManagerBase, new()
{
protected InjuredWorkerController()
{
Manager = new TManager();
}
public TManager Manager { get; private set; }
}
public interface IInjuredWorkerManagerBase
{
string Name { get; }
}
public interface IInjuredWorkerManager
: IInjuredWorkerManagerBase {}
public class InjuredWorkerManager : IInjuredWorkerManager
{
public string Name
{
get { return "Homer"; }
}
}
public class HomeController
: InjuredWorkerController<InjuredWorkerManager> {}
internal class Program
{
private static void Main()
{
var controller = new HomeController();
var manager = ((IInjuredWorkerController<IInjuredWorkerManagerBase>)controller).Manager;
Console.Out.WriteLine(manager.Name);
Console.ReadKey();
}
}
}
Eric Lippert's blog series on the subject is a must read.

Resources