RavenDb:更新非规范化参考属性值 [英] RavenDb : Update a Denormalized Reference property value

查看:80
本文介绍了RavenDb:更新非规范化参考属性值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have implemented the RavenDB Denormalized Reference pattern. I am struggling to wire together the static index and the patch update request required to ensure that my denormalized reference property values are updated when a referenced instance value is changed.

这是我的域:

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

You can see that the UserReference contains the Id and the UserName of the referenced User. So now, if I update the UserName for a given User instance then I want the referenced Username value in all the UserReferences to update also. To achieve this I have written a static Index and a Patch Request as follows:

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.

And finally a UnitTest that fails because the update is not working as expected.

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

一切运行良好,索引在那里,但我怀疑这并没有返回补丁请求所需的关系,但老实说,我已经用完了人才.你能帮忙吗?

Everything runs fine, the index is there but I suspect this is not returning the relationships required by the patch request, but honestly, I have run out of talent. Can you help please?

编辑 1

@MattWarren allowStale=true 没有帮助.但是,我注意到了一个潜在的线索.

@MattWarren allowStale=true did not help. However I have noticed a potential clue.

因为这是一个单元测试,所以我使用的是 InMemory、嵌入的 IDocumentSession - 上面代码中的 Fake.Db().然而,当调用静态索引时,即在执行 UpdateByIndex(...) 时,它使用通用 IDocumentStore,而不是特定的假 IDocumentSession.

Because this is a unit test I am using an InMemory, embedded IDocumentSession - the Fake.Db() in the code above. Yet when an static index is invoked i.e. when doing the UpdateByIndex(...), it uses the general IDocumentStore, not the specific fake IDocumentSession.

当我更改索引定义类然后运行我的单元测试时,索引会在真实"数据库中更新,并且可以通过 Raven Studio 看到更改.但是,保存"到 InMemory 数据库的虚假域实例(mentormentee 等)并未存储在实际数据库中(如预期的那样),因此不能通过 Raven Studio 看到.

When I change my index definition class and then run my unit-test the index is updated in the 'real' database and the changes can be seen via Raven Studio. However, the fake domain instances (mentor, mentee etc) that are 'saved' to the InMemory db are not stored in the actual database (as expected) and so cannot be seen via Raven Studio.

难道我对 UpdateByIndex(...) 的调用是针对不正确的 IDocumentSession 运行的,它是真实"的(没有保存的域实例),而不是假的?

Could it be that my call to the UpdateByIndex(...) is running against the incorrect IDocumentSession, the 'real' one (with no saved domain instances), instead of the fake one?

编辑 2 - @Simon

我已针对上述编辑 1 中概述的问题实施了您的修复,我认为我们正在取得进展.你是对的,我通过 RavenSessionProvder 使用了对 IDocumentStore 的静态引用.现在不是这种情况.下面的代码已更新为使用 Fake.Db() 代替.

I have implemented your fix for the problem outlined in Edit 1 above and I think we are making progress. You were correct, I was using a static reference to the IDocumentStore via the RavenSessionProvder. This is not the case now. The code below has been updated to use the Fake.Db() instead.

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.现在,当我运行它时,出现以下错误:

You will note that I also reset the allowStale=false. Now when I run this I get the following error:

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

我认为我们已经解决了第一个问题,现在我使用了正确的 Fake.Db,我们遇到了第一个突出显示的问题,即索引过时,因为我们在单元测试中运行速度超快.

I reckon we have solved the first problem, and now I am using the correct Fake.Db, we have encountered the issue first highlighted, that the index is stale because we are running super-fast in a unit-test.

现在的问题是:如何让 UpdateByIndex(..) 方法等到 command-Q 为空并且索引被认为是新鲜的"?

The question now is: How can I make the UpdateByIndex(..) method wait until the command-Q is empty and index is considered 'fresh'?

编辑 3

考虑到防止过时索引的建议,我将代码更新如下:

