通过记录对象的新实例更新TableServiceContext中的记录?

时间:2012-03-07 14:10:16

标签: c# azure wcf-data-services azure-table-storage tableservicescontext

我有一个定义为

的天蓝色表记录对象
[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方法1}}抱怨它不是。

我哪里出错了?

明白了

好的,在ilspy的帮助下,我找到了问题的根本原因。 DataServiceContext为加载到上下文的实体维护两个字典。一个字典的键是实体本身,另一个字典的键是实体id,它实质上是实体url。在AttachTo方法中,如果在其中任何一个中找到条目,则上下文将验证字典和引发InvalidOperationException。但是UpdateObject方法只验证字符,其中key是实体本身,如果没有找到则会失败。

似乎DataServiceContext假定只能对同一个实体进行修改,默认情况下它不支持实体将被新实例整体替换。但逻辑使用带有默认比较器的标准字典类,因此在为TableRecord实现IEquatable接口后,一切都运行良好。

所以对我来说解决方案是:

[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 个答案:

答案 0 :(得分:2)

对此的解决方案是,如果有现有实体,则需要Detach()它和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;
        }

要使用此功能,您可以通过调用SafeAttach(storeRec)来替换try / catch块。

请注意,此方法会破坏表存储的内置并发检查。你基本上得到了最后写赢的行为。这可能会也可能不会被接受,这取决于您的情况。

此外,如果您计划继续工作,则可能需要设置MergeOption.NoTracking。无论如何,您基本上都在模仿该行为,并且禁用实体跟踪会有一些性能优势。

答案 1 :(得分:0)