RavenDb: обновить значение свойства денормализованной ссылки

Я реализовал RavenDBДенормализованная ссылка шаблон. Я изо всех сил пытаюсь соединить вместе статический индекс и запрос на обновление патча, необходимые, чтобы гарантировать, что мои денормализованные значения эталонного свойства обновляются при изменении ссылочного значения экземпляра.

Вот мой домен:

<code>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; }
}
</code>

Вы можете видеть, что UserReference содержит Id и UserName указанного пользователя. Так что теперь, если я обновлю имя пользователя для данного экземпляра пользователя, то я хочу, чтобы значение имени пользователя, на которое ссылаются, во всех ссылках пользователя также обновлялось. Для этого я написал статический индекс и запрос на исправление следующим образом:

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

И, наконец, UnitTest, который завершается неудачно, поскольку обновление не работает должным образом.

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

Все работает нормально, индекс есть, но я подозреваю, что это не возвращает отношения, требуемые запросом патча, но, честно говоря, у меня закончились таланты. Можете ли вы помочь, пожалуйста?

Edit 1

@MattWarrenallowStale=true не помогло. Однако я заметил потенциальную подсказку.

Поскольку это модульный тест, я использую InMemory, встроенный IDocumentSession -Fake.Db() в коде выше. Тем не менее, когда вызывается статический индекс, т.е.UpdateByIndex(...), он использует общий IDocumentStore, а не конкретный поддельный IDocumentSession.

Когда я изменяю свой класс определения индекса, а затем запускаю свой модульный тест, индекс обновляется в «реальном». базу данных и изменения можно увидеть через Raven Studio. Тем не менее, поддельные экземпляры домена (mentor, mentee и т. д.), которые «сохранены»; к InMemory БД не хранятся в фактической базе данных (как ожидалось) и поэтому не могут быть видны через Raven Studio.

Может ли быть так, что мой призыв кUpdateByIndex(...) работает с неправильным IDocumentSession, «реальным» один (без сохраненных экземпляров домена) вместо поддельного?

Edit 2 - @Simon

Я реализовал ваше исправление для проблемы, описанной в Редактировании 1 выше, и я думаю, что мы делаем успехи. Вы были правы, я использовал статическую ссылку наIDocumentStore черезRavenSessionProvder, Сейчас это не так. Код ниже был обновлен для использованияFake.Db() вместо.

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

Вы заметите, что я также сбросилallowStale=false, Теперь, когда я запускаю это, я получаю следующую ошибку:

<code>Bulk operation cancelled because the index is stale and allowStale is false
</code>

Я считаю, что мы решили первую проблему, и теперь я использую правильный Fake.Db, мы столкнулись с проблемой, которая была впервые выделена, что индекс устарел, потому что мы выполняем супер-быстрый тест в модульном тесте.

Теперь вопрос: как я могу сделатьUpdateByIndex(..) метод ожидает, пока команда Q не станет пустой и индекс не будет считаться «свежим»?

Edit 3

Принимая во внимание предложение по предотвращению устаревшего индекса, я обновил код следующим образом:

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

Из пронумерованных комментариев выше:

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.

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').

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

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 для обновления?

<code>...
new PatchRequest
{
        Type = PatchCommandType.Set,
        Name = "UserName",
        Value = userName
}
...
</code>

Вы заметите, что я просто присваиваю строковое значениеuserName Param прямо кValue свойство, которое имеет типRavenJToken, Может ли это быть проблемой?

Edit 4

Фантастика! У нас есть решение. Я переработал свой код, чтобы учесть всю новую информацию, которую вы, ребята, предоставили (спасибо). На всякий случай, если кто-то действительно прочитал это далеко, я лучше вставлю рабочий код, чтобы закрыть его:

The Unit Test

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

The Fakes

<code>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;
}
</code>

The Raven Session Provider for Unit Tests

<code>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();
    }
}
</code>

The Indexes

<code>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 {});
        }
    }
}
</code>

Update Denormalized Reference

<code>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
                                            },
                                    }
                    }
            };
}
</code>

Ответы на вопрос(1)

Ваш ответ на вопрос