RavenDb: обновление Денормализованного значения ссылочного свойства

я реализовал 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 содержит идентификатор и имя пользователя указанного пользователя. Итак, теперь, если я обновлю имя пользователя для a данный экземпляр пользователя, то я хочу, чтобы ссылочное значение имени пользователя во всех UserReferences также обновлялось. Для этого я написал статический индекс и запрос на исправление следующим образом:

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. Однако поддельные экземпляры домена (mentor, mentee etc), которые "сохранены" в БД InMemory, не хранятся в фактической базе данных (как ожидалось) и поэтому не могут быть видны через Raven Studio.

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

Изменить 2 - @Simon

я реализовал ваше исправление для проблемы, описанной в Edit 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

я считаю, что мы решили первую проблему, и теперь я использую правильную подделку.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. это тест, чтобы убедиться, что мой индекс работает правильно. Это, кажется, нормально. Возвращаемый результат является правильным экземпляром отношения для предоставленного наставника.Id (пользователей-1').

    { "Наставник": { "Id": "пользователи-1", "Имя": "Г-Н Ментор" }, "Подопечный": { "Id": "пользователи-2", "Имя": "Г-Н Подопечный" } ... }

  3. несмотря на то, что индекс не является устаревшим и, по-видимому, функционирует правильно, фактический запрос патча, по-видимому, ничего не делает. Имя Пользователя в Денормализованной ссылке для наставника остается неизменным.

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

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

вы заметите, что я просто присваиваю строковое значение userName парам прямо к 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 ответов


попробуйте изменить строку:

RavenSessionProvider.UpdateByIndex(indexName,  //etc

to

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

это гарантирует, что команда Update будет выдана в том же (поддельном) хранилище документов, которое вы используете в модульных тестах.

ответ на редактирование 2:

при использовании UpdateByIndex нет автоматического способа дождаться не устаревших результатов. У вас есть несколько вариантов в вашем SetUserName способ:

1-измените хранилище данных, чтобы всегда обновлять индексы сразу же, как это (примечание: это может негативно повлиять на производительность):

store.Conventions.DefaultQueryingConsistency = ConsistencyOptions.MonotonicRead;

2-Запустите запрос к индексу непосредственно перед вызовом UpdateByIndex, указав :

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

не могу поверить, что я не заметил этого раньше, извините.