2012-04-24 69 views
6

我已經實現了RavenDB Denormalized Reference模式。我正努力將靜態索引和所需的修補程序更新請求連接在一起,以確保在更改引用的實例值時更新非規範化的引用屬性值。RavenDb:更新非規範化的引用屬性值

這裏是我的域名:

public class User 
{ 
    public string UserName { get; set; } 
    public string Id { get; set; } 
    public string Email { get; set; } 
    public string Password { get; set; } 
} 

public class UserReference 
{ 
    public string Id { get; set; } 
    public string UserName { get; set; } 

    public static implicit operator UserReference(User user) 
    { 
     return new UserReference 
       { 
         Id = user.Id, 
         UserName = user.UserName 
       }; 
    } 
} 

public class Relationship 
{ 
    public string Id { get; set; } 
    public UserReference Mentor { get; set; } 
    public UserReference Mentee { get; set; } 
} 

你可以看到UserReference包含ID和引用用戶的用戶名。所以,現在,如果我更新給定用戶實例的用戶名,那麼我希望所有用戶引用中的引用用戶名值也要更新。爲了實現這一點,我寫了一個靜態指標和修補程序要求如下:

public class Relationships_ByMentorId : AbstractIndexCreationTask<Relationship> 
{ 
    public Relationships_ByMentorId() 
    { 
     Map = relationships => from relationship in relationships 
           select new {MentorId = relationship.Mentor.Id}; 
    } 
} 

public static void SetUserName(IDocumentSession db, User mentor, string userName) 
{ 
    mentor.UserName = userName; 
    db.Store(mentor); 
    db.SaveChanges(); 

    const string indexName = "Relationships/ByMentorId"; 
    RavenSessionProvider.UpdateByIndex(indexName, 
     new IndexQuery 
     { 
       Query = string.Format("MentorId:{0}", mentor.Id) 
     }, 
     new[] 
     { 
       new PatchRequest 
       { 
         Type = PatchCommandType.Modify, 
         Name = "Mentor", 
         Nested = new[] 
           { 
             new PatchRequest 
             { 
               Type = PatchCommandType.Set, 
               Name = "UserName", 
               Value = userName 
             }, 
           } 
       } 
     }, 
     allowStale: false); 
} 

最後一個單元測試,因爲預期的更新不工作失敗。

[Fact] 
public void Should_update_denormalized_reference_when_mentor_username_is_changed() 
{ 
    using (var db = Fake.Db()) 
    { 
     const string userName = "updated-mentor-username"; 
     var mentor = Fake.Mentor(db); 
     var mentee = Fake.Mentee(db); 
     var relationship = Fake.Relationship(mentor, mentee, db); 
     db.Store(mentor); 
     db.Store(mentee); 
     db.Store(relationship); 
     db.SaveChanges(); 

     MentorService.SetUserName(db, mentor, userName); 

     relationship = db 
      .Include("Mentor.Id") 
      .Load<Relationship>(relationship.Id); 

     relationship.ShouldNotBe(null); 
     relationship.Mentor.ShouldNotBe(null); 
     relationship.Mentor.Id.ShouldBe(mentor.Id); 
     relationship.Mentor.UserName.ShouldBe(userName); 

     mentor = db.Load<User>(mentor.Id); 
     mentor.ShouldNotBe(null); 
     mentor.UserName.ShouldBe(userName); 
    } 
} 

一切都正常運行,該指數是有,但我懷疑這是不是返回由補丁請求所需要的關係,但說實話,我已經江郎才盡了。你能幫忙嗎?

編輯1個

@MattWarren allowStale=true沒有幫助。但是我注意到了一個潛在的線索。

因爲這是一個單元測試,所以我使用了InMemory,嵌入了IDocumentSession - 上面代碼中的Fake.Db()。然而,當調用靜態索引時,即在執行UpdateByIndex(...)時,它使用通用IDocumentStore,而不是特定的假IDocumentSession。

當我更改我的索引定義類,然後運行我的單元測試時,索引在「真實」數據庫中更新,可以通過Raven Studio查看更改。但是,「保存」到InMemory數據庫中的虛假域實例(mentor,mentee等)並未存儲在實際數據庫中(如預期的那樣),因此無法通過Raven Studio查看。

