2012-04-24 49 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