Programación a interfaces mientras se mapea con Fluent NHibernate


He sido azotado hasta la sumisión y he comenzado a aprender NHibernate Fluido (sin experiencia previa en NHibernate). En mi proyecto, estoy programando interfaces para reducir el acoplamiento, etc. Eso significa que prácticamente "todo" se refiere a la interfaz en lugar del tipo concreto (iMessage en lugar de Message). La idea detrás de esto es ayudar a que sea más comprobable al ser capaz de simular dependencias.

Sin embargo, a NHibernate (Fluido) no le encanta cuando intento mapear interfaces en lugar de clases concretas. El problema es simple-de acuerdo con Fluent Wiki, es inteligente definir el campo ID de mi clase como por ejemplo

int Id { get; private set; }

Para obtener una clave primaria típica generada automáticamente. Sin embargo, eso solo funciona con clases concretas - no puedo especificar un nivel de acceso en una interfaz, donde la misma línea tiene que ser

int Id { get; set; }

Y supongo que eso niega hacer que el setter sea privado en la clase concreta (la idea es que solo NHibernate debería establecer el ID como asignado por el DB).

Por ahora, supongo que solo haré público al armador y trataré de evitar la tentación de escribirle.. Pero, ¿alguien tiene una idea de cuál sería la forma "adecuada" y de mejores prácticas para crear un campo de clave primaria adecuado en el que solo NHibernate puede escribir mientras todavía solo programa en interfaces?

ACTUALIZADO

Por lo que entiendo después de las dos respuestas a continuación de mookid y James Gregory, bien podría estar en el camino equivocado - no debería haber un razón para tener una interfaz por entidad como la tengo ahora. Eso está muy bien. Supongo que mi pregunta entonces se convierte - ¿no hay razón para programar 100% contra una interfaz para cualquier entidad? Y si hay incluso una sola situación donde esto podría justificarse, ¿es posible hacer esto con NHibernate (Fluido)?

Pregunto porque no lo sé, para no ser crítico. Gracias por las respuestas. :)

Author: Rune Jacobsen, 2009-05-10

5 answers

ACTUALIZACIÓN: no se admite el uso de la subclase union a través de la interfaz fluent que proporciona fluent-nhibernate. Tendrá que utilizar un archivo de asignación de hbm normal y añadirlo.

Yo también estoy tratando de hacer esto con NHibernate fluido. No creo que deba ser un problema mapeando interfaces. Desea utilizar una estrategia de herencia, específicamente la estrategia table-per-concrete-class.

Esencialmente, se crea una definición de asignación para la clase base (en este caso la interfaz) y especificar cómo NHibernate debe tratar con los implementadores mediante el uso de la subclase union.

Así que, por ejemplo, esto debería permitirle hacer asociaciones polimórficas:

<class name="IAccountManager"
                abstract="true"
                table="IAccountManager">

        <id name="Id">
                <generator class="hilo"/>
        </id>

        <union-subclass
                table="DefaultAccountManager"
                name="DefaultAccountManager">
                <property name="FirstName"/>
        </union-subclass>

        <union-subclass
                table="AnotherAccountManagerImplementation"
                name="AnotherAccountManagerImplementation">
                <property name="FirstName"/>
        </union-subclass>
        ...
</class> 

Observe cómo el Id es el mismo para todos los implementadores concretos. NHibernate requirió esto. Además, la tabla IAccountManager en realidad no existe.

También puede intentar aprovechar el polimorfismo Implícito de NHibernate (documentado debajo de la estrategia table-per-concrete-class), pero tiene toneladas de limitaciones.

 8
Author: Dane O'Connor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-13 01:18:32

Me doy cuenta de que esto es una distracción, y no una respuesta a su pregunta (aunque creo que mookid lo tiene cubierto).

Realmente deberías evaluar si las interfaces en tus entidades de dominio están realmente proporcionando algo de valor; es raro encontrar una situación en la que realmente necesites hacer esto.

Por ejemplo: ¿Cómo es confiar en IMessage menos acoplado que confiar en Message, cuando ambos (casi) sin duda comparten firmas idénticas? No deberías tener que burlarte. una entidad, porque es raro que tenga suficiente comportamiento para requerir ser burlado.

 12
Author: James Gregory,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-10 17:28:25

Puede ajustar su interfaz para que contenga solo un getter:

public interface ISomeEntity
{
    int Id { get; }
}

Su clase concreta todavía puede implementar un setter también, y ya que está programando para sus interfaces nunca llamará al setter "por accidente".

Si desea no permitir la configuración del id incluso cuando mantiene una referencia a una instancia concreta, puede abstenerse de implementar un setter y luego dejar que NHibernate acceda al campo en lugar de a la propiedad-así es, NHibernate puede usar alguna ingeniosa truco de reflexión para establecer su campo id directamente en lugar de invocar la propiedad. Entonces puede mapear el id de esta manera:

Id(e => e.Id).Access.AsCamelCaseField();

En cuyo caso su propiedad Id debe estar respaldada por un campo id correspondiente. Hay más convenciones de nomenclatura, por ejemplo, si prefiere guiones bajos como prefijo de campo privado.

 10
Author: mookid8000,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-10 17:14:16

Estoy teniendo exactamente el mismo problema. Desafortunadamente tengo una razón válida para usar interfaces de entidad; el modelo de entidad se implementará de diferentes maneras y con diferentes asignaciones por cliente.

Todo el modelo necesita ser de solo lectura, por lo que las interfaces son del estilo:

public interface IAccount
{
    long AccountId { get; }
    IHouse House { get; }
}

public interface IHouse
{
    long HouseId { get; }
    HouseStatus Status { get; }
    IList<IAccount> Accounts { get; }
}

Implementaciones concretas luego implementarlas con fijadores internos:

public class Account: IAccount
{
    public virtual long AccountId { get; internal set; }
    public virtual IHouse House { get; internal set; }
}

public class House: IHouse
{
    public virtual long HouseId { get; internal set; }
    public virtual HouseStatus Status { get; internal set; }
    public virtual IList<IAccount> Accounts { get; internal set; }
}

He recorrido la ruta del mapeo a las clases concretas. Todo está bien hasta que creas relaciones que devuelven las interfaces y necesitan ser coladas a las implementaciones concretas.

HasMany(x => x.Accounts)

Puede convertirse en

HasMany<Account>(x => x.Accounts)

Pero no hay un 'cast' equivalente para

References(x => x.House)

La asignación a las interfaces (la solución más limpia) genera el problema mencionado anteriormente en el sentido de que el Id debe existir en la clase superior para la configuración y requiere un configurador en la interfaz.

public sealed class AccountMap : ClassMap<IAccount>
{
    public PokerPlayerMap()
    {
        Id(x => x.AccountId, "account_id");

        DiscriminateSubClassesOnColumn("Type").SubClass<Account>(s =>  
        {
            References(x => x.House);       
        });
    }
}

Por ahora, mi única solución es agregar setters a todos los campos de Id de interfaz. Es una pena que la identificación no pueda existir dentro de un subclase o tener su tipo fundido desde la interfaz.

 10
Author: theGecko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-15 11:27:41

Parece que no tengo suficiente reputación para comentar las respuestas de otras personas, pero como tal, voy a tener que hacer de esta una respuesta en su propio derecho.

References ahora tiene una sobrecarga genérica para permitir el reparto que theGecko estaba buscando en su respuesta.

 4
Author: BenCr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-20 13:51:27