.NET Core DI Child Scope with Db Context - .net-core

(I am writing a processor that handles requests in a queue (console app).
I would like to use the .NET Core DI.
So far my code looks like this:
...
var connectionString = exportConfiguration.ConnectionString;
using (var scope = _serviceProvider.CreateScope())
{
var provider = scope.ServiceProvider;
var service = provider.GetRequiredService<MyContext>();
service.SqlConnectionString = sqlConnectionString; // I don't think property injection on a dbcontext will work, it takes its connection string in via the constructor
}
I have read how to assign parameters to the object as shown above, but how do I create a new context based on the connection string that is used in all the objects that the service uses (using constructor injection because thats why dbcontexts take - connection string in constructor)?
(I am not storing my connection string in the queue by the way, a code comes down the queue and my app then chooses the connection string to use).

I have managed to work this out. The key was that when you use CreateScope(), then GetRequiredService(), the DI system will provide new objects. So I just had to provide the correct information. This is now what my code looks like:
// Prior code gets information from a queue, which could be different every time.
// This needs passing as a constructor to the DbContext and possibly other information from the queue to other methods constructors
// (constructor injection not property injection)
var connectionString = queueItem.ConnectionString;
// save the connection string so the DI system (startup.cs) can pick it up
Startup.ConnectionString = connectionString;
using (var scope = _serviceProvider.CreateScope())
{
var provider = scope.ServiceProvider;
var service = provider.GetRequiredService<IMyService>();
// go off and get data from the correct dbcontext / connection string
var data = service.GetData();
// more processing
}
/// The Service has the DbContext in its constructor:
public class MyService : IMyService {
private DbContext _dbContext;
public MyService(DbContext dbContext) {
_dbContext = dbContext;
}
// more stuff that uses dbcontext
}
/// In startup.cs:
public static string ConnectionString {get;set;}
...
builder.Services.AddScoped<IMyService, MyService>();
builder.Services.AddScoped<DbContext>(options => options.UseSqlServer(Startup.ConnectionString));
// Also the following code will work if needed:
// Parameter1 is something that comes from the queue and could be different for each
// CreateScope()
build.Services.AddScoped<IMyOtherService>((_) =>
new MyOtherService(Startup.Parameter1));
I hope this helps somebody, because when I was googling around I couldn't find out how to do this.

Related

Unity to DryIoC conversion ParameterOverride

We are transitioning from Xamarin.Forms to .Net MAUI but our project uses Prism.Unity.Forms. We have a lot of code that basically uses the IContainer.Resolve() passing in a collection of ParameterOverrides with some primitives but some are interfaces/objects. The T we are resolving is usually a registered View which may or may not be the correct way of doing this but it's what I'm working with and we are doing it in backend code (sometimes a service). What is the correct way of doing this Unity thing in DryIoC? Note these parameters are being set at runtime and may only be part of the parameters a constructor takes in (some may be from already registered dependencies).
Example of the scenario:
//Called from service into custom resolver method
var parameterOverrides = new[]
{
new ParameterOverride("productID", 8675309),
new ParameterOverride("objectWithData", IObjectWithData)
};
//Custom resolver method example
var resolverOverrides = new List<ResolverOverride>();
foreach(var parameterOverride in parameterOverrides)
{
resolverOverrides.Add(parameterOverride);
}
return _container.Resolve<T>(resolverOverrides.ToArray());
You've found out why you don't use the container outside of the resolution root. I recommend not trying to replicate this error with another container but rather fixing it - use handcoded factories:
internal class SomeFactory : IProductViewFactory
{
public SomeFactory( IService dependency )
{
_dependency = dependency ?? throw new ArgumentNullException( nameof(dependency) );
}
#region IProductViewFactory
public IProductView Create( int productID, IObjectWithData objectWithData ) => new SomeProduct( productID, objectWithData, _dependency );
#endregion
#region private
private readonly IService _dependency;
#endregion
}
See this, too:
For dependencies that are independent of the instance you're creating, inject them into the factory and store them until needed.
For dependencies that are independent of the context of creation but need to be recreated for each created instance, inject factories into the factory and store them.
For dependencies that are dependent on the context of creation, pass them into the Create method of the factory.
Also, be aware of potential subtle differences in container behaviours: Unity's ResolverOverride works for the whole call to resolve, i.e. they override parameters of dependencies, too, whatever happens to match by name. This could very well be handled very differently by DryIOC.
First, I would agree with the #haukinger answer to rethink how do you pass the runtime information into the services. The most transparent and simple way in my opinion is by passing it via parameters into the consuming methods.
Second, here is a complete example in DryIoc to solve it head-on + the live code to play with.
using System;
using DryIoc;
public class Program
{
record ParameterOverride(string Name, object Value);
record Product(int productID);
public static void Main()
{
// get container somehow,
// if you don't have an access to it directly then you may resolve it from your service provider
IContainer c = new Container();
c.Register<Product>();
var parameterOverrides = new[]
{
new ParameterOverride("productID", 8675309),
new ParameterOverride("objectWithData", "blah"),
};
var parameterRules = Parameters.Of;
foreach (var po in parameterOverrides)
{
parameterRules = parameterRules.Details((_, x) => x.Name.Equals(po.Name) ? ServiceDetails.Of(po.Value) : null);
}
c = c.With(rules => rules.With(parameters: parameterRules));
var s = c.Resolve<Product>();
Console.WriteLine(s.productID);
}
}

Creating a proper db context .NET Core 3

I'm trying to explicitly create a db context in .NET Core 3 startup
I know I can do this in startup.cs ConfigureServices to inject a dbcontext into the controller (which works fine):
String dbconn = Configuration["ConnectionStrings:VerseDBConnectionStringMSSQL"];
services.AddDbContext<VerseDBContext>(options => options.UseSqlServer(dbconn));
but I am trying to generalize the storage provider (and keep the controller code the same for all storage readers), so it takes an IVerseStorageReader interface, instead of a DB context (as I may want to read from memory, or xmlfile, etc) and use the same code in the controller, just switch it based on config in appsettings. One of the VerseStorageReaders takes a db context in constructor:
public class DBVerseReader : IVerseStorageReader
{
private VerseDBContext _dbContext;
public DBVerseReader(VerseDBContext dbContext)
{
_dbContext = dbContext;
}
...
}
My problem is: I can't quite figure out the syntax right for creating the db context explicitly. I'm very close (I think) but this doesn't work:
String dbconn = Configuration["ConnectionStrings:VerseDBConnectionStringMySQL"];
var optionsBuilder = new DbContextOptionsBuilder<VerseDBContext>();
optionsBuilder.UseMySql(dbconn);
VerseDBContext x = optionsBuilder.UseMySql<VerseDBContext>(dbconn); <-- compile error
services.AddSingleton<IVerseStorageReader>(new DBVerseReader(x));
Can someone clue me on what I'm doing wrong? What I'm trying to inject is an instance of IVerseStorageReader, not a DBContext. There are overloads of VerseStorageReader that take a db context as input, and others which take other inputs (e.g. xmlfilename, etc)...so I want startup to add an instance of one of the IVerseStorageReaders and that gets injected (not a dbcontext injection).
You have to get the options from the builder after configuring it
String dbconn = Configuration["ConnectionStrings:VerseDBConnectionStringMySQL"];
var optionsBuilder = new DbContextOptionsBuilder<VerseDBContext>();
optionsBuilder.UseMySql(dbconn);
DbContextOptions<VerseDBContext> options = optionsBuilder.Options;
VerseDBContext x = new VerseDBContext(options);
services.AddSingleton<IVerseStorageReader>(new DBVerseReader(x));
But since DbContext derived classes are usually registered as scoped, I would suggest you move the context into the factory delegate and register the service abstraction as scoped also.
String dbconn = Configuration["ConnectionStrings:VerseDBConnectionStringMySQL"];
var optionsBuilder = new DbContextOptionsBuilder<VerseDBContext>();
optionsBuilder.UseMySql(dbconn);
DbContextOptions<VerseDBContext> options = optionsBuilder.Options;
services.AddScoped<IVerseStorageReader>( sp => {
VerseDBContext x = new VerseDBContext(options);
return new DBVerseReader(x);
});

UnitTest in ASP.NET with Postgres

I write some tests of created system which worked with PostgreSQL. I create in solution new project with type Class Library (.NET Core). Then, i create class, which testing class DocumentRepository. But in constructor of DocumentRepository is used IConfiguration (for connecting with database), and this IConfiguration i can't call in test class. How i can to imitate connecting with database in UnitTest?
Here class, which i want testing
public class DocumentsRepository : IRepository<Documents>
{
private string connectionString;
public DocumentsRepository(IConfiguration configuration, string login, string password)
{
connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");
connectionString = connectionString.Replace("username", login);
connectionString = connectionString.Replace("userpassword", password);
}
internal IDbConnection Connection
{
get
{
return new NpgsqlConnection(connectionString);
}
}
public void Add(Documents item)
{
using (IDbConnection dbConnection = Connection)
{
dbConnection.Open();
dbConnection.Execute("SELECT addrecuserdocuments(#DocumentName,#Contents,#DocumentIntroNumber)", item);
}
}
Here's test, which i try use
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApplication4.Controllers;
using WebApplication4.Entites;
using WebApplication4.ViewModels;
using Xunit;
namespace TestsApp
{
public class UserControllerTest
{
private IConfiguration configuration;
private string connectionString;
[Fact]
public async Task IndexUsers()
{
connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");
var aCon = new AccountController(configuration);
var uCon = new UserController(configuration);
LoginModel model = new LoginModel
{
Login = "postgres",
Password = "111"
};
aCon.Authorization(model);
var result = uCon.Index();
var okResult = result.Should().BeOfType<OkObjectResult>().Subject;
var persons = okResult.Value.Should().BeAssignableTo<IEnumerable<Documents>>().Subject;
persons.Count().Should().Be(7);
}
}
}
Test show my error on
var result = uCon.Index();
And get me NullReferenceException.
How i can resolve this problem?
First and foremost, you're not unit testing, you're integration testing. As soon as you've got something like a database connection in the mix, unit testing is well out the window. If your goal is to write unit tests for your repository class, you should be mocking the data store.
Second, you should not inject IConfiguration, if you need some data from your configuration, such as a connection string, you should bind it to a strongly-typed class, and inject that instead:
services.Configure<MyConnectionStringsClass>(Configuration.GetSection("ConnectionStrings"));
Then, inject IOptionsSnapshot<MyConnectionStringsClass> instead.
Third, you really shouldn't be handling it this way, anyways. If you repository has a dependency on IDbConnection, then you should be injecting that into your repository. In Startup.cs:
services.AddScoped(p => new NpgsqlConnection(Configuration.GetConnectionString("Foo"));
Then, accept NpgsqlConnection in your repo constructor and set it to a private field.
Fourth, if you insist on continuing the way you currently are, you should absolutely not have a custom getter on your Connection property that news up NpgsqlConnection. That means you'll get a new instance every single time you access this property. Instead, you should define it as simple { get; private set; }, and set it in your repo's constructor.
Fifth, you should not be using using with a property defined in either way, as it will be disposed after the first time you do it, making all subsequent queries fail with an ObjectDisposedException. If you're going to new it up in your class, then your class needs to implement IDisposable and you should dispose of your connection in the Dispose method. FWIW, if you inject all dependencies (including your connection) into your class, you don't need to implement IDisposable as there's nothing the class will own that it needs to dispose of - another great reason to use dependency injection all the way down.
Finally, to answer you main question, you should use TestServer. When creating a TestServer you pass it your Startup class as a type param, so you end up with a true replica of your actual app, with all the appropriate services and such. Then, you can issue HTTP requests, like you would with HttpClient to test your controller actions and such. However, again, this is for integration testing only, which is the only time you should actually have a PostreSQL database in-play anyways.

How long will a DB context that's set in a MVC application be available for?

I have the following in my MVC Application:
namespace WebUx.Areas.User.Controllers
{
[Authorize]
[InitializeSimpleMembership]
public class AccountController : Controller
{
Plus:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
System.Diagnostics.Debug.Write("Set Initializer\n");
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
}
I understand that when there's a call to the account controller then this will set the DB context but once this is set will it stay set for my application. What about later on for other users who connect. Will the DB context always be available?
The reason I am asking this is that I have other information that I want to store in a table and access with Web API. Should I code in something similar for these controllers so that each time I check that there's a DB context available or could I just use this?
The connection is tightly coupled to the DbContext. As a result, the connection will only be open when your class which inherits DbContext, UsersContext in your case, retains its scope.
In your example, UsersContext is scoped to the using block.
using (var context = new UsersContext())
{
//some actions
}
Therefore, once "some actions" are finished, the connection will close and any attempt to access lazy loading will throw an exception stating the connection is no longer available. Every time you need to access your database, you should start a new connection in my opinion. What you want to make sure is that you only make one actual trip to the database. Make sure that your query is optimized so that you do not make multiple trips to the database instead of just doing it all at once as that will affect your performance.
Edit
As a side note, the using block breaks down into this:
try{
var context = new UsersContext();
//some actions
}finally{
context.Dispose();
}

Variety of NHibernate errors relating to transactions during spidering

We have an ASP.Net 4 / MVC 3 hybrid web application which uses NInject 3 and (Fluent) NHibernate 3.2. DB is SQL Server 2008 R2. Server is 6-core 28 GB Windows 2008 64-bit server.
Our customer has recently started testing the site using a spidering tool. As soon as the site experiences the load produced by the spider, our log starts to fill up with exceptions.
We see a variety of errors from NHibernate, including some of the following:
NHibernate.TransactionException: Commit failed with SQL exception ---> System.Data.SqlClient.SqlException: The transaction operation cannot be performed because there are pending requests working on this transaction.
System.Data.SqlClient.SqlException (0x80131904): The server failed to resume the transaction. Desc:410000050f. The transaction active in this session has been committed or aborted by another session.
System.NullReferenceException: Object reference not set to an instance of an object. at System.Data.SqlClient.SqlInternalTransaction.GetServerTransactionLevel()....
NHibernate.Exceptions.GenericADOException: could not execute native bulk manipulation query:exec [Stats.InsertListingStatsList] #ListingStats =:ListingStats[SQL: exec [Stats.InsertListingStatsList] #ListingStats =#p0] ---> System.Data.SqlClient.SqlException: New request is not allowed to start because it should come with valid transaction descriptor.
to give just four examples. All have a similar flavour - they all seem to relate to the management of transactions by ADO.Net as the substrate of NHibernate.
Now, some details of our NH implementation:
SessionFactory is static;
SessionFactory uses AdoNetTransactionFactory;
ISession is in request scope, and stored in the HttpContext.Items collection;
Repositories are also in request scope;
We are now using config.CurrentSessionContext();
Each call to our generic repository uses a transaction
Here are two methods from our repository.
public T GetById<T>(int id)
{
using (var t = Session.BeginTransaction())
{
var entity = Session.Get<T>(id);
t.Commit();
return entity;
}
}
public void Add<T>(T entity)
{
using (var t = Session.BeginTransaction())
{
Session.Save(entity);
t.Commit();
}
}
My question is simple: what is going wrong? What is causing these apparent conflicts between transactions, or between the various data-related operations that our domain instigates as it de/hydrates our domain?
UPDATE: here is our full configuration:
public FluentConfiguration BuildConfiguration(string connectionString)
{
var sqlConfig = MsSqlConfiguration.MsSql2008.ConnectionString(connectionString).AdoNetBatchSize(30);
var config = Fluently.Configure().Database(sqlConfig);
var entityMapping = AutoMap.AssemblyOf<User>(new AutomappingConfiguration())
.UseOverridesFromAssemblyOf<UserMappingOverride>()
.AddMappingsFromAssemblyOf<TableNamingConvention>()
.Conventions.AddFromAssemblyOf<TableNamingConvention>();
var cqrsMapping = AutoMap.AssemblyOf<AdvertView>(new QueryAutomappingConfiguration())
.UseOverridesFromAssemblyOf<AdvertViewMappingOverride>();
config.Mappings(c => c.AutoMappings.Add(entityMapping));
config.Mappings(c => c.AutoMappings.Add(cqrsMapping));
config.Mappings(c => c.HbmMappings.AddFromAssemblyOf<AdvertView>());
config.ExposeConfiguration(c => c.SetProperty(Environment.TransactionStrategy, typeof(AdoNetTransactionFactory).FullName));
config.CurrentSessionContext<WebSessionContext>();
return config;
}
More code for you guys and gals. Here is the relevant section of our IoC Container configuration.
var domainEntityBootstrapper = new DomainEntitySessionBootStrapper("Domain", "NHibernate.ISession.Domain", _enableLucine, HttpContextItemsProvider);
Bind<ISessionFactory>().ToMethod(domainEntityBootstrapper.CreateSessionFactory).InSingletonScope().Named(domainEntityBootstrapper.Name);
Bind<ISession>().ToMethod(domainEntityBootstrapper.GetSession).InRequestScope();
var queryBootstrapper = new QueryEntitySessionBootStrapper("Query", "NHibernate.ISession.Query", HttpContextItemsProvider);
Bind<ISessionFactory>().ToMethod(queryBootstrapper.CreateSessionFactory).InSingletonScope().Named(queryBootstrapper.Name);
Bind<ISession>().ToMethod(queryBootstrapper.GetSession).WhenInjectedInto(typeof (QueryExecutor)).InRequestScope();
and here is the code from the GetSession() method of the base class for these SessionBootstrappers (please note that the CreateSessionFactory method calls the BuildConfiguration method above and then calls BuildSessionFactory()).
public virtual ISession GetSession(IContext context)
{
var items = GetHttpContextItems();
var session = default(ISession);
var sessionExists = items.Contains(SessionKey);
if (!sessionExists)
{
session = context.Kernel.Get<ISessionFactory>(Name).OpenSession();
items.Add(SessionKey, session);
}
else
{
session = (ISession)items[SessionKey];
}
return session;
}
// a Func which serves access to the HttpContext.Current.Items collection
private Func<IDictionary> GetHttpContextItems { get; set; }
Please note that we use two sessions, one for ordinary domain de/hydration and one for CQRS, hence the pair of bindings in the Container.
The error messages indicate that you are not managing transactions correctly. I think the root cause is that you are handling transactions in the repository methods which in my opinion is a very poor design. Your repositories should have an ISession injected into their constructors, and your controllers should have any repositories they are dependent upon injected into their constructors. It's easy to wire this all up with Ninject. With this approach you can use transaction-per-request or (much better imo) manage the transaction in the action methods.
Here's how I'm setting up NHibernate with Ninject in NinjectWebCommon. The root cause of your problem may be that you are binding the ISession in request scope and storing it in HttpContext, which is unnecessary. I am also confused why you have two sets of bindings for Domain and Query.
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ISessionFactory>().ToProvider(new SessionFactoryProvider()).InSingletonScope();
kernel.Bind<ISession>().ToProvider(new SessionProvider()).InRequestScope();
}
private class SessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
// create and configure the session factory
// I have a utility class to do this so the code isn't shown
return nhibernateHelper.BuildSessionFactory();
}
}
private class SessionProvider : Provider<ISession>
{
protected override ISession CreateInstance(IContext context)
{
var sessionFactory = context.Kernel.Get<ISessionFactory>();
var session = sessionFactory.OpenSession();
session.FlushMode = FlushMode.Commit;
return session;
}
}
A sample controller action using a transaction. Managing transactions outside of the repositories is important for several reasons:
Allows multiple repositories to participate in a transaction
Allows the controller to set the transaction boundaries (unit of work)
Allows lazy loads to occur in the transaction
Transactions are needed for read operations if second level caching is used. Even if it caching isn't used I think it's a best practice
public ActionResult EditDocuments(int id, string name)
{
using (var txn = _session.BeginTransaction())
{
var summary = _characterizationRepository
.GetCharacterization(id)
.AsCharacterizationSummaryView()
.ToFutureValue();
var documents = _characterizationRepository
.GetCharacterization(id)
.SelectMany(c => c.Documents)
.OrderBy(d => d.FileName)
.AsDocumentSelectView(true)
.ToFuture();
if (summary.Value == null)
{
throw new NotFoundException(_characterizationRepository.ManualId, "Characterization", id);
}
CheckSlug(name, summary.Value.Title);
var model = new DocumentSectionEditView()
{
CharacterizationSummary = summary.Value,
Documents = documents.ToArray()
};
txn.Commit();
return View(model);
}
}
It seems you are using the wrong context manager, check if you are using the WebSessionContext. This context manager will bind your session to the httpcontext of the current call instead of the thread. What happens now under load (the spider), when you are using the ThreadStaticSessionContext, session will 'jump' to an other 'call'.

Resources