Asignación de clave compuesta de Nhibernate fluida

He tratado de resolver esta pregunta durante bastante tiempo. Tengo una manera hacky de hacer que funcione.

Solo quiero saber si esto es posible en el mapeo de Fluent NHibernate.

Digamos que tengo dos tablas, por ejemplo:

Table ComissionLevel
{
    Year,
    ComissionLevelID,

    ... other properties ....
}
primary key (Year,ComissionLevelID)

Table ClientCommission
{
    Year,
    ClientID,
    CommissionLevelID_1,
    CommissionLevelID_2,

    ... other properties ...
}
primary key (Year,ClientID)
foreign key CommissionLevel1 (Year,CommissionLevelID_1)
foreign key CommissionLevel2 (Year,CommissionLevelID_2)

Actualmente mis asignaciones son las siguientes:

public ComissionLevelMap()
{
  Schema("XXXX");
  Table("ComissionLevel");
  LazyLoad();
  CompositeId()
    .KeyProperty(x => x.Year, set => {
        set.ColumnName("Year");
        set.Access.Property(); } )
    .KeyProperty(x => x.CommissionLevelID, set => {
        set.ColumnName("CommissionLevelID");
        set.Length(10);
        set.Access.Property(); } );

  HasMany<ClientCommission>(x => x.ClientCommissions)
    .Access.Property()
    .AsSet()
    .Cascade.AllDeleteOrphan()
    .LazyLoad()
    .Inverse()
    .Generic()
    .KeyColumns.Add("Year", mapping => mapping.Name("Year")
                                                         .SqlType("NUMBER")
                                                         .Nullable())
    .KeyColumns.Add("CommissionLevelID_1", mapping => mapping.Name("CommissionLevelID_1")
                                                         .SqlType("VARCHAR2")
                                                         .Nullable()
                                                         .Length(10));
  HasMany<ClientCommission>(x => x.ClientCommission2s)
    .Access.Property()
    .AsSet()
    .Cascade.AllDeleteOrphan()
    .LazyLoad()
    .Inverse()
    .Generic()
    .KeyColumns.Add("Year", mapping => mapping.Name("Year")
                                                         .SqlType("NUMBER")
                                                         .Nullable())
    .KeyColumns.Add("CommissionLevelID_2", mapping => mapping.Name("CommissionLevelID_2")
                                                         .SqlType("VARCHAR2")
                                                         .Nullable()
                                                         .Length(10));
}

public ClientCommissionMap()
{
  Schema("XXXXX");
  Table("ClientCommission");
  LazyLoad();
  CompositeId()
    .KeyProperty(x => x.ClientID, set => {
        set.ColumnName("ClientID");
        set.Length(10);
        set.Access.Property(); } )
    .KeyProperty(x => x.Year, set => {
        set.ColumnName("Year");
        set.Access.Property(); } );
  References(x => x.ComissionLevel1)
    .Class<ComissionLevel>()
    .Access.Property()
    .Cascade.None()
    .LazyLoad()
    .Insert()
    .Update()
    .Columns("Year", "CommissionLevelID_1");
  References(x => x.ComissionLevel2)
    .Class<ComissionLevel>()
    .Access.Property()
    .Cascade.None()
    .LazyLoad()
    .Insert()
    .Update()
    .Columns("Year", "CommissionLevelID_2");

}

Mi problema ahora es que cada vez que creo un Nivel de Comisión y asigno Comisión de Cliente a su colección, si los guardo mediante una sesión de llamada. Guardar (Nivel de Comisión) me generará una excepción.

<Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index>.

Mi pregunta aquí es:

  1. ¿NHibernate guarda automáticamente las relaciones? me gusta:

        ClientCommission commission = new ClientCommission{Year = 2012, ClientID =SomeGuid};
        CommissionLevel  newCommissionLevel = new CommissionLevel{Year = 2012, CommissionLevelID =NewCommissionLevelGuid};
    
        newCommissionLevel.ClientCommission1s.Add(commission);
        newCommissionLevel.ClientCommission2s.Add(commission);
    
        CommissionLevelRepo.Save(newCommissionLevel);
    

    Cuando llamo a CommissionLevelRepo.Save(newCommissionLevel), si NHibernate también actualizará ClientCommission.ComissionLevel1 y ClientCommission.ComissionLevel2

