1
votes

Problème de mappage de clé composite avec NHibernate (colonne déjà ajoutée)

nouveau sur NHibernate (et Hibernate d'ailleurs), et je suis aux prises avec un problème de clé composite. Voici une version simplifiée d'une partie de la conception de la base de données.

FluentNHibernate.Cfg.FluentConfigurationException
  HResult=0x80131500
  Message=An invalid or incomplete configuration was used while creating a SessionFactory. Check PotentialReasons collection, and InnerException for more detail.


  Source=FluentNHibernate
  StackTrace:
   at FluentNHibernate.Cfg.FluentConfiguration.BuildSessionFactory()
   at pm.dal.DAL.CreateSessionFactory(String connectionString) in C:\Users\Laptop\source\repos\pm\dal\DAL.cs:line 49
   at pm.dal.DAL..ctor(String connectionString) in C:\Users\Laptop\source\repos\pm\dal\DAL.cs:line 41
   at pm.Manager.Manager.Connect(String connectionString) in C:\Users\Laptop\source\repos\pm\Manager\Manager.cs:line 102
  (blah...)

Inner Exception 1:
MappingException: Unable to build the insert statement for class pm.TestZ: a failure occured when adding the Id of the class

Inner Exception 2:
ArgumentException: The column 'a_id' has already been added in this SQL builder
Parameter name: columnName

L'élément clé ici est la clé primaire table_z est un composite des 3 clés primaires de la table a, b, c (donc, elle contrôle combinaisons uniques de a, b et c). Ils sont également individuellement FK à table_a, table_b et table_c.

Maintenant, au-delà des considérations de conception de base de données, existe-t-il un moyen de mapper cela dans NHibernate. Mes tentatives aboutissent à une trace de pile se plaignant que "ArgumentException: La colonne 'a_id' a déjà été ajoutée dans ce générateur SQL". Google me dit que le problème est que j'utilise le même nom de champ aux deux extrémités de la jointure. Je suis surpris que ce soit même un problème - ou je comprends totalement le problème ..

Voici le DDL (Postgresql)

using FluentNHibernate.Mapping;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace pm
{

    class TestAMapping : ClassMap<TestA>
    {
        public TestAMapping()
        {
            Table("test.table_a");
            Id(x => x.Id, "a_id");
            Map(x => x.Label, "label");
        }
    }

    class TestBMapping : ClassMap<TestB>
    {
        public TestBMapping()
        {
            Table("test.table_b");
            Id(x => x.Id, "b_id");
            Map(x => x.Label, "label");
        }
    }

    class TestCMapping : ClassMap<TestC>
    {
        public TestCMapping()
        {
            Table("test.table_c");
            Id(x => x.Id, "c_id");
            Map(x => x.Label, "label");
        }
    }

    class TestZMapping : ClassMap<TestZ>
    {
        public TestZMapping()
        {
            Table("test.table_z");
            CompositeId()
                .KeyProperty(x => x.Aid, "a_id")
                .KeyProperty(x => x.Bid, "b_id")
                .KeyProperty(x => x.Cid, "c_id");
            Map(x => x.Name, "name");
            References(x => x.TestAObj).Column("a_id");
            References(x => x.TestBObj).Column("b_id");
            References(x => x.TestCObj).Column("c_id");

        }
    }


    class TestA
    {
        public virtual string Id { get; set; }
        public virtual string Label { get; set; }

    }
    class TestB
    {
        public virtual string Id { get; set; }
        public virtual string Label { get; set; }

    }
    class TestC
    {
        public virtual string Id { get; set; }
        public virtual string Label { get; set; }

    }

    class TestZ
    {
        public virtual string Aid { get; set; }
        public virtual string Bid { get; set; }
        public virtual string Cid { get; set; }
        public virtual string Name { get; set; }
        public virtual TestA TestAObj { get; set; }
        public virtual TestB TestBObj { get; set; }
        public virtual TestC TestCObj { get; set; }

        // https://stackoverflow.com/a/7919012/8691687
        public override bool Equals(object obj)
        {
            var other = obj as TestZ;

            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;

            return this.Aid == other.Aid &&
                this.Bid == other.Bid && this.Cid == other.Cid;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hash = GetType().GetHashCode();
                hash = (hash * 31) ^ Aid.GetHashCode();
                hash = (hash * 31) ^ Bid.GetHashCode();
                hash = (hash * 31) ^ Cid.GetHashCode();

                return hash;
            }
        }
    }

}

et voici le Fluent Hibernate Code C #

CREATE TABLE test.table_a(
a_id varchar(10) primary key,
label varchar(50)
);

CREATE TABLE test.table_b(
b_id varchar(10) primary key,
label varchar(50)
);


CREATE TABLE test.table_c(
c_id varchar(10) primary key,
label varchar(50)
);

CREATE TABLE test.table_z(
a_id varchar(10),
b_id varchar(10),
c_id varchar(10),
name varchar(100)
);

-- add combined primary key on table_z
ALTER TABLE test.table_z ADD CONSTRAINT pk_z_combined
    PRIMARY KEY (a_id,b_id,c_id)