難道是我對UpdateByIndex(...)的調用針對不正確的IDocumentSession,'真正'的一個(沒有保存的域實例),而不是假的?

編輯2 - @Simon

我已經實現了你的修爲在編輯上述1中列出的問題,我認爲我們正在取得進展。你是對的,我通過RavenSessionProvder使用了對IDocumentStore的靜態引用。現在情況並非如此。下面的代碼已更新爲使用Fake.Db()

public static void SetUserName(IDocumentSession db, User mentor, string userName) 
{ 
    mentor.UserName = userName; 
    db.Store(mentor); 
    db.SaveChanges(); 

    const string indexName = "Relationships/ByMentorId"; 
    db.Advanced.DatabaseCommands.UpdateByIndex(indexName, 
             new IndexQuery 
             { 
               Query = string.Format("MentorId:{0}", mentor.Id) 
             }, 
             new[] 
             { 
               new PatchRequest 
               { 
                 Type = PatchCommandType.Modify, 
                 Name = "Mentor", 
                 Nested = new[] 
                   { 
                     new PatchRequest 
                     { 
                       Type = PatchCommandType.Set, 
                       Name = "UserName", 
                       Value = userName 
                     }, 
                   } 
               } 
             }, 
             allowStale: false); 
} 
} 

你會注意到我還重置了allowStale=false。現在,當我運行此我得到以下錯誤:

Bulk operation cancelled because the index is stale and allowStale is false 

我想我們已經解決了第一個問題,現在我使用的是正確的Fake.Db,我們遇到的問題首先強調,該指數是陳舊,因爲我們在單元測試中運行速度超快。

現在的問題是:如何讓UpdateByIndex(..)方法等待,直到命令-Q爲空並且索引被視爲「新鮮」?

public static void SetUserName(IDocumentSession db, User mentor, string userName) 
{ 
    mentor.UserName = userName; 
    db.Store(mentor); 
    db.SaveChanges(); 

    const string indexName = "Relationships/ByMentorId"; 

    // 1. This forces the index to be non-stale 
    var dummy = db.Query<Relationship>(indexName) 
      .Customize(x => x.WaitForNonStaleResultsAsOfLastWrite()) 
      .ToArray(); 

    //2. This tests the index to ensure it is returning the correct instance 
    var query = new IndexQuery {Query = "MentorId:" + mentor.Id}; 
    var queryResults = db.Advanced.DatabaseCommands.Query(indexName, query, null).Results.ToArray(); 

    //3. This appears to do nothing 
    db.Advanced.DatabaseCommands.UpdateByIndex(indexName, query, 
     new[] 
     { 
       new PatchRequest 
       { 
         Type = PatchCommandType.Modify, 
         Name = "Mentor", 
         Nested = new[] 
           { 
             new PatchRequest 
             { 
               Type = PatchCommandType.Set, 
               Name = "UserName", 
               Value = userName 
             }, 
           } 
       } 
     }, 
     allowStale: false); 
} 

從編號的註釋以上:

  1. 編輯3

    考慮到用於防止陳舊索引的建議,如下我已經更新了代碼

    加入一個虛擬查詢強制索引等待,直到它是非陳舊的作品。有關陳舊索引的錯誤被消除。

  2. 這是一個測試線,以確保我的索引正常工作。它似乎很好。返回的結果是提供的Mentor.Id('users-1')的正確關係實例。

    { 「學長」:{ 「ID」: 「用戶-1」, 「用戶名」: 「導師先生」 }, 「受指導者」:{ 「ID」:「用戶-2 」 ‘用戶名’:‘受導先生’ } ... }

  3. 儘管指數爲未失效的,看似正常,實際修補請求看似什麼都不做。非導師非規範化參考中的UserName保持不變。

因此,懷疑現在落在補丁請求本身。爲什麼這不起作用?這可能是我設置UserName屬性值進行更新的方式嗎?

... 
new PatchRequest 
{ 
     Type = PatchCommandType.Set, 
     Name = "UserName", 
     Value = userName 
} 
... 

你會注意到,我只是分配userName PARAM直奔Value屬性,這是RavenJToken類型的字符串值。這可能是一個問題嗎?

