NHibernate - How do I change schemas during run time? - nhibernate

I'm using NHibernate to connect to an ERP database on our DB2 server. We have a test schema and a production schema. Both schemas have the same table structure underneath. For testing, I would like to use the same mapping classes but point NHibernate to the test environment when needed and then back when in production. Please keep in mind that we have many production schemas and each production schema has an equivalent test schema.
I know that my XML mapping file has a schema property inside it, but since it's in XML, it's not like I can change it via a compiler directive or change the schema property based on a config file.
Any ideas?
Thank You.

No need to specify schema in the mappings: there's a SessionFactory-level setting called default_schema. However, you can't change it at runtime, as NHibernate pregenerates and/or caches SQL queries, including the schema part.

To get what I wanted, I had to use NHibernate.Mapping.Attributes.
[NHibernate.Mapping.Attributes.Class(0, Table = “MyTable”, Schema = MySchemaConfiguration.MySchema)]
In this way, I can create a class like MySchemaConfiguration and have a property inside of it like MySchema. I can either set the property's value via a compiler directive or get it through a configuration file. This way I only have to change the schema in one place and it will be reflected throughout all of the other mappings.

I have found following link that actually fixes the problem.
How to set database schema for namespace in nhibernate
The sample code could be
cfg.ClassMappings.Where(cm => cm.Table.Schema == "SchemaName")
.ForEach(cm => cm.Table.Schema = "AnotherSchemaName");
This should happen before you initialize your own data service class.
#Brian, I tried NHibernate.Mapping.Attributes, the attribute value you put inside should be a constant. So it could not be updated during run time. How could you have set the property's value using a parameter value in configuration file?

The code to fix HBM XML resources.
// This is how you get all the hbm resource names.
private static IList<string> GetAllHbmXmlResourceNames(Assembly assembly)
{
var result = new List<string>();
foreach (var resource in assembly.GetManifestResourceNames())
{
if (resource.EndsWith(".hbm.xml"))
{
result.Add(resource);
}
}
return result;
}
// This is how you get the stream for each resource.
Assembly.Load(assembly).GetManifestResourceStream(name)
// What you need to do next is to fix schema name in this stream
// Replacing schema name.
private Stream FixSchemaNameInStream(Stream stream)
{
StreamReader strStream = new StreamReader(stream);
string strCfg = strStream.ReadToEnd();
strCfg = strCfg.Replace(string.Format("schema=\"{0}\"" , originalSchemaName), string.Format("schema=\"{0}\"" , newSchemaName));
return new MemoryStream(Encoding.ASCII.GetBytes(strCfg));
}

Take a look at SchemaUpdate.
http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/04/28/create-and-update-database-schema.aspx

Related

RepoDb cannot find mapping configuration

I'm trying to use RepoDb to query the contents of a table (in an existing Sql Server database), but all my attempts result in an InvalidOperationException (There are no 'contructor parameter' and/or 'property member' bindings found between the resultset of the data reader and the type 'MyType').
The query I'm using looks like the following:
public Task<ICollection<MyType>> GetAllAsync()
{
var result = new List<MyType>();
using (var db = new SqlConnection(connectionString).EnsureOpen())
{
result = (await db.ExecuteQueryAsync<MyType>("select * from mytype")).ToList();
}
return result;
}
I'm trying to run this via a unit test, similar to the following:
[Test]
public async Task MyTypeFetcher_returns_all()
{
SqlServerBootstrap.Initialize();
var sut = new MyTypeFetcher("connection string");
var actual = await sut.GetAllAsync();
Assert.IsNotNull(actual);
}
The Entity I'm trying to map to matches the database table (i.e. class name and table name are the same, property names and table column names also match).
I've also tried:
putting annotations on the class I am trying to map to (both at the class level and the property level)
using the ClassMapper to map the class to the db table
using the FluentMapper to map the entire class (i.e. entity-table, all columns, identity, primary)
putting all mappings into a static class which holds all mapping and configuration and calling that in the test
providing mapping information directly in the test via both ClassMapper and FluentMapper
From the error message it seems like RepoDb cannot find the mappings I'm providing. Unfortunately I have no idea how to go about fixing this. I've been through the documentation and the sample tutorials, but I haven't been able to find anything of use. Most of them don't seem to need any mapping configuration (similar to what you would expect when using Dapper). What am I missing, and how can I fix this?

Is it possible to make `#SQLDelete` take the `hibernate.default_schema` parameter into account?

