3

The problem appears to be simple however I'm having so much trouble trying to map this entities. I just can't see what am I doing wrong. Can you guys help me?

I have the class Cliente:

public class Cliente
{    
    public Cliente () { }
    public virtual int ClienteId { get; set; }  
    public IList<Medidor> ListaMedidores { get; set; }   
    public virtual string NumeroMedidor { get; set; }       
}

And class Medidor

public class Medidor
{
    public Medidor() { }
    public virtual string NumeroMedidor { get; set; }
    public virtual string MarcaMedidor { get; set; }
    public virtual Cliente Cliente { get; set; }
}

I tried to map like this

public ClienteMap()
{
    Map(x => x.NumeroMedidor).Column("CORE_NUMERO_MEDIDOR");
    HasMany(x => x.ListaMedidores)
        .KeyColumn("NUMERO_MEDIDOR").Inverse().Cascade.All();
}


public MedidorMap()
{
    Table("medidor");
    LazyLoad();

    Id(x => x.NumeroMedidor).Column("NUMERO_MEDIDOR");
    Map(x => x.TipoMedidor).Column("TIPO_MEDIDOR");
    References(x => x.Cliente).Column("CORE_NUMERO_MEDIDOR");
}

The goal is bring my List of Medidor according to database. So I did:

Session.Query<Cliente>().Fetch(x => x.ListaMedidores).ToList();

And i'm getting the list always empty. Even having data on those tables... I would appreciate any kind of help or suggestion.

Regards

EDIT

My database is like this:


CREATE TABLE CLIENTE
(
  CORE_ID                      NUMBER           NOT NULL,
  CORE_NUMERO_MEDIDOR          VARCHAR2(50 BYTE)
)

CREATE TABLE MEDIDOR
(
  NUMERO_MEDIDOR  VARCHAR2(50 BYTE),
  MARCA_MEDIDOR   VARCHAR2(50 BYTE)
)

Given the sql select * from cliente where core_numero_medidor = '3569371':

CORE_ID CORE_NUMERO_MEDIDOR
123     3569371

and the sql select * from MEDIDOR where numero_medidor = '3569371':

NUMERO_MEDIDOR MARCA_MEDIDOR
3569371        general_motors
3569371        kia
3569371        FIAT

So I'm suppose to get 3 elements on my IList<Medidor> Lista Medidores on Cliente class..

EDIT

I changed to this:

public class Cliente
{    
    public Cliente () { }
    public virtual int ClienteId { get; set; }  
    public IList<Medidor> ListaMedidores { get; set; }   
    public virtual string NumeroMedidor { get; set; }       
}
public class Medidor
{
    public Medidor() { }
    public virtual string NumeroMedidor { get; set; }
    public virtual string MarcaMedidor { get; set; }
}

And changed the map of ClienteMap to:

Map(x => x.NumeroMedidor).Column("NUMERO_MEDIDOR");    
HasMany(x => x.ListaMedid)
            .KeyColumns.Add("NUMERO_MEDIDOR")
            .Table("MEDID")
            .PropertyRef("CoreNumeroCliente")
            .Cascade.All();

and now the list gets the expected number of records but all of them its the same as the first one. ie:

Expected

NUMERO_MEDIDOR MARCA_MEDIDOR
3569371        general_motors
3569371        kia
3569371        FIAT

My result

NUMERO_MEDIDOR MARCA_MEDIDOR
3569371        general_motors
3569371        general_motors
3569371        general_motors

Any suggestions? I would like to thank @Radim Köhler so far for the help.

ANOTHER EDIT

I found the solution!

I was trying to map a non-unique column as a primary key... I just changed the column to a real primary key and worked!

So now here's the solution

public class Cliente
{    
    public Cliente () { }
    public virtual int ClienteId { get; set; }  
    public IList<Medidor> ListaMedidores { get; set; }   
    public virtual string NumeroMedidor { get; set; }       
}
public class Medidor
{
    public Medidor() { }
    public virtual string NumeroMedidor { get; set; }
    public virtual string MarcaMedidor { get; set; }
}

public class ClienteMap : ClassMap<Cliente>
{
    public ClienteMap()
    {
        Map(x => x.NumeroMedidor).Column("NUMERO_MEDIDOR");    
        HasMany(x => x.ListaMedid)
            .KeyColumns.Add("NUMERO_MEDIDOR")
            .Table("MEDID")
            .PropertyRef("CoreNumeroCliente")
            .Cascade.All();
    }
}

public class MedidorMap : ClassMap<Medidor>
{
    public MedidorMap()
    {
        LazyLoad();

        Id(x => x.NumeroMedidor).Column("NUMERO_MEDIDOR");
        Map(x => x.MarcaMedidor).Column("MARCA_MEDIDOR");
        [...] //Other properties
    }
}

And here is my query:

Session.Query<CorteReligacao>()
                .Fetch(x => x.ListaMedid)

I really would like to thanks Radim Köhler for the help. His patience, attention and willingness to helping solve the problem leaves me with lack of way of thanks..I can only wish him all the best in life.

