开发者

Let 2 different mapping files work with same domain

The application I'm working on needs to support 3 databases. Sybase, SQL and Oracle.

We have a problem now with Oracle. As for oracle we use sequences as id generators, for Sybase and Oracle we use our own implementation to generate an ID.

We have a problem with one table that consists of a double primary key. My mapping file consists of a composite-id. But in a composite-id you can't work with generated values. For Oracle and Sybase this is no problem as I manually create the new id. For Oracle however I do have a problem as Sequences are necessary to be used.

So what I'm thinking of doing is create a second mapping file specific for Oracle, that only uses 1 field as PK. My query won't be correct, but I don't see any other option to do so..

So I have 2 mapping files:

Sybase and SQL:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping default-cascade="none" xmlns="urn:nhibernate-mapping-2.2"
    namespace="VS3.Domain.Address" assembly="VS3.Domain">

<class name="Top_Address" table="TOP_ADDRESS">
<composite-id name="ID" class="IDComposite_TopAddress">
  <key-property name="TnrAddress" column="TNR_ADDRESS" type="Int32"/>
  <key-many-to-one name="TopIdentity" class="Top_Identity" column="TNR_ID"/>
</composite-id>

<property name="nmAddress" column="NM_ADDRESS" type="String" not-null="true"/>
<property name="dPosx" column="D_POSX" type="Decimal" not-null="false"/>
<property name="dPosy" column="D_POSY" type="Decimal" not-null="false"/>

<property name="nmStreetAdd" column="NM_STREET_ADD" type="String" not-null="false"/>
<property name="nmStreetAdd2" column="NM_STREET_ADD2" type="String" not-null="false"/>
<property name="nmStreetAdd3" column="NM_STREET_ADD3" type="String" not-null="false"/>

<!-- Added By Transics -->
<property name="HouseNumber" column="CNR_HOUSE_ADD" type="String" not-null="false" />
<property name="BusNumber" column="CNR_BUS_ADD" type="String" not-null="false" />
<property name="PostalCode" column="CNR_ZIP" type="String" not-null="false" />
<property name="City" column="NM_ZIP" type="String" not-null="false" />
<property name="Country" column="COD_COUNTRY" type="String" not-null="false" />
<property name="Email" column="CNR_EMAIL_ADD" type="String" not-null="false" />
<property name="Phone" column="CNR_PHONE_ADD" type="String" not-null="false" />
<property name="CellPhone" column="CNR_GSM_ADD" type="String" not-null="false" />
<property name="NmAddress2" column="NM_ADDRESS2" type="String" not-null="false" />

<property name="Active" column="BOL_ACTIVE_ADD" type="Int32" not-null="false" />
<property name="Language" column="COD_LANG" type="String" not-null="false" />
<property name="AmFrom" column="T_TRB_AM_FROM" type="DateTime" not-null="false" />
<property name="AmUntil" column="T_TRB_AM_UNTIL" type="DateTime" not-null="false" />
<property name="PmFrom" column="T_TRB_PM_FROM" type="DateTime" not-null="false" />
<property name="PmUntil" column="T_TRB_PM_UNTIL" type="DateTime" not-null="false" />
<property name="Remark" column="DES_TRB_REMARK" type="String" not-null="false" />
<property name="ExtraInformation" column="TXT_ADDRESS" type="String" not-null="false" />
<property name="AddressType" column ="COD_ADDRESSTYPE" type="Char" not-null="false" />
<property name="AddressOrPlace" column ="COD_TRB_ADDRESSTYPE" type="String" not-null="false" />

</class>
</hibernate-mapping>

Oracle开发者_高级运维:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping default-cascade="none" xmlns="urn:nhibernate-mapping-2.2"
    namespace="VS3.Domain.Address" assembly="VS3.Domain">

<class name="Top_Address_Oracle" table="TOP_ADDRESS">
<id name="TnrAddress" column="TNR_ADDRESS" type="Int32">
  <generator class="sequence" />
</id>

<property name="nmAddress" column="NM_ADDRESS" type="String" not-null="true"/>
<property name="dPosx" column="D_POSX" type="Decimal" not-null="false"/>
<property name="dPosy" column="D_POSY" type="Decimal" not-null="false"/>

<property name="nmStreetAdd" column="NM_STREET_ADD" type="String" not-null="false"/>
<property name="nmStreetAdd2" column="NM_STREET_ADD2" type="String" not-null="false"/>
<property name="nmStreetAdd3" column="NM_STREET_ADD3" type="String" not-null="false"/>
<property name="HouseNumber" column="CNR_HOUSE_ADD" type="String" not-null="false" />
<property name="BusNumber" column="CNR_BUS_ADD" type="String" not-null="false" />
<property name="PostalCode" column="CNR_ZIP" type="String" not-null="false" />
<property name="City" column="NM_ZIP" type="String" not-null="false" />
<property name="Country" column="COD_COUNTRY" type="String" not-null="false" />
<property name="Email" column="CNR_EMAIL_ADD" type="String" not-null="false" />
<property name="Phone" column="CNR_PHONE_ADD" type="String" not-null="false" />
<property name="CellPhone" column="CNR_GSM_ADD" type="String" not-null="false" />
<property name="NmAddress2" column="NM_ADDRESS2" type="String" not-null="false" />

