2009-11-11 5 views
8

Estoy escribiendo un motor de blog como ejercicio de aprendizaje. Sé que hay muchos motores de blogs por ahí, pero tengan paciencia ...Cómo evitar NHibernate.NonUniqueObjectException

Tengo una entidad BlogPost que tiene una propiedad Etiquetas que son una IList de etiquetas asociadas. El método BlogPost.SetTags (cadena) divide la cadena, crea nuevos objetos Tag con el nombre de etiqueta especificado y los agrega a la lista. Lo mismo para BlogPost.AddTag (string tagName).

Lo que me gustaría que sucediera es que cuando llamo a BlogPost.AddTag ("foo") donde ya existe una entidad de etiqueta llamada "foo" y persiste en la base de datos, nHibernate se da cuenta de eso y conecta la publicación con la etiqueta existente.

En el método BlogRepository.Save(), compruebo si cada etiqueta en la lista de etiquetas ya existe. Si no, lo guardo con una llamada a TagRepository.Save (tag);

El problema es, en el código de ejemplo siguiente, estoy recibiendo un error "NHibernate.NonUniqueObjectException: un objeto diferente con el mismo valor de identificador ya estaba asociado con la sesión: la etiqueta 1, de entidad: CMS.Core. Model.Tag "cuando intento persistir un objeto BlogPost usando una etiqueta existente. Cuando insisto en un objeto BlogPost que solo usa nuevas etiquetas, se crean y todo está bien.

Nota También estoy usando TagName como clave principal en la base de datos para la tabla bp_Tags. Parecía superfluo usar un entero o GUID PK cuando la tabla solo almacena nombres únicos de Etiqueta.

configuración de Mi NHibernate parece:

<class name="CMS.Core.Model.Tag,CMS.Core" table="bp_Tags"> 
    <id column="TagName" name="TagName" type="String" unsaved-value=""> 
     <generator class="assigned" /> 
    </id> 
    </class> 

    <class name="CMS.Core.Model.BlogPost,CMS.Core" table="bp_Content"> 
    <id name="Id" column="Id" type="Int32" unsaved-value="0"> 
     <generator class="native"></generator> 
    </id> 
    <property name="SubmittedBy" column="SubmittedBy" type="string" length="256" not-null="true" /> 
    <property name="SubmittedDate" column="SubmittedDate" type="datetime" not-null="true" /> 
    <property name="PublishDate" column="PublishDate" type="datetime" not-null="true" /> 
    ...  
    <bag name="_tagsList" table="bp_Tags_Mappings" lazy="false" cascade="all"> 
     <key column="Target_Id" /> 
     <many-to-many class="CMS.Core.Model.Tag,CMS.Core" column="TagName" lazy="false" /> 
    </bag> 

NHibernate.NonUniqueObjectException: un objeto diferente con el mismo valor de identificador ya estaba asociado con la sesión: etiqueta 1, de entidad: Bariliant.CMS.Core.Model. tag

BlogPost post, post2; 

    using (UnitOfWork.Start()) 
    { 
     post = BlogPostFactory.CreateBlogPost("test post", "test body"); 
     post.Publish(); 
     BlogRepository.Save(post); 
     UnitOfWork.Current.Flush(); 

     post.SetTags("tag 1, tag 2"); 
     BlogRepository.Save(post); 
     UnitOfWork.Current.Flush(); 
    } 

    using (UnitOfWork.Start()) 
    { 
     post2 = BlogPostFactory.CreateBlogPost("test post2", "test body"); 
     post2.Publish(); 
     BlogRepository.Save(post2); 
     UnitOfWork.Current.Flush(); 

     post2.AddTag("tag 1"); 
     BlogRepository.Save(post2); // throws 

...

Alguna idea de lo que estoy haciendo mal y cómo solucionarlo?

+2

Creo que el problema es interno y proviene de la forma en que recibe la lista de etiquetas existentes para compara el nuevo con, y cómo asignar uno existente al nuevo objeto BlogPost. Sería una buena idea publicar el código de BlogRepository.Save(); método donde todo esto sucede para que podamos detectar el problema. – tolism7

Respuesta

8

Dado que TagName es la identificación, se está enfrentando al mapa de identidad de NHibernate. Su mapa de identidad ya conoce un objeto con la misma ID, por lo que le ofrece esa excepción.

Es posible que desee intentar algo donde mire para ver si esa etiqueta ya existe en esa sesión y, de ser así, asocie esa etiqueta preexistente con la segunda publicación.

ejemplo Pseudo-código:

var tag = session.Get<Tag>("Tag 1"); 

if (tag != null) 
{ 
    post.AddTag(tag); 
} 
else 
{ 
    post.AddTag(new Tag("Tag 1")); 
} 

Esta entrada de blog le dará una explicación detallada: NHibernate - Cross session operations

+0

Gracias. Terminé haciendo algo similar. En lugar de hacer que la persona que llama compruebe antes de agregar la etiqueta o hacer que las entidades de contenido tomen una dependencia en el repositorio de etiquetas para realizar la verificación, yo hago la verificación en la clase base del repositorio de contenido. Si la etiqueta ya existe, elimino la "nueva" y agrego la "existente". Funciona como un encanto y no rompe mis objetivos de dependencia. –

4

No es la forma en que lo haces, pero aquí te explicamos cómo resolver tu problema. Normalmente, en la programación orientada a objetos la siguiente 2 objetos no son iguales:

var object1 = new Tag("hello"); 
var object2 = new Tag("hello"); 

var areSame = (object1 == object2); // false 

Usted ha hecho 2 objetos separados con idéntico estado, pero son dos objetos diferentes por lo que si se los compara por la igualdad, entonces no son los mismos . Claramente, cuando se trata de NHibernate, estos objetos son en realidad la misma entidad.

Resolvemos esto para NHibernate anulando 2 métodos de la clase de objeto. GetHashCode() e Igual()

GetHashCode() básicamente devuelve un código de hash único basado en el estado de un objeto. equals() compara dos objetos para la igualdad

como esto:

public override int GetHashCode() 
{ 
    return (this.GetType() + "|" + _tagName).GetHashCode(); 
} 

public override bool Equals(object obj) 
{ 
    return this.GetHashCode() == obj.GetHashCode(); 
} 

Básicamente GetHashCode concatena el tipo de objeto y el nombre de la etiqueta como una cadena, es decir App.Domain.Tag|nameoftag y genera un código hash para esa cadena

Equals() luego compara el resultado GetHashCode() para el primer objeto con el resultado GetHashCode() para el segundo objeto para probar la igualdad. Si hace esto con los dos objetos que definimos anteriormente, los dos códigos de almohadilla serán los mismos y, por lo tanto, la comparación de Igual() será verdadera. Cuando NHibernate prueba los dos objetos para la igualdad en su funcionamiento interno, determinará que son los mismos y que debería resolver su problema.

+0

Gracias. Sí, los he reemplazado, pero me gustan más tus implementaciones y las he adoptado. –

+0

Por cierto, me encantaría escuchar su opinión sobre cómo manejaría este escenario de manera diferente. –

+3

@Joe: tenga en cuenta que no se recomienda implementar 'Equals' de esta manera, ya que' GetHashCode' no devuelve valores únicos de hash para diferentes instancias. De esta forma, 'Equals' podría devolver' true' para los objetos que ** no ** realmente son iguales. Esto resolverá su problema de excepción, pero podría obtener fácilmente una instancia incorrecta devuelta de un 'Hashtable', por ejemplo. – Groo