In my case, I have several EF contexts that share the same database (ie, all the tables are on the same db) and I'd like to share the same SqlConnection amongst all contexts so that I can apply several changes inside the same transaction.
In order to achieve this, I've started by registering the SqlConnection with the following code:
services.AddScoped(sp => new SqlConnection(Configuration.GetConnectionString("DefaultConnection")));
After doing this, I've change one of the EF contexts so that it reuses the same connection:
services.AddDbContext<ApplicationDbContext>((provider, options) =>
options.UseSqlServer(provider.GetRequiredService()));
I need to do something similar when registering the configuration and operational store. Unlike the EF extension method, there isn't an overload which lets me pass an Action that references the options builder and the service provider:
services.AddIdentityServer()
.AddConfigurationStore(options =>
{
// HERE: no serviceprovider, how can I get the SqlConnection object???
options.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
sql => sql.MigrationsAssembly(migrationsAssembly));
})
builder.UseSqlServer does have an overload which lets me pass the connection, but how can I resolve the SqlConnection at runtime so that the same connection is shared across all contexts?
Thanks,
Luis
Answered by the man himself :)
Instead of using the CondigureDbContext property, you should use the ResolveDbContextOptions:
.AddConfigurationStore(options => {
options.ResolveDbContextOptions = (provider, builder) =>
builder.UseSqlServer(provider.GetRequiredService<Foo>().FooValue);
})
Related
I do have an application using EF core connected to azure SQL.
We were facing resilience failure , to which adding EnableRetryOnFailure() was the solution which I have configured.
services.AddEntityFrameworkSqlServer()
.AddDbContext<jmasdbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DataContext"), sqlServerOptionsAction: sqlActions =>
{
sqlActions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null);
}), ServiceLifetime.Transient);
Now, this one would fail when we have BeginTransaction throwing error as below
"The configured execution strategy
'SqlServerRetryingExecutionStrategy' does not support user-initiated
transactions. Use the execution strategy returned by
'DbContext.Database.CreateExecutionStrategy()' to execute all the
operations in the transaction as a retriable unit."
I looked into MS docs and they suggest a way to define Execution strategy manually using ExecuteAsync "https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-resilient-entity-framework-core-sql-connections"
This has become pain as we do have more than 25+ places where we have these transactions.
I tried to have custom ExecutionStrategy at DbContext level but that did not help
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured && !string.IsNullOrEmpty(ConnectionString))
{
optionsBuilder.UseSqlServer(ConnectionString, options =>
{
options.ExecutionStrategy((dependencies) =>
{
return new SqlServerRetryingExecutionStrategy(dependencies, maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(5), errorNumbersToAdd: new List<int> { 4060 });
});
});
}
}
Is there any way to have this defined at global level? We do not want different strategy for each operation, whenever there is failure , we want that to be rollback completely and start from beginning.
We are using asp.net core 3.x with EF Core 3.x
We do have authorization on couple of entities(so that it only allow few of the records from table returned as response) which is achieved by accessing SQL view (internally joins two table) and we query against that view which will give you only those records which logged in user is authorized to.
In order to achieve this we need to insert logged in user id and ##spid (SQL Server) to the table (Session) (being used in above view) just before executing select queries (on Application table) and we need to delete that record immediately after query is executed. In order to achieve this we are using DbInterceptor.
Session table:
userId
sessionId
1
32
2
26
Application table:
id
userId
text
1
1
I need help to ...
2
2
I don't speak english...
Db interceptor implementation:
public class DbInterceptor : DbCommandInterceptor
{
private readonly IExecutionContext _executionContext;
public DbInterceptor(IExecutionContext executionContext)
{
_executionContext = executionContext;
}
public override async Task<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command,
CommandEventData eventData, InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = new CancellationToken())
{
var sqlParameter = new SqlParameter("UserId",
_executionContext.CurrentPrincipal.FindFirst(Claims.TSSUserId).Value);
await eventData.Context.Database.ExecuteSqlRawAsync("EXEC InsertUserSP #UserId", sqlParameter);
return await base.ReaderExecutingAsync(command, eventData, result);
}
public override async Task<DbDataReader> ReaderExecutedAsync(DbCommand command,
CommandExecutedEventData eventData, DbDataReader result,
CancellationToken cancellationToken = new CancellationToken())
{
var sqlParameter = new SqlParameter("UserId",
_executionContext.CurrentPrincipal.FindFirst(Claims.TSSUserId).Value);
await eventData.Context.Database.ExecuteSqlRawAsync("EXEC DeleteUserSP #UserId", sqlParameter);
return await base.ReaderExecutedAsync(command, eventData, result);
}
}
Now with this we got an exception
System.InvalidOperationException: 'There is already an open DataReader associated with this Command which must be closed first.' on line await eventData.Context.Database.ExecuteSqlRawAsync("EXEC DeleteUserSP #UserId", sqlParameter); in `ReaderExecutedAsync` method of interceptor.
I googled this exception and found that this error can be overcome by providing MultipleActiveResultSets to true in connection string.
Is there any side effect of using MultipleActiveResultSets?
While goggling around that topic, I come across several articles stating that It may share connection instance among different request, when MultipleActiveResultSets is set to true. If same connection is shared among the live request threads, then it can be problematic since authorization is working on the fact that it will have unique ##spid for all running live thread.
How DbContext will be provided with connection instance from Connection pool?
At ReaderExecutedAsync the data reader is still open and fetching rows. So it's too early to unset the user. Try hooking DataReaderDisposing instead.
If that doesn't work, force the connection open and call the procedure outside an interceptor. eg
var con = db.Database.GetDbConnection();
await con.OpenAsync();
await con.Database.ExecuteSqlRawAsync("EXEC InsertUserSP #UserId", sqlParameter);
This will ensure that the connection is not returned to the connection pool until the DbContext is Disposed.
I have been slowly trying to convert my code from using action delegates to the new Tasks in my WPF application. I like the fact that an await operation can run in the same method, greatly reducing the number of methods I need, enhancing readability, and reducing maintenance. That being said, I am having a problem with my code when calling EF6 async methods. They all seem to run synchronously and are blocking my UI thread. I use the following technologies/frameworks in my code:
.NET Framework 4.5
WPF
MVVM Light 5.2
Generic Unit Of Work/Repository Framework v3.3.5
https://genericunitofworkandrepositories.codeplex.com/
Entity Framework 6.1.1
SQL Server 2008 R2 Express
As an example, I have a LogInViewModel, with a command that executes after a button is clicked on my WPF application. Here is the command as initialized in the constructor:
LogInCommand = new RelayCommand(() => ExecuteLogInCommand());
Here is the command body:
private async void ExecuteLogInCommand()
{
// Some code to validate user input
var user = await _userService.LogIn(username, password);
// Some code to confirm log in
}
The user service uses a generic repository object that is created using MVVM Light's SimpleIoC container. The LogIn method looks like this:
public async Task<User> LogIn(string username, string password)
{
User user = await _repository.FindUser(username);
if (user != null && user.IsActive)
{
// Some code to verify passwords
return user;
}
return null;
}
And my repository code to log in:
public static async Task<User> FindUser(this IRepositoryAsync<User> repository, string username)
{
return await repository.Queryable().Where(u => u.Username == username).SingleOrDefaultAsync();
}
The SingleOrDefaultAsync() call is Entity Framework's async call. This code is blocking my UI thread. I have read multiple articles from Stephen Cleary and others about async await and proper use. I have tried using ConfigureAwait(false) all the way down, with no luck. I have made my RelayCommand call use the async await keywords with no luck. I have analyzed the code and the line that takes the longest to return is the SingleOrDefaultAsync() line. Everything else happens almost instantaneously. I have the same problem when making other async calls to the DB in my code. The only thing that fixes it right away is the following:
User user = await Task.Run(() =>
{
return _userService.LogIn(Username, p.Password);
});
But I understand this should not be necessary since the call I am making to the database is IO bound and not CPU bound. So, what is wrong with my application and why is it blocking my UI thread?
Your RelayCommand is not async.
LogInCommand = new RelayCommand(() => ExecuteLogInCommand());
Because there is no async/await your ExecuteLogInCommand will be called synchronously.
You got to change it to
LogInCommand = new RelayCommand(async () => await ExecuteLogInCommand());
so that the RelayCommand is called async too.
Your LogIn and FindUser (which, according to the guidelines, should be called LogInAsync and FindUserAsync) which are not supposed to work with the UI should use ConfigureAwait(false) on all awaits.
However all calls are synchronous until something really asynchronous is called. I suppose that would be SingleOrDefaultAsync.
If wrapping it in Task.Run makes such a difference, then, for some reason, SingleOrDefaultAsync must be running synchronously.
I'm currently using StructureMap to inject an NHibernateRegistry instance into my DAL, which configures NHibernate for a single connection string and bootstraps a Singleton FluentConfiguration for my single-user app.
How should I modify my Fluent NHibernate configuration to use a different database based on a {tenant} routing parameter in my routing URL?
Routing example:
{tenant}/{controller}/{action}/{id}
...where requests for branch1/Home/Index and branch2/Home/Index use the same application code, but different databases to retrieve the data displayed.
I solved this problem in the past for StructureMap and LINQ by injecting a per-request TenantContext object, which retrieved the routing parameter from the HttpContext it accepted as a constructor parameter and specified a different LINQ data context.
However, I suspect NHibernate has a better of handling this than I could cook up.
Partial NHibernateRegistry class
public class NHibernateRegistry : Registry
{
// ... private vars here
public NHibernateRegistry()
{
var cfg = Fluently.Configure()
.Database(MsSqlConfiguration
.MsSql2008.ConnectionString(c =>
c.FromConnectionStringWithKey("TenantConnectionStringKey")))
// where to inject this key?
.ExposeConfiguration(BuildSchema)
.Mappings(x =>
x.FluentMappings.AddFromAssembly(typeof(UserMap).Assembly)
For<FluentConfiguration>().Singleton().Use(cfg);
var sessionFactory = cfg.BuildSessionFactory();
For<ISessionFactory>().Singleton()
.Use(sessionFactory);
For<ISession>().HybridHttpOrThreadLocalScoped()
.Use(x => x.GetInstance<ISessionFactory>().OpenSession());
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped()
.Use<UnitOfWork>();
For<IDatabaseBuilder>().Use<DatabaseBuilder>();
}
}
StructureMap configuration:
public static class Bootstrapper
{
public static void ConfigureStructureMap()
{
ObjectFactory.Initialize(Init);
}
private static void Init(IInitializationExpression x)
{
x.AddRegistry(new NHibernateRegistry()); // from Data project
}
}
I'm new to NHibernate, so I am unsure of scoping my sessions and configurations. Does NHibernate have a built-in way to handle this?
This worked for me in an a module
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(x => x.FromConnectionStringWithKey("IMB"))
.Cache(c => c.UseQueryCache().QueryCacheFactory<StandardQueryCacheFactory>()
.RegionPrefix("IMB")
.ProviderClass<HashtableCacheProvider>()
.UseMinimalPuts()).UseReflectionOptimizer())
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("IMB.Data")))
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.Load("IMB.Security")))
.ExposeConfiguration(
c => c.SetProperty("current_session_context_class", "web"))
.ExposeConfiguration(cfg => _configuration = cfg)
.BuildSessionFactory();
The problem is that you really want your ISessionFactory object to be a singleton. This means its best not to specify the connection string when creating the ISessionFactory. Have your tried creating the ISessionFactory without specifying a connection string and then passing a manually created connection to ISessionFactory.OpenSession?
For example:
public ISession CreateSession()
{
string tennantId = GetTennantId();
string connStr = ConnectionStringFromTennant(tennantId);
SqlConnection conn = new SqlConnection(connStr);
conn.Open();
session = sessionFactory.OpenSession(conn);
}
And then tell StructureMap to call this method.
The downside is that you can't now from build the database schema when creating the ISessionFactory, but maybe creating database schemas in web applications isn't that great an idea anyway?
I want to generate a database script without having an actual database connection string declared.
To do this for now i use NHibernate ExportSchema bases on a NHibernate configuration generated with Fluent NHibernate this way (during my ISessionFactory creation method):
FluentConfiguration configuration = Fluently.Configure();
//Mapping conf ...
configuration.Database(fluentDatabaseProvider);
this.nhibernateConfiguration = configuration.BuildConfiguration();
returnSF = configuration.BuildSessionFactory();
//Later
new SchemaExport(this.nhibernateConfiguration)
.SetOutputFile(filePath)
.Execute(false, false, false);
fluentDatabaseProvider is a FluentNHibernate IPersistenceConfigurer which is needed to get proper sql dialect for database creation.
When factory is created with an existing database, everything works fine.
But what i want to do is to create an NHibernate Configuration object on a selected database engine without a real database behind the scene... And i don't manage to do this.
If anybody has some idea.
This is what I used. My mistake was calling BuildSessionFactory which tries to connect to the database:
var config = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008)
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<SessionManager>());
new SchemaExport(config.BuildConfiguration())
.SetOutputFile(filedestination)
.Create(false, false);
Try using:
.Create(false, false);
inplace of
.Execute(false, false, false);
Do not add the connectionstring property to the IPersistenceConfigurer (fluentDatabaseProvider).