RavenDb:更新非规范化引用属性值

时间:2012-04-24 13:01:32

标签: ravendb

我已经实现了RavenDB Denormalized Reference模式。我正在努力将静态索引和补丁更新请求连接在一起,以确保在更改引用的实例值时更新我的​​非规范化引用属性值。

这是我的域名:

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包含引用的User的Id和UserName。所以现在,如果我更新给定User实例的UserName,那么我希望所有UserReferences中引用的Username值也更新。为实现这一点,我编写了一个静态索引和一个补丁请求,如下所示:

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

最后一个UnitTest失败,因为更新没有按预期工作。

[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数据库中的伪域实例(mentormentee等)不会存储在实际数据库中(如预期的那样),因此无法通过Raven Studio查看。

可能是我对UpdateByIndex(...)的调用是针对错误的IDocumentSession运行的,“真正的”(没有保存的域实例),而不是假的?

编辑2 - @Simon

我已针对上面编辑1中列出的问题实施了修复,我认为我们正在取得进展。你是对的,我通过IDocumentStore使用了对RavenSessionProvder的静态引用。现在情况并非如此。以下代码已更新为使用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为空并且索引被认为是“新鲜”?

编辑3

考虑到防止陈旧索引的建议,我更新了代码如下:

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. 放入一个虚拟查询以强制索引等待,直到它是非陈旧的工作。消除了有关陈旧指数的错误。

  2. 这是确保我的索引正常工作的测试行。看起来很好。返回的结果是提供的Mentor.Id('users-1')的正确Relationship实例。

    {   “导师”: {     “Id”:“users-1”,     “UserName”:“Mentor先生”   },   “被指导者”:{     “Id”:“users-2”,     “UserName”:“Mentee先生”   }   ... }

  3. 尽管索引是非陈旧的并且看似正常运行,但实际的补丁请求似乎什么也没做。 Mentor的非规范化参考中的UserName保持不变。

  4. 所以怀疑现在落在补丁请求本身上。为什么这不起作用?这可能是我设置要更新的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", "mentor@email.com", "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", "mentee@email.com", "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
                                                },
                                        }
                        }
                };
    }
    

1 个答案:

答案 0 :(得分: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...
}

不敢相信我没有早点发现这个,抱歉。

相关问题