我有一个定义为
的天蓝色表记录对象[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();
}
}
答案 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)