Taking into account the suggestion for preventing a stale index, I have updated the code as follows:

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. 放入一个虚拟查询来强制索引等待它是非陈旧的工作.消除了关于过时索引的错误.

  1. Putting in a dummy Query to force the index to wait until it is non-stale works. The error concerning the stale index is eliminated.

这是一条测试线,用于确保我的索引正常工作.看起来没问题.返回的结果是提供的 Mentor.Id ('users-1') 的正确关系实例.

This is a test line to ensure that my index is working correctly. It appears to be fine. The result returned is the correct Relationship instance for the supplied Mentor.Id ('users-1').

{导师": {"Id": "users-1",用户名":导师先生"},学员":{"Id": "users-2","UserName": "Mentee 先生"}...}

{ "Mentor": { "Id": "users-1", "UserName": "Mr. Mentor" }, "Mentee": { "Id": "users-2", "UserName": "Mr. Mentee" } ... }

尽管索引未过时且看似正常运行,但实际的补丁请求似乎什么也没做.Mentor 的非规范化参考中的用户名保持不变.

Despite the index being non-stale and seemingly functioning correctly, the actual Patch Request seemingly does nothing. The UserName in the Denormalized Reference for the Mentor remains unchanged.

所以怀疑现在落在补丁请求本身上.为什么这不起作用?这可能是我设置要更新的 UserName 属性值的方式吗?

So the suspicion now falls on the Patch Request itself. Why is this not working? Could it be the way I am setting the UserName property value to be updated?

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

您会注意到我只是将 userName 参数的字符串值直接分配给 Value 属性,该属性的类型为 RavenJToken.这可能是个问题吗?

You will note that I am just assigning a string value of the userName param straight to the Value property, which is of type RavenJToken. Could this be an issue?

编辑 4

太棒了!我们有一个解决方案.我已经重新编写了我的代码,以允许你们提供的所有新信息(谢谢).以防万一有人真的读到这里,我最好把工作代码给他们关闭:

Fantastic! We have a solution. I have reworked my code to allow for all the new information you guys have supplied (thanks). Just in case anybody has actually read this far, I better put in the working code to give them closure:

单元测试

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

推荐答案

尝试更改您的线路:

RavenSessionProvider.UpdateByIndex(indexName,  //etc

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

这将确保在您在单元测试中使用的同一个(假)文档存储中发出更新命令.

This will ensure that the Update command is issued on the same (Fake) document store that you are using in your unit tests.

回答编辑 2:

在使用 UpdateByIndex 时,没有自动等待非陈旧结果的方法.您在 SetUserName 方法中有几个选择:

There's no automatic way to wait for non-stale results when using UpdateByIndex. You have a couple of choices in your SetUserName method:

1 - 更改您的数据存储以始终像这样立即更新索引(注意:这可能会对性能产生不利影响):

1 - Change your datastore to always update indexes immediately like this (NOTE: this may adversely affect performance):

store.Conventions.DefaultQueryingConsistency = ConsistencyOptions.MonotonicRead;

2 - 在 UpdateByIndex 调用之前针对您的索引运行查询,指定 WaitForNonStaleResults 选项:

2 - Run a query against your index, just before the UpdateByIndex call, specifying the WaitForNonStaleResults option:

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

3 - 当索引失效时捕获异常抛出,执行 Thread.Sleep(100) 并重试.

3 - Catch the exception throw when the index is stale, do a Thread.Sleep(100) and retry.

回答编辑 3:

我终于弄明白了,并且通过了测试......不敢相信,但这似乎只是一个缓存问题.当您重新加载文档以进行断言时,您需要使用不同的会话...例如

I've finally figured it out, and have a passing test... can't believe it, but it seems to just have been a caching issue. When you re-load your docs for asserting against, you need to use a different session... e.g.

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...
}

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

Can't believe I didn't spot this sooner, sorry.

这篇关于RavenDb:更新非规范化参考属性值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