Как сравнить значения LSN CDC SQL Server в C #? - PullRequest
2 голосов
/ 29 марта 2011

В SQL все просто, поскольку он поддерживает двоичные (10) значения LSN для сравнения:

SELECT *, __$start_lsn, __$seqval
FROM cdc.fn_cdc_get_all_changes_dbo_sometable(@startLsn, @endLsn, 'all update old') 
WHERE __$seqval > @seqval 
ORDER BY __$start_lsn, __$seqval

В C # это сложнее:

byte[] mySeqval = ...
foreach(var row in cdcData)
{
    if(row.seqval > mySeqval) // Cannot perform this
        ...
}

Могут ли значения LSN / SeqValпревратить в число, которое можно легко сравнить?Это 10 байтов (80 бит) в размере.

Мой проект в .Net 3.5

Ответы [ 4 ]

2 голосов
/ 12 мая 2015

Я был удивлен, что нигде не нашел приличного ответа на эту проблему, когда просматривал последние несколько недель.

Основная проблема с номерами LSN состоит в том, что они имеют размер 10 байт, поэтому их нельзя просто преобразовать в Int64 и сравнить (кроме того: вы действительно сгенерируете такое количество номеров LSN?! Int64 действительно большой). И как обнаружил OP, сравнение байтов один за другим немного болезненно / подвержено ошибкам (сравнение на равенство - это хорошо - сравнение на большее / меньшее, чем меньшее). Однако в .Net Framework 4 у нас есть класс BigInteger, который можно использовать для простого сравнения целых чисел, превышающих 8 байтов.

Так что проблема в том, как получить varbinary (10) из LSN в BigInteger. Из проверки [1] видно, что SQL хранит LSN в формате с прямым порядком байтов, поэтому вам необходимо:

  • получить varbinary(10) в память. LinqToSql выдаст вам Binary, другие провайдеры будут напрямую сопоставлены с байтом [].
  • перевернуть байты, если вы используете архитектуру с прямым порядком байтов (подсказка: вы есть). IEnumerable.Reverse().ToArray() сделает это, если вы не хотите делать обратный цикл самостоятельно
  • звоните new BigInteger(bytes)
  • сравните значения на досуге

Это может выглядеть примерно так:

// https://gist.github.com/piers7/91141f39715a2ec133e5
// Example of how to interpret SQL server CDC LSNs in C# / .Net
// This is required when polling a server for updates in order to determine
// if a previously stored LSN is still valid (ie > min LSN available)

// Requires .Net 4 (or equivilent BigInteger implementation)
// Sample is a Linqpad script, but you get the idea

// NB: That SQL uses big-endian representation for it's LSNs is not
// (as best I know) something they guarantee not to change

Connection.Open();
var command = Connection.CreateCommand();
command.CommandText = @"select sys.fn_cdc_get_max_lsn() as maxLsn";
var bytes = (byte[])command.ExecuteScalar();

// dump bytes as hex
var hexString = string.Join(" ", bytes.Select(b => b.ToString("X2")))
    .Dump("Hex String");

if(BitConverter.IsLittleEndian)
    bytes = bytes.Reverse().ToArray();

var bigInt = new BigInteger(bytes)
    // dump Integer representation
    .Dump("Big Integer")
;

[1] Я внес последовательные изменения и посмотрел на номера LSN. Последний байт был явно инкрементным, следовательно, big-endian.

1 голос
/ 30 марта 2011

В конце написал мой собственный LSN-компаратор:

public class CdcLsnValue : IEquatable<CdcLsnValue>
{
    public byte[] Bytes;
    private const int Size = 10;

    public CdcLsnValue()
    {
        Bytes = null;
    }

    public CdcLsnValue(byte[] lsn)
    {
        if (lsn == null)
        {
            Bytes = null;
            return;
        }
        if(lsn.Length != Size)
            throw new ArgumentOutOfRangeException("lsn");
        Bytes = (byte[]) lsn.Clone();
    }

    public static bool operator ==(CdcLsnValue left, CdcLsnValue right)
    {
        if (ReferenceEquals(left, right)) return true;
        if (ReferenceEquals(null, left)) return false;
        if (ReferenceEquals(null, right)) return false;

        for (int i = 0; i < Size; i++)
        {
            if (left.Bytes[i] == right.Bytes[i])
                continue;
            return false;
        }
        return true;

    }

    public static bool operator !=(CdcLsnValue left, CdcLsnValue right)
    {
        return !(left == right);
    }

    public static bool operator <=(CdcLsnValue left, CdcLsnValue right)
    {
        if (ReferenceEquals(null, left)) return false;
        if (ReferenceEquals(null, right)) return false;

        for (int i = 0; i < Size; i++)
        {
            if (left.Bytes[i] <= right.Bytes[i])
                continue;
            return false;
        }
        return true;
    }

    public static bool operator >=(CdcLsnValue left, CdcLsnValue right)
    {
        if (ReferenceEquals(null, left)) return false;
        if (ReferenceEquals(null, right)) return false;

        for (int i = 0; i < Size; i++)
        {
            if (left.Bytes[i] >= right.Bytes[i])
                continue;
            return false;
        }
        return true;
    }

    public static bool operator <(CdcLsnValue left, CdcLsnValue right)
    {
        if (ReferenceEquals(null, left)) return false;
        if (ReferenceEquals(null, right)) return false;

        if (left == right)
            return false;

        return left <= right;
    }

    public static bool operator >(CdcLsnValue left, CdcLsnValue right)
    {
        return !(left < right);
    }

    public bool Equals(CdcLsnValue other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other.Bytes, Bytes);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof(CdcLsnValue)) return false;
        return Equals((CdcLsnValue)obj);
    }

    public override int GetHashCode()
    {
        return (Bytes != null ? Bytes.GetHashCode() : 0);
    }
}
1 голос
/ 30 марта 2011

В настоящее время исследуется http://intx.codeplex.com/ как его .Net 2.0

0 голосов
/ 12 апреля 2011

В конце не нужно использовать ничего из вышеперечисленного.В конце концов, мои коллеги решили проблему (спасибо Тони Броди).Способ сделать это - сравнить с seqval, затем взять + 1.Simples.

SqlExecutor.ExecuteReader(cnn,
string.Format("SELECT {0} , __$start_lsn, __$seqval , __$update_mask " +
    "FROM cdc.fn_cdc_get_all_changes_{1}(@startLsn,@endLsn,'all update old') cdc {2} " +
    "where __$operation = {3} ORDER BY __$start_lsn, __$seqval", columns,
    captureInstance, joins, (int)operation), 
    reader =>
    {
        if (reader != null)
            items.Add(createEntity(reader));
    }, 5, 60, new SqlParameter("@startLsn", lsn), 
              new SqlParameter("@endLsn", endLsn));
});
startLsn = lsn;
seqVal = sequence;
var startIndex = sequence == null ? 0 : 
  items.FindIndex(0, item => item.Lsn.SequenceEqual(lsn)
    && item.Seqval.SequenceEqual(sequence)) + 1; // <---- Look here. See the +1
return items.Skip(startIndex).ToList();
...