jemiller wrote:
I always thought it should be possible to use a single session factory as long as all the databases were on the same database server. So far as I know, this isn't possible though, at least not with Hibernate. I would assume that NHibernate is the same. I guess maybe transactions wouldn't work across databases?
The reason I was thinking about this is that I have several applications that all have a Person table. I was thinking that maybe I would put that table into a Common database and then have other databases for the individual applications that had their specific data. As far as I know you can't really do this with N/Hibernate unless as you pointed out, you use multiple sessions/factories.
BUT:
You CAN use a single session factory for two databases in the same server (and maybe in a separate server - see final note), and you CAN refactor these common classes in its own assembly.
SOLUTION:
You just specify the schema in each class mapping file including the Database name in it.
EXAMPLE:
Using your example, you can put Persons table in CommonDB (I am asumming a SQL Server 2000 or 2005 instance) and then create a dll project for Common things. Here goes Person code (in VB):
Code:
Public Class Person
Private _id As Guid
Private _name As String
Public Overridable ReadOnly Property Id() As Guid
Get
Return _id
End Get
End Property
Public Overridable Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
End Class
Here's Person mapping:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0"
schema="CommonDB.dbo"
assembly="Common"
namespace="Common"
default-access="field.camelcase-underscore" >
<class name="Person" table="Persons" >
<id name="Id" column="PersonId" >
<generator class="guid.comb" />
</id>
<property name="Name" />
</class>
</hibernate-mapping>
The KEY thing here is to specify schema="CommonDB.dbo".
Then suposing you have a ClientClasses table in a ClientDB database, you create a Client dll that uses the person class:
Code:
Imports Common
Public Class ClientClass
Private _id As Guid
Private _person As Person
Public Overridable ReadOnly Property Id() As Guid
Get
Return _id
End Get
End Property
Public Overridable Property Person() As Person
Get
Return _person
End Get
Set(ByVal value As Person)
_person = value
End Set
End Property
End Class
And you use the mapping:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0"
schema="ClientDB.dbo"
assembly="Client"
namespace="Client"
default-access="field.camelcase-underscore" >
<class name="ClientClass" table="ClientClasses" >
<id name="Id" column="ClientClassId" >
<generator class="guid.comb" />
</id>
<many-to-one name="Person" column="PersonId" class="Common.Person, Common" />
</class>
</hibernate-mapping>
Note the schema attribute! and check that full qualified name has been used in the many-to-one reference.
Next you define in your UI project that uses both Client.dll and Common.dll, an app.config or web.config including something like:
Code:
<configSections>
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler,NHibernate" />
</configSections>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.0">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">Server=SERVERNAME; Database=ClientDB; Integrated Security=SSPI</property>
<property name="connection.isolation">ReadCommitted</property>
<property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
<property name="show_sql">false</property>
<property name="use_reflection_optimizer">true</property>
<property name="bytecode.provider">lcg</property>
<mapping assembly="Common"/>
<mapping assembly="Client"/>
</session-factory>
</hibernate-configuration>
The important thing here are to include the assemblies in the right order.
Then your client code goes like this:
Code:
Dim cfg As New Cfg.Configuration()
cfg.Configure()
Dim mySessionFactory as ISessionFactory = cfg.BuildSessionFactory()
' It works without transaction
Dim session1 As ISession = mySessionFactory.OpenSession
Dim person1 As New Common.Person
person1.Name = "Hector"
session1.Save(person1)
Dim client1 As New Client.ClientClass
client1.Name = "Client 1"
client1.Person = person1
session1.Save(client1)
session1.Flush()
session1.Close()
' And using transactions (distributed?)
Dim session2 As ISession = mySessionFactory.OpenSession
Dim myTransaction As ITransaction = session2.BeginTransaction
Dim person2 As New Common.Person
person2.Name = "Esther"
session2.Save(person2)
Dim client2 As New ClientSystem.ClientClass
client2.Name = "Client class 2"
client2.Person = person2
session2.Save(client2)
myTransaction.Commit()
session2.Close()
This works because when making SQL statements NHibernate uses the schema in each table to create things like:
Code:
INSERT INTO CommonDB.dbo.Persons Values('(guid1)', 'Hector')
INSERT INTO ClientDB.dbo.ClientClasses Values('(guid2)', '(guid1)')
the SQL above puts each thing in its place.
Final note: I'm not sure but maybe this could be extended to databases in separate servers appending [SERVERNAME] to the schema like:
schema="[anotherDbServer].CommonDb.dbo"
Hector Cruz