I'm using ef core 6 and I want to get a service from DI inside my db context. This is my DbContext:
public class MyDbContext : DbContext
{
public MyDbContext (DbContextOptions<MyDbContext> opt) : base(opt)
{
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var myService = this.GetService<IMyService>();
// Do some stuffs before saving with myService
var result = await base.SaveChangesAsync(cancellationToken);
return result;
}
}
The IMyService and MyDbContext already introduced with the DI:
services.AddScoped<IMyService, MyService>();
services.AddDbContextPool<MyDbContext>(opt => opt.UseSqlServer(connectionString));
The service doesn't inject, and this line throws an error:
var myService = this.GetService<IMyService>();
Error:
Cannot resolve scoped service 'IMyService' from root provider
What should I do? Do I miss something?
DI works the same way with DbContexts as any other injected service - by adding the dependency as a constructor parameter.
From the comments it seems that what you actually want is to use second-level caching (between sessions/transactions/Units-of-Work) with EF Core. This isn't available out of the box and in general, is a concept that isn't as popular now as it was some years ago. ORMs aren't used to talk to non-relational databases so applications use separate object caching at a higher level instead.
There are some NuGet packages that do add second-level caching to EF Core, for example EFCoreSecondLevelCacheInterceptor. This project uses EF Core DbCommand interceptors to track and cache the data loaded or persisted by EF Core. The landing page examples show how to use either in-memory or Redis caching.
Once you configure caching, adding it to a DbContext is easy :
public static class MsSqlServiceCollectionExtensions
{
public static IServiceCollection AddConfiguredMsSqlDbContext(this IServiceCollection services, string connectionString)
{
services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
optionsBuilder
.UseSqlServer(
connectionString,
sqlServerOptionsBuilder =>
{
sqlServerOptionsBuilder
.CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds)
.EnableRetryOnFailure()
.MigrationsAssembly(typeof(MsSqlServiceCollectionExtensions).Assembly.FullName);
})
.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>()));
return services;
}
}
The relevant part only adds the DbCommand interceptor SecondLevelCacheInterceptor
.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>()));
The package allows caching the results of specific queries :
var post1 = context.Posts
.Where(x => x.Id > 0)
.OrderBy(x => x.Id)
.Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5))
.FirstOrDefault();
It can also be configured to cache all queries:
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_");
options.CacheAllQueries(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30));
});
Specific queries :
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_")
/*.CacheQueriesContainingTypes(
CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableTypeComparison.Contains,
typeof(Post), typeof(Product), typeof(User)
)*/
.CacheQueriesContainingTableNames(
CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableNameComparison.ContainsOnly,
"posts", "products", "users"
);
});
Or avoid caching specific queries
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true).UseCacheKeyPrefix("EF_")
// How to skip caching specific commands
.SkipCachingCommands(commandText =>
commandText.Contains("NEWID()", StringComparison.InvariantCultureIgnoreCase));
});
Related
How do I connect to multiple database instances from a Blazor WebAssembly project, where I have also added the ASP.NET Core hosted?
My thought was to initiate the DBContexts into the `Startup.cs`` (from Blazor.Server Application which has a reference to Blazor.Client Application):
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlite(
"connection string holder ..."));
}
like this but I want to let the user choose in my View if they want to do a test run of the App where the SQLite database instance will be created. The regular run will be an instance to SQL Server database. How can I do this in the ConfigureServices method?
Right now I am building the DBContexts classes, are these effected too?
The controllers are not done yet, are ASP.NET Core MVC controllers the right choice?
You can implement that using 2 DB contexts, one interface and a service choosing the context from data sent in API requests:
DB context interface
public interface IDatabaseContext
{
// add all DbSet declaration here
}
db context
public class DatabaseContext : IDatabaseContext
{
// db context implementation
}
Test db context
public class TestDatabaseContext: DatabaseContext
{
// add your constructor
}
DbContext resolver service
public class DbContextResolver
{
public bool IsTest { get; set; }
Server side DI setup
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlServer(
"SqlServer connection string holder ..."))
.AddDbContext<TestDatabaseContext>(options =>
options.UseSqlite(
"Sqlite connection string holder ..."))
.AddScoped<DbContextResolver>())
.AddScoped<IDatabaseContext>(p =>
{
var resolver = p.GetRequiredService<DbContextResolver>();
if (resolver.IsTest)
{
retrun p.GetRequiredService<TestDatabaseContext>();
}
return p.GetRequiredService<DatabaseContext>();
}));
Select the DB Context from request
public void Configure(IApplicationBuilder app)
{
app.Use((context, next) =>
{
var resolver = context.RequestServices.GetRequiredService<DbContextResolver>();
resolver.IsTest = context.Request.Query.ContainsKey("test"); // here the context is choosed by query string but you can choose to send an header instead
return next();
}
}
use the chosen DB context in controller or a service
public class MyController : Controller
{
public MyController(IDatabaseContext context)
...
}
I haven't tried but you can try something like this-
In Blazor client Program.cs -
builder.Services.AddDatabaseStorage();
In Blazor server Startup.cs -
public void ConfigureServices(IServiceCollection services)
{
services.AddDatabaseStorage();
}
Create a static class and add all the DB storage interfaces and their implementations-
public static class StorageServiceCollections
{
public static IServiceCollection AddDatabaseStorage(this IServiceCollection
services)
{
//you may also return DB service based on certain condition here. Like factory, singleton pattern.
return services
.AddSingleton<IDBStorageService, SQLStorageService>()
.AddSingleton<IMongoDBStorageService, MongoStorageService>();
}
}
Stablish DB connections and their implementation classes e.g. SQLStorageService, MongoStorageService Something like this -
public MongoStorageService()
{
var client = new MongoClient("mongodb://localhost:23456");
_mongoDatabase = client.GetDatabase("DB");
}
Inject in razor components to call them -
#inject IDBStorageService dbStorage
Server side Blazor :
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
services.AddDbContext<ApplicationDbContext>(options => options.UseFirebird(Configuration.GetConnectionString("FirebirdConnection"));
Then register your service:
services.AddSingleton<SalesService>();
where your service has a model you can return using any connection:
public Task<Sales[]> GetSalesRangeAsync(DateTime StartDate, DateTime EndDate)
{
IDbConnection db = new FbConnection(Startup.FirebirdDatabase);
// QUERY
return Task.FromResult(Enumerable.Range(0, 1).Select(index => new Sales
{
// RESULTS
}).ToArray());
}
I try to setup the DI for a new ASP.NET Core site and I have this code:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Get the configuration from the app settings.
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// Get app settings to configure things accordingly.
var appSettings = Configuration.GetSection("AppSettings");
var settings = new AppSettings();
appSettings.Bind(settings);
services
.AddOptions()
.Configure<AppSettings>(appSettings)
.AddSingleton<IConfigurationRoot>(config)
.AddDbContext<MyDbContext>(builder =>
{
builder.UseSqlServer(config.GetConnectionString("myConn"));
}, ServiceLifetime.Transient, ServiceLifetime.Transient);
services.AddSingleton<ILoadTestCleanUpServiceRepository, LoadTestCleanUpServiceRepository>();
...
Now, the LoadTestCleanUpServiceRepository depends on the MyDbContext:
public class LoadTestCleanUpServiceRepository : ILoadTestCleanUpServiceRepository
{
private readonly MyDbContext _dbContext;
public LoadTestCleanUpServiceRepository(MyDbContext dbContext)
{
_dbContext = dbContext;
}
...
..and the DB Context is this:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> ctxOptions) : base(ctxOptions)
{
}
}
When I run the application, I get this error:
InvalidOperationException: Unable to resolve service for type
'MyCode.Infrastructure.Common.MyDbContext' while attempting to
activate
'MyCode.Infrastructure.LoadTestCleanUpService.LoadTestCleanUpServiceRepository'.
I have tried changing the ServiceLifetime options and adding this extra code:
services.AddTransient<MyDbContext>(sp => new MyDbContext(config));
...but nothing seems to help and I cannot understand why this doesn't work. It does try to construct the repository, but why can't it construct the DB Context too? It doesn't even reach the point where I call UseSqlServer()!
Any ideas?
UPDATE 1:
Hmm... I now see this. Most likely it is related:
UPDATE 2:
I have now :
Replaced EF 6 with Microsoft.EntityFrameworkCore.SqlServer
Upgraded to netcoreapp2.2 target framework to solve some conflicting assembly versions.
Made the repository scoped.
But I still get the same error.
I see you have registered LoadTestCleanUpServiceRepository as Singleton while MyDbContext as Transient and then you are trying to resolve MyDbContext from LoadTestCleanUpServiceRepository. That's the problem. According to ASP.NET Core Service lifetimes documentation:
It's dangerous to resolve a scoped service/transient service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
Solution is: register LoadTestCleanUpServiceRepository and MyDbContext as follows:
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("YourConnectionStringName")));
services.AddScoped<ILoadTestCleanUpServiceRepository, LoadTestCleanUpServiceRepository>();
Now problem should go away.
After reading a lot on the subject it looks like a good approach is to create a context once per request.
To achive this, in in Startup.cs I have declared two statics objects
public class Startup
{
public static DbContextOptionsBuilder<MCContext> optionsBuilder = new DbContextOptionsBuilder<MCContext>();
public static MCContext db = null;
then init optionsBuilder when the app starts (so only once):
public Startup(IConfiguration configuration)
{
optionsBuilder.UseSqlServer(configuration["ConnectionStrings:DefaultConnection"]);
}
while db at each request:
app.Use(async (context, next) =>
{
db = db ?? new MCContext(optionsBuilder.Options);
await next.Invoke();
});
Then when I need the context in a controller or in a razor page cs I can get it using Startup.db:
User cur = await Startup.db.User.Where(x => x.Id == uid).FirstOrDefaultAsync();
I do not Dispose the Context as per here
As I'm not familiar with DI I wonder if this approach is correct or if I am missing anything.
base on What is new in EF Core 2.0 - EF Core | Microsoft Docs
If you want a new context once per request : AddDbContext
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MCContext >(
options => options.UseSqlServer(connectionString));
}
then you can
public class TiketsController : ControllerBase
{
private readonly MCContext _context;
public TiketsController (MCContext context)
{
_context = context;
}
}
The basic pattern for using EF Core in an ASP.NET Core application
usually involves registering a custom DbContext type into the
dependency injection system and later obtaining instances of that type
through constructor parameters in controllers. This means a new
instance of the DbContext is created for each requests.
but if you need High Performance/Safe reuse : AddDbContextPool
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<MCContext >(
options => options.UseSqlServer(connectionString));
}
then you can
public class TiketsController : ControllerBase
{
private readonly MCContext _context;
public TiketsController (MCContext context)
{
_context = context;
}
}
If this method is used, at the time a DbContext instance is requested
by a controller we will first check if there is an instance available
in the pool. Once the request processing finalizes, any state on the
instance is reset and the instance is itself returned to the pool.
If you are not creating object, then don't dispose it. Let IOC container handle it.
btw, I don't think this block of code is required. MCContext is a dependency, so its instance creation and injection gets done by IOC container.
app.Use(async (context, next) =>
{
db = db ?? new MCContext(optionsBuilder.Options);
await next.Invoke();
});
Is it possible to use ASP.NET Identity without Entity Framework and Entity Framework migrations? The rest of my application will be using a Micro ORM for data access. However, the application is using the built in ASP.NET Identity Individual User accounts.
My goal is to still be able to use the built in UserManager and LoginManager classes and additionally retrieve a list of the Users using the Micro ORM and do away with anything to do with EF/Migrations. Is this possible? It doesn't seem like it is since the original database structure is created by Applying the initial migration.
If someone has a good technique for doing this, please share.
First you need to create a custom user Store:
public class UserStore : IUserStore<IdentityUser>,
IUserClaimStore<IdentityUser>,
IUserLoginStore<IdentityUser>,
IUserRoleStore<IdentityUser>,
IUserPasswordStore<IdentityUser>,
IUserSecurityStampStore<IdentityUser>
{
// interface implementations not shown
}
Then you need to register it into the dependency injection container:
// Add identity types
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddDefaultTokenProviders();
// Identity Services
services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();
This is documented here.
Asp.Net Identity has abstracted away the stores it needs and documentation on their stores is here;
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-custom-storage-providers
This is an example of a store;
public class InMemoryUserStore<TUser> :
IUserStore<TUser>,
IUserLoginStore<TUser>,
IUserClaimStore<TUser>,
IUserPasswordStore<TUser>,
IUserSecurityStampStore<TUser>,
IUserTwoFactorStore<TUser>,
IUserEmailStore<TUser>,
IUserLockoutStore<TUser>,
IUserAuthenticatorKeyStore<TUser>,
IUserTwoFactorRecoveryCodeStore<TUser>,
IUserPhoneNumberStore<TUser> where TUser: MemoryIdentityUser
{
...
}
You can also have your own User object, and it doesn't have to inherit from anything.
public class MemoryIdentityUser
{
private List<MemoryUserClaim> _claims;
private List<MemoryUserLogin> _logins;
private List<MemoryUserToken> _tokens;
...
}
Asp.Net Identity is an engine and as such is opinionated. It is that opinion that drove the abstractions of the stores. I wish the Asp.Net Identity docs has full sequence diagrams as to how it interacts with the stores. At a minimum a few reference sequences that have to be honored.
The store has some quirks where it has required methods that are only to mutate private data in the implementation and then followed up by update calls that assume you will commit that data to persistent storage.
You might want to check out this project;
https://github.com/ghstahl/AspNetCore.2.InMemoryIdentity
You can see what you need to do without the burden of having a database.
Hooking it up;
// My user is custom, so I made ApplicationUser inherit
public class ApplicationUser : MemoryIdentityUser
{
}
Startup.cs;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserStore<ApplicationUser>>(provider =>
{
return new InMemoryUserStore<ApplicationUser>();
});
services.AddIdentity<ApplicationUser>(Configuration)
.AddDefaultTokenProviders();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}
In AddIdentity, the following illustrates to the extent you can bring in your own implementations
public static class InMemoryIdentityServiceCollectionExtensions
{
public static IdentityBuilder AddIdentity<TUser>(this IServiceCollection services, IConfiguration configuration)
where TUser : class => services.AddIdentity<TUser>(configuration,null);
public static IdentityBuilder AddIdentity<TUser>(this IServiceCollection services, IConfiguration configuration,Action<IdentityOptions> setupAction)
where TUser : class
{
// Services used by identity
var authenticationBuilder = services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = IdentityConstants.ExternalScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme,
o => o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme)
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
// Hosting doesn't add IHttpContextAccessor by default
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Identity services
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser>>();
services.TryAddScoped<UserManager<TUser>, AspNetUserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), services);
}
}
There are a bunch of IUserStore implementations out there, with every type of backing database. I copied my InMemoryUserStore from another project that was using MongoDB as a backing DB.
I am having some trouble with autofac and webapi, I think its due to my lack of understanding on the correct way to register them.
I have a repository layer using entityframework and standard type of repository pattern to interact with the database.
My repository layer, I inject the context via the constructor and also other repositories, in my example below I am passing in the customerRepository.
e.g.
public CustomerTransactionsRepository(MyContext context,
ICustomerRepository customerRepository,
ILog log)
{
_context = context;
_customerRepository = customerRepository;
_log = log;
}
public async Task<CustomerWithTransactions> FindCustomerSalesAsync(int customerId)
{
var customer= await _customerRepository.FindAsync(customerId);
var transactions = await _context.Transactions
.Include(c => c.CancelledSales)
.SingleOrDefaultAsync(c => c.CustomerId== customer.UserId);
return transactions;
}
My startup.cs configuration looks like
private static void RegisterDependences(ContainerBuilder builder)
{
builder.RegisterType<AifsLog>().As<ILog>().InstancePerDependency();
builder.RegisterAssemblyTypes(Assembly.Load("MyNS.DAL"))
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces().InstancePerDependency();
builder.Register(c => new LoggingActionFilter())
.AsWebApiActionFilterFor<ApiController>()
.PropertiesAutowired();
builder.RegisterType<MyContext>().InstancePerRequest();
}
When I try running the application, I get an error
No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.
I think its due to using InstancePerDependency for my repository and InstancePerRequest for my dbContext.
Anyone else had this problem or can see what I am doing wrong?