o tengo que decir

ClientCommission.ComissionLevel1 = newCommissionLevel; 
ClientCommission.ComissionLevel2 = newCommissionLevel; 
  1. Para la excepción que obtuve, es porque NHibernate no genera la columna correcta, parece que generará tres columnas de año. Porque si creo manualmente dos propiedades llamadas ComissionLevelID1 y CommissionLevelID2, desactivo .Insert() y .Update() en ClientCommission, lo guardará correctamente.

¿Alguien puede mostrarme una forma adecuada de mapear esas dos clases?

Muchas gracias.

preguntado el 02 de julio de 12 a las 02:07

solo un pensamiento. si está introduciendo toda esta complejidad. probablemente estés haciendo algo mal. Simplifique su mapeo y diseño. no podrá mantenerlo más adelante. -

2 Respuestas

respuesta corta: no puede compartir columnas para múltiples referencias

respuesta larga: NHibernate trata cada referencia de forma independiente, pero elimina las columnas duplicadas en las declaraciones de inserción, por lo que las referencias intentan acceder a las columnas que ya no están presentes. lo hace porque si la columna compartida difiere entre las referencias a en el modelo de objetos, no puede decidir cuál es la correcta.

Si puede cambiar el esquema de la base de datos y hacer que las identificaciones sean únicas, entonces ignore el año en las identificaciones y referencias.

Actualizar:

puede simplificar algunas de las asignaciones

CompositeId()
    .KeyProperty(x => x.Year, set => {
        set.ColumnName("Year");
        set.Access.Property(); } )
    .KeyProperty(x => x.CommissionLevelID, set => {
        set.ColumnName("CommissionLevelID");
        set.Length(10);
        set.Access.Property(); } );

// to
CompositeId()
    .KeyProperty(x => x.Year)  // columnname is equal propertyname by default
    .KeyProperty(x => x.CommissionLevelID, set => set.Length(10).Access.Property());  // property is default access and can also be left out


.SqlType("VARCHAR2").Length(10)
// to
.Length(10) or .SqlType("VARCHAR2")
// because length is ignored when sqltype is specified

Respondido 23 Abr '14, 13:04

Muchas gracias. Desafortunadamente no puedo cambiar el db_schema. DBA no nos permite hacerlo. ¿Puedo preguntar dónde leyó esos conocimientos sobre NHibernate? ¿Hay alguna documentación en alguna parte? - user1494907

la mayor parte es una experiencia dolorosa con dbs heredados. Descubrí las causas buscando en Google, leyendo la fuente de NH, prueba y error. Cada vez que te encuentras con un problema de este tipo, tienes que hacer concesiones. Puede mapearlo de manera diferente (p. ej., ignorar las claves primarias/foráneas en la base de datos si las partes son únicas), sangrar propiedades privadas en el modelo de dominio, implementar ganchos de nhibernate para sortear el manejo predeterminado de NHibernate. - fuego

Para la clave compuesta, mira Asignación de claves compuestas en Fluent NHibernate

Para simplificar el mapeo, puede cambiar la clave principal a una sola clave y crear un índice único para representarla, pero no es la mejor solución.

Antes (clave compuesta):

CREATE TABLE XPTO ( COD_XPTO1 INT NOT NULL IDENTITY,
                    COD_XPTO2 INT NOT NULL,
                    TXT_XPTO VARCHAR(10) NOT NULL)
ALTER TABLE XPTO
  ADD CONSTRAINT PK_XPTO (COD_XPTO1, COD_XPTO2)

Después (clave única con índice único):

CREATE TABLE XPTO ( COD_XPTO1 INT NOT NULL IDENTITY,
                    COD_XPTO2 INT NOT NULL,
                    TXT_XPTO VARCHAR(10) NOT NULL)

ALTER TABLE XPTO
  ADD CONSTRAINT PK_XPTO (COD_XPTO1)

CREATE UNIQUE INDEX UK_XPTO ON XPTO (COD_XPTO1, COD_XPTO2)

contestado el 23 de mayo de 17 a las 12:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.