Pregunta

Tengo una relación de muchos a muchos entre fotos y etiquetas: una foto puede tener varias etiquetas y varias fotos pueden compartir las mismas etiquetas.

Tengo un bucle que escanea las fotos en un directorio y luego las agrega a NHibernate. Algunas etiquetas se agregan a las fotos durante ese proceso, por ej. una etiqueta de 2009 cuando la foto se toma en 2009.

La clase Tag implementa Equals y GetHashCode y usa la propiedad Name como la única propiedad de firma. Tanto la foto como la etiqueta tienen claves sustitutas y están versionadas.

Tengo un código similar al siguiente:

public void Import() {
    ...
    foreach (var fileName in fileNames) {
        var photo = new Photo { FileName = fileName };
        AddDefaultTags(_session, photo, fileName);
        _session.Save(photo);
    }
    ...
}

private void AddDefaultTags(…) {
    ...
    var tag =_session.CreateCriteria(typeof(Tag))
                    .Add(Restriction.Eq(“Name”, year.ToString()))
                    .UniqueResult<Tag>();

    if (tag != null) {
        photo.AddTag(tag);
    } else {
        var tag = new Tag { Name = year.ToString()) };
        _session.Save(tag);
        photo.AddTag(tag);
    }
}

Mi problema es cuando la etiqueta no existe, por ejemplo, La primera foto de un nuevo año. El método AddDefaultTags comprueba si la etiqueta existe en la base de datos y luego la crea y la agrega a NHibernate. Eso funciona muy bien al agregar una sola foto, pero al importar varias fotos en el nuevo año y dentro de la misma unidad de trabajo falla, ya que todavía no existe en la base de datos y se agrega nuevamente. Al completar la unidad de trabajo, falla porque intenta agregar dos entradas en la tabla de Etiquetas con el mismo nombre ...

Mi pregunta es cómo asegurarse de que NHibernate solo intente crear una sola etiqueta en la base de datos en la situación anterior. ¿Debo mantener una lista de etiquetas recién agregadas o puedo configurar la asignación de tal manera que funcione?

¿Fue útil?

Solución

Debe ejecutar _session.Flush () si sus criterios no deben devolver datos obsoletos. O bien, debería poder hacerlo correctamente configurando _session.FlushMode en Auto.

Con FlushMode.Auto, la sesión se vaciará automáticamente antes de que se ejecuten los criterios.

EDITAR: e importante! Al leer el código que ha mostrado, no parece que esté utilizando una transacción para su unidad de trabajo. Recomendaría envolver su unidad de trabajo en una transacción, que se requiere para que FlushMode.Auto funcione si está utilizando NH2.0 +.

Lea más aquí: NHibernate ISession Flush : ¿Dónde y cuándo usarlo y por qué?

Otros consejos

Si desea que la nueva etiqueta esté en la base de datos cuando la verifica cada vez que necesita confirmar la transacción después de guardar para colocarla allí.

Otro enfoque sería leer las etiquetas en una colección antes de procesar las fotos. Luego, como dijiste, buscarías localmente y agregarías nuevas etiquetas según sea necesario. Cuando haya terminado con la carpeta, puede confirmar la sesión.

Debería publicar sus asignaciones ya que es posible que no haya interpretado su pregunta correctamente.

Esto es lo típico "bloquear algo que no está allí" problema. Ya lo enfrenté varias veces y todavía no tengo una solución simple para ello.

Estas son las opciones que conozco hasta ahora:

  • Optimista: tenga una restricción única en el nombre y deje que una de las sesiones se active. Entonces lo intentas de nuevo. Debe asegurarse de no terminar en un bucle infinito cuando se produce otro error.
  • Pesimista: cuando agrega una nueva etiqueta, bloquea toda la tabla de etiquetas con TSQL.
  • Bloqueo .NET: sincroniza los subprocesos utilizando bloqueos .NET. Esto solo funciona si sus transacciones paralelas están en el mismo proceso.
  • Crear etiquetas utilizando una sesión propia (ver más abajo)

Ejemplo:

public static Tag CreateTag(string name)
{
  try
  {
    using (ISession session = factors.CreateSession())
    {
      session.BeginTransaction();
      Tag existingTag = session.CreateCriteria(typeof(Tag)) /* .... */
      if (existingtag != null) return existingTag;
      {
        session.Save(new Tag(name));
      }
      session.Transaction.Commit();
    }
  }
  // catch the unique constraint exception you get
  catch (WhatEverException ex)
  {
    // try again
    return CreateTag(name);
  }
}

Esto parece simple, pero tiene algunos problemas. Siempre obtiene una etiqueta, ya sea existente o creada (y confirmada de inmediato). Pero la etiqueta que obtiene es de otra sesión, por lo que se separa de su sesión principal. Debe adjuntarlo a su sesión utilizando cascadas (que probablemente no desee) o actualizar.

Crear etiquetas ya no está acoplado a su transacción principal, este era el objetivo, pero también significa que deshacer su transacción deja todas las etiquetas creadas en la base de datos. En otras palabras: crear etiquetas ya no forma parte de su transacción.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top