<property name="Active" column="BOL_ACTIVE_ADD" type="Int32" not-null="false" />
<property name="Language" column="COD_LANG" type="String" not-null="false" />
<property name="AmFrom" column="T_TRB_AM_FROM" type="DateTime" not-null="false" />
<property name="AmUntil" column="T_TRB_AM_UNTIL" type="DateTime" not-null="false" />
<property name="PmFrom" column="T_TRB_PM_FROM" type="DateTime" not-null="false" />
<property name="PmUntil" column="T_TRB_PM_UNTIL" type="DateTime" not-null="false" />
<property name="Remark" column="DES_TRB_REMARK" type="String" not-null="false" />
<property name="ExtraInformation" column="TXT_ADDRESS" type="String" not-null="false" />
<property name="AddressType" column ="COD_ADDRESSTYPE" type="Char" not-null="false" />
<property name="AddressOrPlace" column ="COD_TRB_ADDRESSTYPE" type="String" not-null="false" />

<many-to-one name="Identity" class="Identity" column="TNR_ID" not-null="true"/>
</class>
</hibernate-mapping>

Is there any way I can make sure that these mapping files use the same Domain class? Or do I need to create a new domain too (as I for the moment did).

But that's not really an option. Would be to much work.

Does anyone have a solution for this?


You can have two mapping files for the same domain class - if you only need one during the execution of your code (which I assume, since it would be a nightmare to work with two different databases at the same time with the same tables and domain classes). Before the SessionFactory is created you can filter the hbm files and only take those that you want, depending on a parameter.

You would need a mapping file for Oracle and another one for the rest, e.g. MyEntity.oracle.hbm.xml and MyEntity.default.hbm.xml. For those classes, that are exactly the same in both cases, you just keep them as they are, e.g. MyCommonEntity.hbm.xml.

Here is a slightly modified example of code that I use in our project. (I posted another version of that in another question here on SO: Dynamically change the id generator to “assigned” in NHibernate class mapping)

private ISessionFactory BuildSessionFactory(bool useOracleMapping)
{
    Configuration config = new Configuration();

    config.SetProperty(NHibernate.Cfg.Environment.ConnectionProvider, "...");
    config.SetProperty(NHibernate.Cfg.Environment.Dialect, "...");
    config.SetProperty(NHibernate.Cfg.Environment.ConnectionDriver, "...");
    config.SetProperty(NHibernate.Cfg.Environment.ConnectionString, "...");
    config.SetProperty(NHibernate.Cfg.Environment.Isolation, "Serializable");
    config.SetProperty(NHibernate.Cfg.Environment.ProxyFactoryFactoryClass, "...");
    config.SetProperty(NHibernate.Cfg.Environment.ShowSql, "true");
    config.SetProperty(NHibernate.Cfg.Environment.Hbm2ddlKeyWords, "none");

    // filter hbm Files

    // Set reference to entity assembly
    System.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly(typeof(MyEntity));

    // get Resource-files
    string[] resources = assembly.GetManifestResourceNames();

    // scan through all the hbm files and filter them according to the parameter
    foreach (string hbmFile in resources)
    {
        // This filtering here could probably be done simpler, but this is easy to understand
        bool addFile = false;
        // ignore any file that does not end with .hbm.xml
        if (hbmFile.EndsWith(".hbm.xml"))
        {
            if (hbmFile.ToLower().EndsWith(".default.hbm.xml"))
            {
                if (!useOracleMapping)
                {
                    // we want that file for this SessionFactory
                    addFile = true;
                }
            }
            else if (hbmFile.ToLower().EndsWith(".oracle.hbm.xml"))
            {
                if (useOracleMapping)
                {
                    // we want that file for this SessionFactory
                    addFile = true;
                }
            }
            else
            {
                // neither default nor oracle -> we want that file no matter what
                addFile = true;
            }
            if (addFile)
            {
                using (System.IO.StreamReader sr = new System.IO.StreamReader(assembly.GetManifestResourceStream(hbmFile)))
                {
                    string resourceContent = sr.ReadToEnd();
                    config.AddXmlString(resourceContent);
                }
            }
        }
    }

    // create Sessionfactory with the files we filtered
    ISessionFactory sessionFactory = config.BuildSessionFactory();
    return sessionFactory;
}

Edit:

Assuming that you can always access the knowledge whether you are in Oracle or the other mode, I would wrap the GetTop_AddressById() method and do something like the following:

public Top_Address GetTop_AddressById(IDComposite_TopAddress id)
{
    if (!oracle)
    {
        return session.CreateCriteria(DB, typeof(Top_Address))
           .Add(Restrictions.Eq("ID.TnrAddress", addressID))
           .Add(Restrictions.Eq("ID.TopIdentity.tnrId", tnrID))
           .Add(Restrictions.Eq("AddressType", 'R')) .UniqueResult<Top_Address>();
    }
    else 
    {
        return session.CreateCriteria(DB, typeof(Top_Address))
           .Add(Restrictions.Eq("TnrAddress", addressID))
           .Add(Restrictions.Eq("AddressType", 'R')) .UniqueResult<Top_Address>();
    }
}

That may not be pretty, but having two classes for the same purpose is worse, I think. You will need a wrapper like that for all CRUD-operations that use the id. You will also need to have the TnrAddress property in your domain class, of course.


I suggest you don't fork the mapping, but instead: Via the mapping metadata deserialized, before creating the SessionFacorty, add the modification you need. I can't give you the code, but here you can grab the idea: http://fabiomaulo.blogspot.com/2010/01/map-nhibernate-using-your-api.html

Or

Create a custom id generator, and here inside handle the difference between tghe two key system: http://nhforge.org/wikis/howtonh/creating-a-custom-id-generator-for-nhibernate.aspx

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