Критерии Nhibernate: "выберите максимум (id)..."

Можно ли использовать критерии для выполнения команды t-sql, чтобы выбрать максимальное значение для столбца в таблице?

'выберите @cus_id = max(id) + 1 из клиентов "

Ta

Олли

3 ответа

Решение

Использовать проекцию:

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

Макс (id) + 1 - очень плохой способ генерировать идентификаторы. Если это ваша цель, найдите другой способ создания идентификаторов.

Изменить: в ответ на LnDCobra:

это плохо, потому что трудно убедиться, что максимальный (id), который вы получили, все еще остается максимальным (id), когда вы делаете вставку. Если другой процесс вставит строку, ваша вставка будет иметь такой же идентификатор, и ваша вставка не удастся. (Или, наоборот, вставка другого процесса завершится неудачно, если ваша вставка произошла первой.)

Чтобы предотвратить это, вы должны запретить любые другие вставки / делать ваши get и последующие вставки атомарными, что обычно означает блокировку таблицы, что снизит производительность.

Если вы блокируете только записи, другой процесс получает max(id), который совпадает с max(id), который вы получили. Вы делаете вставку и снимаете блокировку, она вставляет дубликат идентификатора и завершается ошибкой. Или он тоже пытается заблокировать, и в этом случае он ждет вас. Если вы заблокируете чтение, все будут ждать вас. Если он также блокирует запись, то он не вставляет дубликат идентификатора, но ожидает вашего чтения и записи.

(И это нарушает инкапсуляцию: вы должны позволить rdbms определить свои идентификаторы, а не клиентские программы, которые к нему подключаются.)

Как правило, эта стратегия будет либо:
* перерыв
* требуется куча "слесарного" кода, чтобы он работал
* значительно снизить производительность
* или все три

и он будет медленнее, менее надежным и потребует более сложного сопровождения кода, чем просто использование встроенных в СУБД последовательностей или сгенерированных идентификаторов автоинкремента.

Наилучшим подходом является создание дополнительной таблицы последовательностей. Где вы можете поддерживать последовательность цели и значение.

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

OwnerId - когда вам нужно поддерживать разные последовательности для одной и той же сущности, основываясь на каком-то владельце. Например, вам нужно сохранить нумерацию для документа в контракте, затем OwnerId will be = contractId

Другие вопросы по тегам