Обновить запись в TableServiceContext новым экземпляром объекта записи? - PullRequest
1 голос
/ 07 марта 2012

У меня есть объект записи таблицы Azure, определенный как

[DataServiceKey("PartitionKey", "RowKey")]
public class TableRecord
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public DateTime Timestamp { get; set; }
    public string Data { get; set; }
}

Запись используется как часть инфраструктуры хранилища, которая принимает объект данных уровня бизнес-логики и сериализует его в свойство Data перед сохранением кода в хранилище таблиц, а также десериализует его перед возвратом клиенту, поэтому бизнес-логика ничего не знает о записи, а также о PartitionKey и RowKey.

Вот метод репозитория

public TEntity RegisterSave<TEntity>(TEntity entity, bool createNew)
{
    var storeRec = _strategy.GetStoreRecord(entity);
    if (createNew)
        _context.AddObject(storeRec.TableName, storeRec.Record);
    else
    {
        try
        {
            _context.AttachTo(storeRec.TableName, storeRec.Record, "*");                    
        }
        catch (InvalidOperationException)
        {
            // AttachTo can throw an exception if the entity is already being tracked.
            // Ignore
        }

        _context.UpdateObject(storeRec.Record);
    }
    return entity;
}

_strategy отвечает за правильное отображение типа объекта на имя таблицы, а также за правильное создание TableRecord с ключами и сериализацию объекта в запись. Свойство storeRec.Record является экземпляром класса TableRecord. Этот подход хорошо работает для создания новой записи и чтения записи.

Но когда я пытаюсь обновить существующую запись новыми данными, обновление завершается неудачно, сообщая, что не может обновить сущность, которая не отслеживается контекстом. Хотя, если пройтись по коду в отладчике, выясняется, что на самом деле происходит два исключения - сначала в методе AttachTo, который отслеживает сущность с тем же ключом, и сразу после этого жалуется UpdateObject об этом нет.

Где я ошибся?

Понял

Хорошо, с небольшой помощью ilspy я нашел основную причину проблемы. DataServiceContext поддерживает два словаря для сущностей, загруженных в контекст. Ключи одного словаря - это сама сущность, а ключом другого - идентификатор сущности, который по сути является URL-адресом сущности. В методе AttachTo контекст проверяет оба словаря и выдает InvalidOperationException, если запись найдена в любом из них. Но метод UpdateObject проверяет только словарь, в котором ключ является самой сущностью, и завершается ошибкой, если не найден.

Похоже, DataServiceContext предполагает, что изменения могут быть выполнены только для одной и той же сущности, по умолчанию он не поддерживает замену этой сущности новым экземпляром в целом. Но логика использует стандартный словарный класс с компаратором по умолчанию, поэтому после реализации интерфейса IEquatable для TableRecord все работало отлично.

Так что для меня решение было:

[DataServiceKey("PartitionKey", "RowKey")]
public class TableRecord: IEquatable<TableRecord>
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public DateTime Timestamp { get; set; }
    public string Data { get; set; }

    public bool Equals(TableRecord other)
    {
        if (other == null)
            return false;
        return PartitionKey.Equals(other.PartitionKey) && RowKey.Equals(other.RowKey);
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as TableRecord);
    }

    public override int GetHashCode()
    {
        return PartitionKey.GetHashCode() ^ RowKey.GetHashCode();
    }
}

Ответы [ 2 ]

2 голосов
/ 07 марта 2012

Решение этой проблемы заключается в том, что если существует существующий объект, вам необходимо отсоединить его () и AttachTo () от нового. Затем делайте обновления, которые вы хотите сделать.

Я написал код, который делает это. Это также позволяет избежать исключения, хотя я точно не знаю, какой метод быстрее.

        /// <summary>
        /// Detach any existing rows with the same keys (if necessary), then attach to this object using the "*" ETag
        /// </summary>
        /// <param name="newEntity"></param>
        protected virtual void SafeAttach(TableServiceEntity newEntity)
        {
            TableServiceEntity entity = GetExistingRow(newEntity.PartitionKey, newEntity.RowKey);
            if(entity != null)
            {
                base.Detach(entity);
            }

            base.AttachTo("MY_TABLE_NAME_GOES_HERE", newEntity, "*");
        }

        private TableServiceEntity GetExistingRow(string partitionKey, string rowKey)
        {
            var query = (from e in base.Entities
                         where e.Entity is TableServiceEntity
                         && ((TableServiceEntity)e.Entity).RowKey == rowKey
                         && ((TableServiceEntity)e.Entity).PartitionKey == partitionKey
                         select (TableServiceEntity)e.Entity);

            RetrierFunctionResult<TableServiceEntity> r = StorageOperationRetrier.Execute(() =>
            {
                return query.FirstOrDefault();
            });

            return r.Result;
        }

Чтобы использовать это, вы бы заменили свой блок try / catch на вызов SafeAttach(storeRec).

Обратите внимание, что при таком подходе отказывается от встроенной проверки параллелизма хранения таблиц. Вы в основном получаете поведение последней записи побед. Это может или не может быть приемлемым, это зависит от вашей ситуации.

Кроме того, если вы планируете продолжить работу, вы можете установить MergeOption.NoTracking . В любом случае вы в основном эмулируете это поведение, и есть некоторое преимущество в производительности для отключения отслеживания сущностей.

0 голосов
/ 07 марта 2012

Взгляните на функциональность Upsert, представленную в SDK v1.6.http://blogs.msdn.com/b/windowsazurestorage/archive/2011/09/15/windows-azure-tables-introducing-upsert-and-query-projection.aspx

...