編輯4

太棒了!我們有一個解決方案。我重寫了我的代碼,以允許您提供的所有新信息(謝謝)。萬一有人實際上已經讀到這裏,我更好地投入工作代碼給他們關閉:

單元測試

[Fact] 
public void Should_update_denormalized_reference_when_mentor_username_is_changed() 
{ 
    const string userName = "updated-mentor-username"; 
    string mentorId; 
    string menteeId; 
    string relationshipId; 

    using (var db = Fake.Db()) 
    { 
     mentorId = Fake.Mentor(db).Id; 
     menteeId = Fake.Mentee(db).Id; 
     relationshipId = Fake.Relationship(db, mentorId, menteeId).Id; 
     MentorService.SetUserName(db, mentorId, userName); 
    } 

    using (var db = Fake.Db(deleteAllDocuments:false)) 
    { 
     var relationship = db 
       .Include("Mentor.Id") 
       .Load<Relationship>(relationshipId); 

     relationship.ShouldNotBe(null); 
     relationship.Mentor.ShouldNotBe(null); 
     relationship.Mentor.Id.ShouldBe(mentorId); 
     relationship.Mentor.UserName.ShouldBe(userName); 

     var mentor = db.Load<User>(mentorId); 
     mentor.ShouldNotBe(null); 
     mentor.UserName.ShouldBe(userName); 
    } 
} 

假貨

public static IDocumentSession Db(bool deleteAllDocuments = true) 
{ 
    var db = InMemoryRavenSessionProvider.GetSession(); 
    if (deleteAllDocuments) 
    { 
     db.Advanced.DatabaseCommands.DeleteByIndex("AllDocuments", new IndexQuery(), true); 
    } 
    return db; 
} 

public static User Mentor(IDocumentSession db = null) 
{ 
    var mentor = MentorService.NewMentor("Mr. Mentor", "[email protected]", "pwd-mentor"); 
    if (db != null) 
    { 
     db.Store(mentor); 
     db.SaveChanges(); 
    } 
    return mentor; 
} 

public static User Mentee(IDocumentSession db = null) 
{ 
    var mentee = MenteeService.NewMentee("Mr. Mentee", "[email protected]", "pwd-mentee"); 
    if (db != null) 
    { 
     db.Store(mentee); 
     db.SaveChanges(); 
    } 
    return mentee; 
} 


public static Relationship Relationship(IDocumentSession db, string mentorId, string menteeId) 
{ 
    var relationship = RelationshipService.CreateRelationship(db.Load<User>(mentorId), db.Load<User>(menteeId)); 
    db.Store(relationship); 
    db.SaveChanges(); 
    return relationship; 
} 

用於單元測試的Raven會話提供程序

public class InMemoryRavenSessionProvider : IRavenSessionProvider 
{ 
    private static IDocumentStore documentStore; 

    public static IDocumentStore DocumentStore { get { return (documentStore ?? (documentStore = CreateDocumentStore())); } } 

    private static IDocumentStore CreateDocumentStore() 
    { 
     var store = new EmbeddableDocumentStore 
      { 
       RunInMemory = true, 
       Conventions = new DocumentConvention 
        { 
          DefaultQueryingConsistency = ConsistencyOptions.QueryYourWrites, 
          IdentityPartsSeparator = "-" 
        } 
      }; 
     store.Initialize(); 
     IndexCreation.CreateIndexes(typeof (RavenIndexes).Assembly, store); 
     return store; 
    } 

    public IDocumentSession GetSession() 
    { 
     return DocumentStore.OpenSession(); 
    } 
} 

的指標

public class RavenIndexes 
{ 
    public class Relationships_ByMentorId : AbstractIndexCreationTask<Relationship> 
    { 
     public Relationships_ByMentorId() 
     { 
      Map = relationships => from relationship in relationships 
            select new { Mentor_Id = relationship.Mentor.Id }; 
     } 
    } 

    public class AllDocuments : AbstractIndexCreationTask<Relationship> 
    { 
     public AllDocuments() 
     { 
      Map = documents => documents.Select(entity => new {}); 
     } 
    } 
} 

更新規格化參考