;

-- FK

ALTER TABLE test.table_z ADD CONSTRAINT FK_to_a
    FOREIGN KEY (a_id) REFERENCES test.table_a (a_id) ON DELETE No Action ON UPDATE No Action;

ALTER TABLE test.table_z ADD CONSTRAINT FK_to_b
    FOREIGN KEY (b_id) REFERENCES test.table_b (b_id) ON DELETE No Action ON UPDATE No Action;

    ALTER TABLE test.table_z ADD CONSTRAINT FK_to_c
    FOREIGN KEY (c_id) REFERENCES test.table_c (c_id) ON DELETE No Action ON UPDATE No Action;

Trace de pile pertinente

  table_a
 +---------------------+
 | * a_id varcha (10)  |                   table_z
 |   label varchar(50) |                 +----------------------+
 |                     +<----------------+ * a_id varchar(10)   |
 +---------------------+      +----------| * b_id varchar(10)   |
                              |   +------+ * c_id varchar(10)   |
  table_b                     |   |      |   name varchar(100)  |
 +---------------------+      |   |      |                      |
 | * b_id varcha (10)  |      |   |      +----------------------+
 |   label varchar(50) <------+   |
 |                     |          |
 +---------------------+          |
                                  |
  table_c                         |
 +---------------------+          |
 | * c_id varcha (10)  <----------+
 |   label varchar(50) |
 |                     |
 +---------------------+

Quelqu'un peut-il me dire où j'ai péché?

Merci beaucoup


0 commentaires

3 Réponses :


1
votes

J'ai juste regardé comment je l'ai fait dans mon projet. Malheureusement, je ne peux pas expliquer pourquoi c'est la voie à suivre ;-)

public TestZMapping()
{
    Table("test.table_z");
    CompositeId()
        .KeyProperty(x => x.Aid, "a_id")
        .KeyProperty(x => x.Bid, "b_id")
        .KeyProperty(x => x.Cid, "c_id");
    Map(x => x.Name, "name");
    References(x => x.TestAObj).Column("a_id").Not.Insert().Not.Update();
    References(x => x.TestBObj).Column("b_id").Not.Insert().Not.Update();
    References(x => x.TestCObj).Column("c_id").Not.Insert().Not.Update();
}


1 commentaires

ok .. cela fonctionne, donc j'ai accepté la réponse, mais n'empêcherais pas l'insertion et les mises à jour? Aussi - pourquoi ça marche?



0
votes

NHibernate 5 vous oblige à définir une responsabilité claire pour la mise à jour d'un champ de base de données - surtout si vous avez plusieurs propriétés accédant au même champ. Un seul mappage de propriété est autorisé à être accessible en écriture - être propriétaire de la "nouvelle valeur".

Une clé composite ne définit pas vraiment les propriétés d'une classe - elle définit simplement une clé composite. La définition d'une propriété-clé ne vous permet PAS, par exemple pour trier cette propriété dans une requête. Vous devez donc répéter cette propriété dans la liste des propriétés. Mais cette répétition doit être en lecture seule pour donner à la propriété-clé la responsabilité de mettre à jour la valeur.

NHibernate 4 avec des propriétés répétées => autorisé, mais peut conduire à un comportement inattendu

<!-- readonly "repeated" properties -->
<composite-id class="MyClass, MyDll" >
    <key-property name="Key1" />
    <key-property name="Key2"/>
</composite-id>
<property name="Key1" insert="false" update="false"/>
<property name="Key2" insert="false" update="false"/>
<property name="SomeProperty" />
<property name="SomeMoreKey1" column="Key1"  insert="false" update="false"/>

NHibernate 5

<!-- not sortable by Key1 or Key2, because property is not known -->
<composite-id class="MyClass, MyDll" >
    <key-property name="Key1" />
    <key-property name="Key2"/>
</composite-id>
<!-- no allowed to repeat the keys as property => "has already been defined"
<property name="SomeProperty" />

NHibernate 5 avec propriétés

<composite-id class="MyClass, MyDll" >
    <key-property name="Key1" />
    <key-property name="Key2"/>
</composite-id>
<property name="Key1" />
<property name="Key2" />
<property name="SomeProperty" />
<property name="SomeMoreKey1" column="Key1" />


0 commentaires

0
votes

Si vous devez utiliser à la fois ComposedId et ManyToOne pour la même colonne et obtenir des erreurs:

Colonne inconnue 'SomeId' dans 'field list'

ou

La colonne 'SomeId' a déjà été ajoutée dans ce générateur SQL Nom du paramètre: columnName

Ensuite, mettez simplement ManyToOne dans le mappeur ComposedId. Comme ceci:

internal class PricesMapping : ClassMapping<PriceEntity>
    {
        public PricesMapping()
        {
            Table("Prices");

            ComposedId(m =>
            {
                m.ManyToOne(x => x.PriceBlock, map =>
                {
                    map.Column("PriceBlockId");
                });
                m.Property(x => x.CurrencyCode);
            });

            Property(x => x.Value, map => map.Column("Price"));
        }
    }


0 commentaires