In a webapp, I use Hibernate's #SQLDelete annotation in order to "soft-delete" entities (i.e. set a status column to a value that denotes their "deleted" status instead of actually deleting them from the table).
The entity code looks like this :
#Entity
#SQLDelete(sql="update pizza set status = 2 where id = ?")
public class Pizza { ... }
Now, my problem is that the web application doesn't use the owner of the schema to which the tables belong to connect to the DB. E.g. the schema (in Oracle) is called pizza, and the db user the webapp uses to connect is pizza_webapp. This is for security reasons. The pizza_webapp user only has select/update/delete rights, it can't modify the structure of the DB itself. I don't have any choice here, it is a policy that I can't change.
I specify the name of the schema where the tables actually are with the hibernate-default_schema parameter in hibernate config :
<property name="hibernate.default_schema">pizza</property>
This works fine for everything that goes through mapped entities, Hibernate knows how to add the schema name in front of the table name in the SQL it generates. But not for raw SQL, and the #SQLDelete contains raw SQL. This is executed 'as is' and results in a "table or view not found error".
So far we worked around the issue by adding synonyms to the pizza_webapp schema, pointing to the pizza schema. It works, but it is not fun to maintain across multiple DBs when entities are added.
So, is it possible to make #SQLDelete take the hibernate.default_schema parameter into account ?
(NB: Obviously I don't want to hard-code the schema name in the SQL either...)
Yes, it is possible:
#SQLDelete(sql="update {h-schema}pizza set status = 2 where id = ?")
I could not find any Hibernate solution to this problem. However I found a work-around based on an Oracle feature. I do this in to my session before using it :
//set the default schema at DB session level for raw SQL queries (see #SQLDelete)
HibernateUtil.currentSession().doWork(new Work() {
#Override
public void execute(Connection connection) throws SQLException {
connection.createStatement().execute("ALTER SESSION SET CURRENT_SCHEMA="+HibernateUtil.getDefaultSchema());
}
});
I works fine, but unfortunately only on Oracle (which is fine for us for now at least). Maybe there are different ways to achieve the same thing on other RDBMS as well ?
Edit: the the getDefaultSchema() method in my HibernateUtil class does this to get the default schema from Hibernate's config :
defaultSchema = config.getProperty("hibernate.default_schema");
where config is my org.hibernate.cfg.Configuration object.

How to set database schema for namespace in nhibernate

I have a database with multiple schemas: Security, Trade, etc.
Each shema has multiple tables. Ie. Security schema has: Users, Roles etc..
Now, can I setup nhibernate so that the schema i bound to namespace.
Ie. I have a security namespace in my project with the User and Role POCOs in it.
So I wont to set bind database schema to namespace.
I know i can add Schema in maping file for each class, but if I have ie. 1000 class i must specify schema for each clas.
Please help.
you can do what you want programatically right before you create your sessionfactory like this:
var cfg = new Configuration().Configure();
foreach (var pc in cfg.ClassMappings)
{//just an example
pc.Table.Schema = pc.MappedClass.Assembly.GetName().FullName.Substring(0, 3);
}
var sessionFactory = cfg.BuildSessionFactory();
note that normally you only build your session factory once so the performance impact (if any) happens only once.
NHibernate won't do it magically. If you want to avoid changing mapping files manually, use a code-based solution like ConfORM or FluentNHibernate instead.

NHibernate: How to get mapped values?

Suppose I have a class Customer that is mapped to the database and everything is a-ok.
Now suppose that I want to retrieve - in my application - the column name that NH knows Customer.FirstName maps to.
How would I do this?
You can access the database field name through NHibernate.Cfg.Configuration:
// cfg is NHibernate.Cfg.Configuration
// You will have to provide the complete namespace for Customer
var persistentClass = cfg.GetClassMapping(typeof(Customer));
var property = persistentClass.GetProperty("FirstName");
var columnIterator = property.ColumnIterator;
The ColumnIterator property returns IEnumerable<NHibernate.Mapping.ISelectable>. In almost all cases properties are mapped to a single column so the column name can be found using property.ColumnInterator.ElementAt(0).Text.
I'm not aware that that's doable.
I believe your best bet would be to use .xml files to do the mapping, package them together with the application and read the contents at runtime. I am not aware of an API which allows you to query hibernate annotations (pardon the Java lingo) at runtime, and that's what you would need.
Update:
Judging by Jamie's solution, NHibernate and Hibernate have different APIs, because the Hibernate org.hibernate.Hibernate class provides no way to access a "configuration" property.

NHibernate: Using slightly different hbm mapping files depending on context

One of my applications is a public website, the other is an intranet. The public website runs using a limited security user that must access a certain table through a view, whereas the intranet can access the table itself.
This seems like it would be quite simple to setup using Fluent NHibernate. In my ClassMap I could do a check like this:
public class MyEntityClassMap : ClassMap<MyEntity>
{
public MyEntityClassMap()
{
if (NHibernateConfig.Current.Context == "intranet")
Table("t_MyEntity");
else
Table("v_MyEntity_pub");
... etc
}
}
Is there a simple way of doing this for embedded hbm files? The only method I can think of would be to have two copies of the hbm file, which would be confusing and far from ideal.
Is there perhaps a better way of achieving the same result?
Actually what you ask it is possible. You can actually access the embedded XML files and alter their content before the SessionFactory is build (on Application Start).
Assuming your will choose to reference the "t_MyEntity" in your entities by default here is how you can dynamically change this reference when you want to reference the "v_MyEntity_pub" table instead (the code may not work as it is but you will get the idea):
NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
cfg.AddAssembly(ASSEMBLYNAME);
if (NHibernateConfig.Current.Context != "intranet") //this is how you have stated you distinguish the intranet application from the other one.
{
string[] resourcesNames = assembly.GetManifestResourceNames();
foreach (string resourceName in resourcesNames)
{
StreamReader sr = new StreamReader(assembly.GetManifestResourceStream(resourceName));
string resourceContent = sr.ReadToEnd();
resourceContent = resourceContent.Replace("t_MyEntity", "v_MyEntity_pub");
cfg.AddXmlString(resourceContent);
}
}
ISessionFactory sessionFactory = cfg.BuildSessionFactory();
The above code should be executed only once for the lifetime of your application and only for the intranet application.
Although this is perhaps not the most helpful answer to your problem, I don't believe that this is possible in a mapping file. I also don't think that two hbm files would work for the same name, as it would be unable to distinguish between the two, you would instead have to have two identical objects each with slightly different names and mapping files. Which as you said in your question, would be completely confusing and ideal would simply be a spot on the horizon that you were hoping to, someday, reach.
Why is it that can't access everything directly through the view? I'm assuming there is no writing involved in this process? Is there any way you can change this method of accessing data while still maintaining your security?