And I really hope that this thread may help people with the same problem.

Regards.

Community
  • 1
  • 1
Marllon Nasser
  • 390
  • 4
  • 22

2 Answers2

2

The one-to-many and many-to-one are always related by one column. This is such column, which contains reference ID (foreign key) to the other table / entity.

In our case, it must be column in table of Medidor, and its name would be "CORE_NUMERO_MEDIDOR". The mapping should look like this

public ClienteMap()
{
    ...
    HasMany(x => x.ListaMedidores)
       //.KeyColumn("NUMERO_MEDIDOR")
       .KeyColumn("CORE_NUMERO_MEDIDOR") // column in other table
       .Inverse().Cascade.All();
}


public MedidorMap()
{
    ...
    References(x => x.Cliente)
        .Column("CORE_NUMERO_MEDIDOR");  // column in this table
}

EXTEND

Based on extended question, when we can see this structure of tables

CREATE TABLE CLIENTE
(
  CORE_ID                      NUMBER           NOT NULL,
  CORE_NUMERO_MEDIDOR          VARCHAR2(50 BYTE)
)

CREATE TABLE MEDIDOR
(
  NUMERO_MEDIDOR  VARCHAR2(50 BYTE),
  MARCA_MEDIDOR   VARCHAR2(50 BYTE)
)

That the DB reference is different then in C#. It seems, like if

table CLIENTE references just one MEDIDOR, while MEDIDOR has many CLIENTEs.

It seems that the objects should look like this:

public class Cliente
{    
    ...
    //public IList<Medidor> ListaMedidores { get; set; }    
    //public Medidor Medidor { get; set; }    
}

public class Medidor
{
    ...
    //public virtual Cliente Cliente { get; set; }
    public virtual IList<Cliente> Clientes { get; set; }
}

and the mapping should be

public ClienteMap()
{
    ...
    References(x => x.Medidor, "CORE_NUMERO_MEDIDOR");
}


public MedidorMap()
{
    ...
    Id(x => x.NumeroMedidor).Column("NUMERO_MEDIDOR")
                               // column in this table to be compared
    HasMany(x => x.Clientes)
       .KeyColumn("CORE_NUMERO_MEDIDOR") // with column in other table
       .Inverse().Cascade.All();
}

ANOTHER EXTEND

Because the second table MEDIDOR is not having its own primary key (column NUMERO_MEDIDOR) but it could contain many same values... coming from CLIENT TABLE... we should use component mapping

public ClienteMap()
{
    ...
    Map(x => x.NumeroMedidor).Column("CORE_NUMERO_MEDIDOR");
    HasMany(x => x.ListaMedidores)
        .Component(com =>
        {
            com.Parent(y => y.Cliente, "NUMERO_MEDIDOR")
               .PropertyRef("NumeroMedidor")
               ;
            com.Map(y => y.MarcaMedidor, "MARCA_MEDIDOR");
        })
        .PropertyRef("NumeroMedidor")
        .Table("MEDIDOR")
       // .Inverse() // NO INVERSE, won't work
       .Cascade.All();

}
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Sorry.. I tryed this already... then I got this as inner exception "ORA-00904: \"LISTAMEDIDORES3_\".\"CORE_NUMERO_MEDIDOR\": invalid identifier" Maybe something wrong with database? I have no foreign key on table `Medidor`... neither pk.. – Marllon Nasser Jul 08 '15 at 12:56
  • Could you extend your answer with full exception? Anyhow, my mapping is the way to go. We have to use same column on both sides... also, could you please confirm, that the REFERENCE column is "CORE_NUMERO_MEDIDOR"? I am still not sure about your table structure, so maybe .. .could you show (simplified) create script? – Radim Köhler Jul 08 '15 at 13:12
  • Great, will check that – Radim Köhler Jul 08 '15 at 13:23
  • Thanks man. This would be simple solved with a left outer join on SQL... I will edit the question with some select data on each tables. – Marllon Nasser Jul 08 '15 at 13:25
  • I would say, that my answer (extended part) is now correct, but it means lot of changes to your model – Radim Köhler Jul 08 '15 at 13:32
  • @Köhler, on my scenario, I can have more than one `Cliente` with the same `Medidor`. I have edited the question with some select that might help. I just checked your answer with extended part. In that way, I wont be able to retrieve a List of `Cliente` with each client having a full list of their `Medidor` right? – Marllon Nasser Jul 08 '15 at 13:34
  • OK, this means totally different world. These tables are not joined by the primary key. That's the first issue. And the second table (MEDIDOR) is not in fact an entity, it does not have its ID. It should be mapped as a component... – Radim Köhler Jul 08 '15 at 13:37
  • I think I understand... If I get the `Cliente` "Medidor", I can access "Medidor" properties and get the List, right? Thanks mate! That `Medidor` table is kinda of filled on an external way... a webservice should do that... so it's not necessary to have a primary key, right? I will check your changes and I will come back with feedback asap. Thanks mate! – Marllon Nasser Jul 08 '15 at 13:40
  • I drafted another solution, with component mapping... hope it help – Radim Köhler Jul 08 '15 at 13:49
  • Unfortunately it doesn't work...now i'm getting `property not found: JobTypeAndCode on entity Cliente`... and I can't use the .Parent map. I'm using HasMany(x => x.ListaMedidores) .Component(com => { com.ParentReference(y => y.Cliente); com.Map(y => y.NumeroMedidor, "NUMERO_MEDIDOR"); }) .PropertyRef("JobTypeAndCode") .Table("MEDIDOR") .Cascade.All(); – Marllon Nasser Jul 08 '15 at 14:05
  • Sorry... that is type... sorry... I updated the answer. It should be **`.PropertyRef("NumeroMedidor")`** (just copy past from my stuff ;)... This setting is saying - map that table (medidor) with my table ... but not by ID, not by primary key... but by other property... in our case NumeroMedidor. Hope it is clear now ;) – Radim Köhler Jul 08 '15 at 14:08
  • Not working... but I think we are close... i'm getting this `{"ORA-00904: \"LISTAMEDIDORES3_\".\"CLIENTE_ID\": invalid identifier"}` And I am doing this: HasMany(x => x.ListaMedid).Component(com => { com.ParentReference(y => y.CorteReligacao); com.Map(y => y.NumeroCliente, "NUMERO_CLIENTE"); }) .PropertyRef("CoreNumeroCliente") .Table("MEDID") .Cascade.All(); - I got the generated SQL and modified the `ClienteID` to "CoreNumeroOrdem" and worked – Marllon Nasser Jul 08 '15 at 14:31
  • I cannot believe it... it seems that I again made a type with cliente.... I updated my answer with `com.Parent(y => y.Cliente, "NUMERO_MEDIDOR") .PropertyRef("NumeroMedidor")`... or something like this... we simply have to do the same with Parent mapping as we did with collection...Have to go... hope you have the direction and will make it ;) – Radim Köhler Jul 08 '15 at 14:34
  • We're almost there @Radim, I updated the question with the changes...can you take a loot please? – Marllon Nasser Jul 08 '15 at 17:34
