9
votes

Critères NHibernate: 'Sélectionnez Max (ID) ...'

Puis-je utiliser un critère pour exécuter une commande T-SQL pour sélectionner la valeur max pour une colonne dans une table?

'Sélectionnez @CUS_ID = Max (ID) + 1 des clients

ta

Ollie


0 commentaires

3 Réponses :


15
votes

max (id) + 1 est un très mauvais moyen de générer des identifiants. Si c'est votre objectif, trouvez une autre façon de générer des identifiants.

Edit: En réponse à LNDCOBRA:

C'est mauvais parce qu'il est difficile de s'assurer que le max (ID) que vous avez est toujours le max (ID) lorsque vous effectuez l'insert. Si un autre processus insère une ligne, votre insert aura le même ID et votre insert échouera. (Ou, à l'inverse, l'insert de l'autre processus échouera si votre insert s'est produit en premier.)

Pour éviter cela, vous devez empêcher tout autre inserts / faire votre get et votre insertion ultérieure atomique, ce qui signifie généralement verrouiller la table, ce qui nuira aux performances.

Si vous verrouillez uniquement contre les écrivies, l'autre processus obtient max (ID), qui est le même max (ID) que vous avez obtenu. Vous faites votre insertion et relâchez la serrure, elle insère un identifiant en double et échoue. Ou il essaie de verrouiller aussi, auquel cas il vous attend sur vous. Si vous vous serrez également contre des lectures, tout le monde vous attend. Si elle se verrouille également contre les écrivies, elle n'entre pas l'identifiant en double, mais cela attend votre lecture et votre écriture.

(et il brise l'encapsulation: vous devez laisser les SGBDM à déterminer ses identifiants, pas les programmes clients qui se connectent.)

En règle générale, cette stratégie sera soit:
* casser
* nécessite un tas de code "plomberie" pour le faire fonctionner
* Réduire de manière significative les performances de
* ou tous les trois

Et il sera plus lent, moins robuste et nécessitera plus de difficulté à gérer le code que d'utiliser uniquement les séquences intégrées de la SGBMS ou des identifiants automobiles générés.


1 commentaires

Quelqu'un peut-il me signaler dans les bonnes directions pourquoi ne pas utiliser cela pour générer des identifiants, et si oui une meilleure façon ...?



24
votes

Utilisez Projection :

session.CreateCriteria(typeof(Customer))
  .SetProjection( Projections.Max("Id") )
  . UniqueResult();


2 commentaires

Quel est le type de retour de cette expression?


Un objet. Vous pouvez utiliser la surcharge omeresult pour le jeter à un type spécifique. Dans ce cas, vous voudrez que Uniceresult () de le jeter à un entier.



0
votes

La meilleure approche consiste à faire une table supplémentaire de séquences. Où vous pouvez maintenir la cible de séquence et la valeur.

public class Sequence : Entity
{

    public virtual long? OwnerId { get; set; }

    public virtual SequenceTarget SequenceTarget { get; set; }

    public virtual bool IsLocked { get; set; }

    public virtual long Value { get; set; }

    public void GenerateNextValue()
    {
        Value++;
    }

}

public class SequenceTarget : Entity
{

    public virtual string Name { get; set; }

}

public long GetNewSequenceValueForZZZZ(long ZZZZId)
{
    var target =
        Session
        .QueryOver<SequenceTarget>()
        .Where(st => st.Name == "DocNumber")
        .SingleOrDefault();

    if (target == null)
    {
        throw new EntityNotFoundException(typeof(SequenceTarget));
    }

    return GetNewSequenceValue(ZZZZId, target);
}

protected long GetNewSequenceValue(long? ownerId, SequenceTarget target)
{
    var seqQry =
       Session
       .QueryOver<Sequence>()
       .Where(seq => seq.SequenceTarget == target);
    if (ownerId.HasValue)
    {
       seqQry.Where(seq => seq.OwnerId == ownerId.Value);
    }

    var sequence = seqQry.SingleOrDefault();

    if (sequence == null)
    {
       throw new EntityNotFoundException(typeof(Sequence));
    }

    // re-read sequence, if it was in session
    Session.Refresh(sequence);

    // update IsLocked field, so we acuire lock on record
    // configure dynamic update , so only 1 field is being updated
    sequence.IsLocked = !sequence.IsLocked;
    Session.Update(sequence);
    // force update to db
    Session.Flush();
    // now we gained block - re-read record.
    Session.Refresh(sequence);

    // generate new value
    sequence.GenerateNextValue();
    // set back dummy filed
    sequence.IsLocked = !sequence.IsLocked;
    // update sequence & force changes to DB
    Session.Update(sequence);
    Session.Flush();

    return sequence.Value;
}


0 commentaires