public static void SetUserName(IDocumentSession db, string mentorId, string userName) 
{ 
    var mentor = db.Load<User>(mentorId); 
    mentor.UserName = userName; 
    db.Store(mentor); 
    db.SaveChanges(); 

    //Don't want this is production code 
    db.Query<Relationship>(indexGetRelationshipsByMentorId) 
      .Customize(x => x.WaitForNonStaleResultsAsOfLastWrite()) 
      .ToArray(); 

    db.Advanced.DatabaseCommands.UpdateByIndex(
      indexGetRelationshipsByMentorId, 
      GetQuery(mentorId), 
      GetPatch(userName), 
      allowStale: false 
      ); 
} 

private static IndexQuery GetQuery(string mentorId) 
{ 
    return new IndexQuery {Query = "Mentor_Id:" + mentorId}; 
} 

private static PatchRequest[] GetPatch(string userName) 
{ 
    return new[] 
      { 
        new PatchRequest 
        { 
          Type = PatchCommandType.Modify, 
          Name = "Mentor", 
          Nested = new[] 
            { 
              new PatchRequest 
              { 
                Type = PatchCommandType.Set, 
                Name = "UserName", 
                Value = userName 
              }, 
            } 
        } 
      }; 
} 
+0

在打電話之前,UpdateByIndex,把一些像這樣的代碼'db.Query (INDEXNAME).Customize(X => x.WaitForNonStaleResultsAsOfNow())。ToList() – 2012-04-24 20:42:30

+0

這將接踵而至,該指數是之前未失效您嘗試使用它進行修補。 – 2012-04-24 20:44:06

+0

@MattWarren請參閱編輯3 – biofractal 2012-04-25 08:54:29

回答

4

試着改變你的線路:

RavenSessionProvider.UpdateByIndex(indexName, //etc 

db.Advanced.DatabaseCommands.UpdateByIndex(indexName, //etc 

這將確保更新命令在單元測試中使用的相同(假)文檔存儲上發出。

答案編輯2:

有沒有自動的方式使用UpdateByIndex時等待未失效的結果。你有兩個選擇您的SetUserName方法:

1 - 改變你的數據存儲始終更新索引立即像這樣(注意:這可能會影響性能):

store.Conventions.DefaultQueryingConsistency = ConsistencyOptions.MonotonicRead; 

2 - 運行鍼對查詢索引,只是UpdateByIndex調用之前,指定WaitForNonStaleResults選項:

var dummy = session.Query<Relationship>("Relationships_ByMentorId") 
.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite()) 
.ToArray(); 

3 - 捕獲異常拋出時的指數是陳舊的,做一個Thread.Sleep(100),然後重試。

答案編輯3:

我終於想通了,並有一個測試通過......不能相信它,但它似乎僅僅是一個緩存的問題。當您重新加載文檔以進行斷言時,您需要使用不同的會話...例如,

using (var db = Fake.Db()) 
{ 
    const string userName = "updated-mentor-username"; 
    var mentor = Fake.Mentor(db); 
    var mentee = Fake.Mentee(db); 
    var relationship = Fake.Relationship(mentor, mentee, db); 
    db.Store(mentor); 
    db.Store(mentee); 
    db.Store(relationship); 
    db.SaveChanges(); 

    MentorService.SetUserName(db, mentor, userName); 
} 

using (var db = Fake.Db()) 
{ 
    relationship = db 
     .Include("Mentor.Id") 
     .Load<Relationship>(relationship.Id); 
    //etc... 
} 

難以置信,我沒有更早發現這一點,對不起。

+1

加載始終是最新的,它不使用索引。然而,在'MentorService.SetUserName(db,mentor,userName)'內部放置'WaitForNonStaleResults(..)'可能會有所幫助,因爲它會使用索引。 – 2012-04-24 13:13:21

+0

其實我不認爲這是問題,因爲你沒有加載你正在更新的索引中的數據。如果你同意,投票刪除,我會刪除這個答案。 – Simon 2012-04-24 13:14:52

+0

@MattWarren是的,只是發現,感謝馬特 - 我認爲你有正確的答案。該補丁無法在索引中找到新保存的用戶,因此無法修補。至少這聽起來對我來說是正確的! – Simon 2012-04-24 13:15:42