1

After all, with these SQL scripts (adjust for SQL Server in my case)

CREATE TABLE CLIENTE
(
  CORE_ID                      int           NOT NULL,
  CORE_NUMERO_MEDIDOR          VARCHAR(50)
)

CREATE TABLE MEDIDOR
(
  NUMERO_MEDIDOR  VARCHAR(50),
  MARCA_MEDIDOR   VARCHAR(50)
)

With these entities (all properties are virtual)

public class Cliente
{    
    public virtual int ClienteId { get; set; }  
    public virtual IList<Medidor> ListaMedidores { get; set; }   
    public virtual string NumeroMedidor { get; set; }       
}
public class Medidor
{
    public virtual string NumeroMedidor { get; set; }
    public virtual string MarcaMedidor { get; set; }
    public virtual Cliente Cliente { get; set; }
}

and with only this one mapping in place:

public class ClienteMap: ClassMap<Cliente>
{
    public ClienteMap()
    {
        Table("CLIENTE");
        Id(x => x.ClienteId, "CORE_ID");
        Map(x => x.NumeroMedidor).Column("CORE_NUMERO_MEDIDOR");
        HasMany(x => x.ListaMedidores)
            .KeyColumn("NUMERO_MEDIDOR")
            .Component(com =>
            {
                com.ParentReference(y => y.Cliente);
                com.Map(y => y.MarcaMedidor, "MARCA_MEDIDOR");
            })
            .PropertyRef("NumeroMedidor")
            .Table("MEDIDOR")
            // .Inverse() // NO INVERSE, won't work
            .Cascade.All();
    }
}

I can confirm, that this query will work:

var list = session.Query<Cliente>().Fetch(x => x.ListaMedidores).ToList();
var firt = list.First().ListaMedidores.First();
var last = list.First().ListaMedidores.Last();
Assert.IsTrue(firt.MarcaMedidor != last.MarcaMedidor);

BTW, this will be (my preferred) generated xml mapping:

<class xmlns="urn:nhibernate-mapping-2.2" name="Cliente" table="CLIENTE">
    <id name="ClienteId" type="System.Int32">
      <column name="CORE_ID" />
      <generator class="identity" />
    </id>
    <bag cascade="all" name="ListaMedidores" table="MEDIDOR">
      <key property-ref="NumeroMedidor">
        <column name="NUMERO_MEDIDOR" />
      </key>
      <composite-element class="Medidor">
        <parent name="Cliente" />
        <property name="MarcaMedidor" type="System.String">
          <column name="MARCA_MEDIDOR" />
        </property>
      </composite-element>
    </bag>
    <property name="NumeroMedidor" type="System.String">
      <column name="CORE_NUMERO_MEDIDOR" />
    </property>
</class>

For documentation see:

7.2. Collections of dependent objects

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • 1
    you're the guy! ahaha I just updated the question with solution thanks to you. I just had to remove the .Component mapping because I have other properties on `Medidor` class. I wish all the best for you man.. thanks :D – Marllon Nasser Jul 08 '15 at 18:21