Domanda

Ho una relazione molti-a-molti tra foto e tag: una foto può avere più tag e più foto possono condividere gli stessi tag.

Ho un ciclo che scansiona le foto in una directory e poi le aggiunge a NHibernate. Alcuni tag vengono aggiunti alle foto durante tale processo, ad es. un tag 2009 quando la foto è stata scattata nel 2009.

La classe Tag implementa Equals e GetHashCode e utilizza la proprietà Name come unica proprietà della firma. Sia Photo che Tag hanno chiavi surrogate e sono versionati.

Ho un codice simile al seguente:

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);
    }
}

Il mio problema è quando il tag non esiste, ad es. la prima foto di un nuovo anno. Il metodo AddDefaultTags verifica se il tag esiste nel database, quindi lo crea e lo aggiunge a NHibernate. Funziona benissimo quando si aggiunge una singola foto ma quando si importano più foto nel nuovo anno e all'interno della stessa unità di lavoro fallisce poiché non esiste ancora nel database e viene aggiunta di nuovo. Durante il completamento dell'unità di lavoro non riesce poiché tenta di aggiungere due voci nella tabella Tag con lo stesso nome ...

La mia domanda è come assicurarsi che NHibernate tenti di creare un singolo tag nel database nella situazione precedente. Devo mantenere un elenco di tag aggiunti di recente o posso impostare la mappatura in modo che funzioni?

È stato utile?

Soluzione

È necessario eseguire _session.Flush () se i criteri non devono restituire dati non aggiornati. Oppure dovresti essere in grado di farlo correttamente impostando _session.FlushMode su Auto.

Con FlushMode.Auto, la sessione verrà automaticamente cancellata prima dell'esecuzione dei criteri.

EDIT: E importante! Quando leggi il codice che hai mostrato, non sembra che tu stia utilizzando una transazione per la tua unità di lavoro. Consiglierei di racchiudere l'unità di lavoro in una transazione, necessaria per FlushMode.Auto per funzionare se si utilizza NH2.0 +!

Ulteriori informazioni qui: NHibernate ISession Flush : Dove e quando usarlo e perché?

Altri suggerimenti

Se si desidera che il nuovo tag sia nel database quando lo si controlla ogni volta che è necessario eseguire il commit della transazione dopo averla salvata per metterla lì.

Un altro approccio sarebbe quello di leggere i tag in una raccolta prima di elaborare le foto. Quindi, come hai detto, avresti cercato in locale e aggiunto nuovi tag secondo necessità. Al termine della cartella è possibile eseguire il commit della sessione.

Dovresti pubblicare i tuoi mapping poiché potrei non aver interpretato correttamente la tua domanda.

Questo è quel tipico "bloccare qualcosa che non c'è" " problema. L'ho affrontato già diverse volte e ancora non ho una soluzione semplice per questo.

Queste sono le opzioni che conosco fino ad ora:

  • Ottimista: avere un vincolo univoco sul nome e lasciare che una delle sessioni passi su commit. Quindi riprovare. Devi assicurarti di non finire in un ciclo infinito quando si verifica un altro errore.
  • Pessimistico: quando aggiungi un nuovo Tag, blocchi l'intera tabella Tag usando TSQL.
  • Blocco .NET: sincronizzi i thread usando i blocchi .NET. Funziona solo se le transazioni parallele sono nello stesso processo.
  • Crea tag usando una propria sessione (vedi sotto)

Esempio:

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);
  }
}

Sembra semplice, ma presenta alcuni problemi. Ottieni sempre un tag, esistente o creato (e impegnato immediatamente). Ma il tag che ricevi proviene da un'altra sessione, quindi è staccato per la sessione principale. È necessario collegarlo alla sessione utilizzando cascades (che probabilmente non si desidera) o aggiornare.

La creazione di tag non è più associata alla transazione principale, questo era l'obiettivo ma significa anche che il rollback della transazione lascia tutti i tag creati nel database. In altre parole: la creazione di tag non fa più parte della transazione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top