Single website multiple connection strings using asp mvc 2 and nhibernate - nhibernate

In my website i use ASP MVC 2 + Fluent NHibernate as orm, StructureMap for IoC container.
There are several databases with identical metadata(and so entities and mappings are the same). On LogOn page user fiils in login, password, rememberme and chooses his server from dropdownlist (in fact he chooses database).
Web.config contains all connstrings and we can assume that they won't be changed in run-time.
I suppose that it is required to have one session factory per database.
Before using multiple databases, i loaded classes to my StructureMap ObjectFactory in Application_Start
ObjectFactory.Initialize(init => init.AddRegistry<ObjectRegistry>());
ObjectFactory.Configure(conf => conf.AddRegistry<NhibernateRegistry>());
NhibernateRegistry class:
public class NhibernateRegistry : Registry
{
public NhibernateRegistry()
{
var sessionFactory = NhibernateConfiguration.Configuration.BuildSessionFactory();
For<Configuration>().Singleton().Use(
NhibernateConfiguration.Configuration);
For<ISessionFactory>().Singleton().Use(sessionFactory);
For<ISession>().HybridHttpOrThreadLocalScoped().Use(
ctx => ctx.GetInstance<ISessionFactory>().GetCurrentSession());
}
}
In Application_BeginRequest i bind opened nhibernate session to asp session(nhibernate session per request) and in EndRequest i unbind them:
protected void Application_BeginRequest(
object sender, EventArgs e)
{
CurrentSessionContext.Bind(ObjectFactory.GetInstance<ISessionFactory>().OpenSession());
}
Q1: How can i realize what SessionFactory should i use according to authenticated user?
is it something like UserData filled with database name (i use simple FormsAuthentication)
For logging i use log4net, namely AdoNetAppender which contains connectionString(in xml, of course).
Q2: How can i manage multiple connection strings for this database appender, so logs would be written to current database? I have no idea how to do that except changing xml all the time and reseting xml configuration, but its really bad solution.

I suppose that it is required to have one session factory per database.
No; you can do just fine with one session factory for both databases.
You just supply an opened IDbConnection as a param to the OpenSession() method of ISessionFactory.
By doing so, you'll lose the possibility for a second level cache, but that might not be a problem.
If you want the second level cache, you need to implement you're own DriverConnectionProvider and supply it via fluent nh's Provider<TYourDriverConnectionProvider>() method.

Related

Create new DbContext dynamically when using ASP.Net Core dependency injection

