How to map an interface in nhibernate?

2019-01-26 13:51发布

问题:

I'm using two class NiceCustomer & RoughCustomer which implment the interface ICustomer.

The ICustomer has four properties. They are:

  1. Property Id() As Integer
  2. Property Name() As String
  3. Property IsNiceCustomer() As Boolean
  4. ReadOnly Property AddressFullText() As String

I don't know how to map the interface ICustomer, to the database.

I get an error like this in the inner exception.

An association refers to an unmapped class: ICustomer

I'm using Fluent and NHibernate.

回答1:

You can map directly to interfaces in NHibernate, by plugging in an EmptyInterceptor during the configuration stage. The job of this interceptor would be to provide implementations to the interfaces you are defining in your mapping files.

public class ProxyInterceptor : EmptyInterceptor
{
    public ProxyInterceptor(ITypeHandler typeHandler) {
        // TypeHandler is a custom class that defines all Interface/Poco relationships
        // Should be written to match your system
    }

    // Swaps Interfaces for Implementations
    public override object Instantiate(string clazz, EntityMode entityMode, object id)
    {
        var handler = TypeHandler.GetByInterface(clazz);
        if (handler == null || !handler.Interface.IsInterface) return base.Instantiate(clazz, entityMode, id);
        var poco = handler.Poco;
        if (poco == null) return base.Instantiate(clazz, entityMode, id);

        // Return Poco for Interface
        var instance = FormatterServices.GetUninitializedObject(poco);
        SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);

        return instance;
    }

}

After this, all relationships and mappings can be defined as interfaces.

public Parent : IParent {
    public int ID { get; set; }
    public string Name { get; set; }
    public IChild Child { get; set; }
}

public Child : IChild {
    public int ID { get; set; }
    public string Name { get; set; }
}

public class ParentMap : ClassMap<IParent>
{
    public ParentMap()
    {
        Id(x => x.ID).GeneratedBy.Identity().UnsavedValue(0);
        Map(x => x.Name)
    }
}   

...

This type of technique is great if you want to achieve true decoupling of your ORM, placing all configuration/mappings in a seperate project and only referencing interfaces. Your domain layer is then not being polluted with ORM, and you can then replace it at a later stage if you need to.



回答2:

It is not possible to map an interface in nhibernate. If your goal is to be able to query using a common type to retrieve both types of customers you can use a polymorphic query. Simply have both your classes implement the interface and map the classes normally. See this reference:

https://www.hibernate.org/hib_docs/nhibernate/html/queryhql.html (section 11.6)



回答3:

how are you querying? If you're using HQL you need to import the interface's namespace with an HBM file with this line:

<import class="name.space.ICustomer, Customers" />

If you're using Criteria you should just be able to query for ICustomer and it'll return both customer types.

If you're mapping a class that has a customer on it either through a HasMany, HasManyToMany or References then you need to use the generic form:

References<NiceCustomer>(f=>f.Customer)

If you want it to cope with either, you'll need to make them subclasses

Subclassmap<NiceCustomer>

In which case I think you'll need the base class Customer and use that for the generic type parameter in the outer class:

References<Customer>(f=>f.Customer)

Regardless, you shouldn't change your domain model to cope with this, it should still have an ICustomer on the outer class.

I'm not sure if the 1.0RTM has the Generic form working for References but a quick scan of the changes should show the change, which I think is a two line addition.