1

我得定義爲通過記錄對象的新實例更新TableServiceContext中的記錄?

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

蔚藍表記錄對象的記錄被用作接受業務邏輯級數據對象,並將其序列化到數據屬性的代碼保存到前庫基礎設施的一部分表存儲以及在返回到客戶端之前對其進行反序列化,因此業務邏輯不知道關於記錄的任何內容以及有關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爲加載到上下文的實體維護兩個字典。一個字典的鍵是實體本身,另一個字典的鍵是實體ID,它實質上是實體URL。在AttachTo方法中,上下文驗證這兩個字典並拋出InvalidOperationException,如果在其中的任何一箇中都找到了條目。但UpdateObject方法僅驗證密鑰實體本身的字典,如果未找到則驗證失敗。

看來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

解決方案是,如果存在一個現有實體,則需要分離()它和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。無論如何,你基本上都在模擬這種行爲,並且禁用實體跟蹤有一些性能優勢。

+0

我只是利用了MergeOption.NotTracking,簡單得多 – knightpfhor 2012-03-07 20:19:05

+0

在我的方法中,表中有一個有效負載字段數據,其中有最新版本的對象序列化。所以最後寫贏的行爲就是我正在尋找的。但看起來我錯過了NoTracking選項背後的想法 - 它最適合這種情況。 – achekh 2012-03-12 18:15:42