I've configured my DbContext with services.AddDbContext() in the Startup class and constructor injection in my controllers works very well.
By default it's a scoped service, but I have one place in the app where I want to update a single property of an entity in a separate scope of work. So I need to create a new DbContext in the controller, but I'm not sure how. I want it to be created by the DI so I don't have to manually call the constructor and provide all the options needed. Is there a way to do that? Maybe there's a way to get the db context options from the DI? Then I could construct the DbContext easily.
The normal method of injecting a DbContext into your Controller works fine, as long as you are doing a small amount of work during an HTTP request. However, you might want to create a DbContext for a long-running a operation that queries/modifies a lot of records (causing SaveChangesAsync() to get bogged down because DbContext.ChangeTracker is tracking a lot of objects). In that case, you can create a scoped DbContext for each operation ("unit of work"). Here is an example ASP.NET Core Controller method:
/// <summary>
/// An endpoint that processes a batch of records.
/// </summary>
/// <param name="provider">The service provider to create scoped DbContexts.
/// This is injected by DI per the FromServices attribute.</param>
/// <param name="records">The batch of records.</param>
public async Task<IActionResult> PostRecords(
[FromServices] IServiceProvider provider,
Record[] records)
{
// The service scope factory is used to create a scope per iteration
var serviceScopeFactory =
provider.GetRequiredService<IServiceScopeFactory>();
foreach (var record in records)
{
// At the end of the using block, scope.Dispose() will be called,
// releasing the DbContext so it can be disposed/reset.
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetService<MainDbContext>();
// Query and modify database records as needed
await context.SaveChangesAsync();
}
}
return Ok();
}
Also, I would recommend switching from AddDbContext() to AddDbContextPool() in Startup.cs to avoid creating/destroying DbContext objects for each request. The DbContextPool will reset the DbContext objects to a clean state after they go out of scope. (In case you were interested, DbContextPool calls DbContext.ResetState() and DbContext.Resurrect(), but I wouldn't recommend calling those directly from your code, as they will probably change in future releases.)
https://github.com/aspnet/EntityFrameworkCore/blob/v2.2.1/src/EFCore/Internal/DbContextPool.cs#L157
Finally, be aware that there are a few pitfalls of creating multiple DbContexts:
Using a large number of DbContexts in parallel may cause the database server to run out of active connections, since many EF database providers open a database connection per DbContext. (Requesting and releasing pooled DbContext objects in a loop should be fine.)
There may be more efficient ways to do the same thing. On my project, I tested and found that running a single "upsert" on a single DbContext was significantly faster than running a SELECT and INSERT/UPDATE on a separate DbContext for each record. There are a number of implementations of upsert for EF Core. For example, here are two that I have used:
FlexLabs.Upsert: https://github.com/artiomchi/FlexLabs.Upsert
EF Extensions BulkMerge: https://entityframework-extensions.net/bulk-merge
One option is to inject IDbContextFactory into your comtroller to create contexts within using blocks.
https://msdn.microsoft.com/en-us/library/hh506876(v=vs.113).aspx

Risks of holding an Entity Framework dynamic proxy object in session?

So I have a fairly comprehensive activity-based access control system I built for a web app under MVC 4 using Entity Framework. Well, to be precise the access control doesn't care if it's using EF or not, but the app is.
Anyway, I'm loading the user's permissions on each request right now. I get a reference to my DbContext injected from the IoC container into my ApplicationController, and it overrides OnAuthorization to stuff the user's profile into the HttpContext.Current.Items. Seems to work fairly well, but I can't help but wonder if it's the best way.
My thought was that since the users' permissions don't change often, if ever, the better way to do it would be to load the profile of permissions into the Session instead, and then not have to change them at all until the user logs out and logs back in (pretty common in desktop OS's anyway). But I'm concerned that if I fetch using the DbContext, then the object I get back is a dynamic proxy which holds a reference to the DbContext and I certainly don't want to do that for the whole session.
Thoughts? Is this a good approach, and if so how do I ensure that my DbContext doesn't linger beyond when I really need it?
Invoke .AsNoTracking() on the Set<UserPermission> before you query out. Entities will still be proxied, but will be detached from the DbContext.
var userPermission = dbContext.Set<UserPermission>().AsNoTracking()
.SingleOrDefault(x => x.UserName == User.Identity.Name);
Thoughts? Is this a good approach?
Putting a dynamically proxied entity in session will break as soon as you load balance your code across more than 1 web server. Why? Because of the dynamic proxy class. Server A understands the type DynamicProxies.UserPermission_Guid, because it queried out the entity. However Server B through N do not, and therefore cannot deserialize it from the Session. The other servers will dynamically proxy the entity with a different GUID.
That said, you could DTO your data into a POCO object and put it in session instead. However then you do not need to worry about your entity being attached to the context when you first query it out. AsNoTracking will only make the query perform a bit faster.
// you can still call .AsNoTracking for performance reasons
var userPermissionEntity = dbContext.Set<UserPermission>().AsNoTracking()
.SingleOrDefault(x => x.UserName == User.Identity.Name);
// this can safely be put into session and restored by any server with a
// reference to the DLL where the DTO class is defined.
var userPermissionSession = new UserPermissionInSession
{
UserName = userPermissionEntity.UserName,
// etc.
};
Thoughts? Is this a good approach?
Another problem attached to this approach is when you use the common pattern that create one dbContext per http request. This pattern typically dispose dbContext when the request ends.
protected virtual void Application_EndRequest(object sender, EventArgs e)
But what happen when we try to get navigation property of a proxy entity which reference to a disposed DbContext?
We will get a ObjectDisposedException

Using SharpArch Nhibernate with different types of SessionStorage

I have a server application where I have 3 scenarios in which I seem to need different kind of nhibernate sessions:
Calls to the repository directly from the server itself (while bootstrapping)
Calls to the repository coming from a Ria Service (default ASP.NET Memberschip Service)
Calls to the repository coming from a WCF Service
Currently I have set up my nhibernate config with sharparch like this
/// <summary>
/// Due to issues on IIS7, the NHibernate initialization cannot reside in Init() but
/// must only be called once. Consequently, we invoke a thread-safe singleton class to
/// ensure it's only initialized once.
/// </summary>
protected void Application_BeginRequest(object sender, EventArgs e)
{
NHibernateInitializer.Instance().InitializeNHibernateOnce(
() => InitializeNHibernateSession());
BootStrapOnce();
}
private void InitializeNHibernateSession()
{
NHibernateSession.Init(
wcfSessionStorage,
new string[] { Server.MapPath("~/bin/bla.Interfaces.dll") },
Server.MapPath("~/Web.config"));
}
This works for the third scenario, but not for the first two.
It seems to need some wcf-session-specific context.
The SharpArch Init method seems to have protection from re-initializing it with another type of sessionstorage;
What is the best way to create a different session for three different kinds of contexts?
To me it looks like this post seems related to this one which has helped me looking in the right direction, but I have not found a solution so far.
I'm not sure you are going to be able to do what you are wanting with S#. The reason being is that you are really wanting to have 3 separate Nhibernate sessions, each with it's own storage mechanism. The current implementation only allows for one storage mechanism, regardless of the number of sessions.
I can easily get you #'s 1 and 3, but not two since I've never used RIA services. In the case of 1 and 3, you would need to take the WCF service out of the site and have it in it's own site. No way of really getting around that as their session lifecycles are different.
Your other option would be to come up with your own Session Management for NHibernate and not use the default S# one. You could look at the code for the S# version and create your own based on that.

Handling Start Up Issues With NHibernate In Global.asax

I have a WCF Service, hosted inside of IIS, using NHibernate for data access.
In my Global.asax I configure NHibernate on Application_Start. This means that I only perform the expensive task of setting up all of the mappings once for my WCF Service.
The only problem with this is that if the database was unavailable during start up, the mappings will never get set up (as they will have failed to be set up during Application_Start and the event won't be called again until the app pool is recycled).
How can I handle the NHibernate set up so it occurs only once, except where there is an error (such as the database not being available) in which case it will occur on each request until it works?
What you need is a Lazy Singleton to be your SessionFactory. You call a method to get the session factory and it checks if the session already exists. So the expensive task of creating the Session Factory is done the first time someone needs it.
You could do something like this:
public ISessionFactory GetSessionFactory()
{
// sessionFactory is STATIC
if (sessionFactory == null)
{
global::NHibernate.Cfg.Configuration cfg = new NHibernateConfigurationFactory(CurrentConfiguration).GetConfiguration(sessionFactoryName);
// Now that we have our Configuration object, create a new SessionFactory
sessionFactory = cfg.BuildSessionFactory();
if (sessionFactory == null)
{
throw new InvalidOperationException("cfg.BuildSessionFactory() returned null.");
}
}
return sessionFactory;
}
A complete solution is available here:
NHibernate - good complete working Helper class for managing SessionFactory/Session
I am throwing this answer into the mix in order to get comments on it - this answer was emailed to me, but I'd appreciate the view of the SO community before I decide on the final solution...
Rather than using the Application_Start event, use the Begin_Request event. Store the NHibernate session in a field and in the Begin_Request event, check if the field is null and if it is, create the NHibernate session (otherwise, continue to use the one already created).
So essentially, this would mean moving the create logic into a method I can call from Begin_Request in the event of "detecting that the session hasn't yet been created".

NHibernate many to many and ISession.Close()

I am using NHibernate for my project. i am quite a beginner at working on NHibernate.
I use many-to-many relation mapping "users" and "roles". Here is the code to get the user:
public User GetUser(int userId){
using(ISessuib session = new SessionManager().GetSession())
{
return session.Get<User>(userId);
}
}
public void LazyLoadUsingSessionTest(){
var user= GetUser(1);
Assert.NotNull(user.Roels);
}
it throws an exception:failed to lazily initialize a collection, no session or session was closed
if i do not use the "using" statement in the "GetUser" method,it works. But i must call the session.Close() to release the resource
when i use it in a web page,i only want to use the GetUser(), not the ISession object.so my question is : Does it mean that i must have a ISession object(to release resouse) in my web page? or any better solution?(because i do not want the ISession object appears in my aspx.cs files)
thanks!
You need to use the Session per Request pattern. See this link for an explanation of the best practices for using NHibernate with ASPX.
The simplest way is to use NHibernateUtil.Initialize (read here for details):
using(ISession session = new SessionManager().GetSession())
{
User user = session.Get<User>(userId);
NHibernateUtil.Initialize(user.Roles);
return user;
}
However, sooner or later you would need to somehow manage the sessions in your application. I recommend creating a data provider layer that would give you access to the database. The data provider layer will manage the creation and destruction of sessions. You could either have a session per request or per conversation (single ISession for the duration of an ASP.Net session).
The Summer Of NHibernate video series would be helpful. Session 5 and 13 are most relevant for you.