RavenDb: aktualizacja wartości właściwości Denormalized Reference

Zaimplementowałem RavenDBDenormalized Reference wzór. Usiłuję połączyć ze sobą indeks statyczny i żądanie aktualizacji łaty wymagane do zapewnienia, że ​​moje wartości właściwości zdenormalizowanych referencji zostaną zaktualizowane po zmianie wartości przywoływanej instancji.

Oto moja domena:

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

Widać, że UserReference zawiera identyfikator i nazwę użytkownika odnośnego użytkownika. Więc teraz, jeśli zaktualizuję nazwę użytkownika dla danej instancji użytkownika, to chcę, aby wymieniona wartość użytkownika we wszystkich referencjach użytkownika była również aktualizowana. Aby to osiągnąć, napisałem indeks statyczny i żądanie poprawki w następujący sposób:

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

I wreszcie test UnitTest, który się nie powiedzie, ponieważ aktualizacja nie działa zgodnie z oczekiwaniami.

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

Wszystko działa dobrze, indeks jest, ale podejrzewam, że nie zwraca on relacji wymaganych przez żądanie poprawki, ale szczerze mówiąc, zabrakło mi talentów. Czy możesz pomóc?

Edytuj 1

@MattWarrenallowStale=true nie pomogło. Zauważyłem jednak potencjalną wskazówkę.

Ponieważ jest to test jednostkowy, używam InMemory, osadzonej IDocumentSession - theFake.Db() w powyższym kodzie. Jednak kiedy wywoływany jest indeks statyczny, tj. Podczas wykonywaniaUpdateByIndex(...), używa ogólnego IDocumentStore, a nie konkretnej fałszywej IDocumentSession.

Gdy zmieniam klasę definicji indeksu, a następnie uruchamiam test jednostkowy, indeks jest aktualizowany w „prawdziwej” bazie danych, a zmiany można zobaczyć w Raven Studio. Jednak fałszywe instancje domen (mentor, mentee itd.), które są „zapisywane” do bazy danych InMemory, nie są przechowywane w rzeczywistej bazie danych (zgodnie z oczekiwaniami) i dlatego nie można ich zobaczyć za pośrednictwem Raven Studio.

Czy to możliwe, że moje wezwanie doUpdateByIndex(...) działa przeciwko niepoprawnej IDocumentSession, „prawdziwej” (bez zapisanych instancji domeny), zamiast fałszywej?

Edytuj 2 - @Szymon, Szymek

Zaimplementowałem poprawkę dotyczącą problemu opisanego w powyższej edycji 1 i myślę, że robimy postępy. Miałeś rację, używałem statycznego odniesienia doIDocumentStore za pośrednictwemRavenSessionProvder. Teraz tak nie jest. Poniższy kod został zaktualizowany w celu użyciaFake.Db() zamiast.

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

Zauważysz, że również zresetowałemallowStale=false. Teraz, gdy to uruchomię, pojawia się następujący błąd:

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

Sądzę, że rozwiązaliśmy pierwszy problem, a teraz używam poprawnego Fake.Db, natrafiliśmy na problem po raz pierwszy podkreślony, że indeks jest nieaktualny, ponieważ działamy bardzo szybko w teście jednostkowym.

Pytanie brzmi teraz: jak mogę zrobićUpdateByIndex(..) metoda czeka, aż komenda-Q będzie pusta, a indeks zostanie uznany za „świeży”?

Edytuj 3

Biorąc pod uwagę sugestię, aby zapobiec nieaktualnemu indeksowi, zaktualizowałem kod w następujący sposób:

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

Z ponumerowanych komentarzy powyżej:

Umieszczenie fałszywego zapytania, aby wymusić na indeksie oczekiwanie, aż stanie się nieaktualny. Błąd dotyczący przestarzałego indeksu jest wyeliminowany.

To jest linia testowa, aby upewnić się, że mój indeks działa poprawnie. Wydaje się być w porządku. Zwracany wynik jest poprawną instancją relacji dla dostarczonego Mentor.Id ('users-1').

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

Pomimo tego, że indeks jest nieaktualny i pozornie działa poprawnie, rzeczywiste żądanie aktualizacji wydaje się nic nie robić. Nazwa użytkownika w Denormalized Reference dla Mentora pozostaje niezmieniona.

Więc podejrzenie spada teraz na samo żądanie poprawek. Dlaczego to nie działa? Czy może to być sposób, w jaki ustawiam wartość właściwości UserName na aktualizację?

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

Zauważysz, że właśnie przypisuję wartość ciąguuserName param prosto doValue własność, która jest typuRavenJToken. Czy to może być problem?

Edytuj 4

Fantastyczny! Mamy rozwiązanie. Przepracowałem mój kod, aby uwzględnić wszystkie nowe informacje, które dostarczyliście (dzięki). Na wszelki wypadek, gdyby ktoś przeczytał tak daleko, lepiej umieściłem kod roboczy, aby dać im zamknięcie:

Test jednostkowy

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

Fałszywe

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

Dostawca sesji Raven dla testów jednostkowych

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

Indeksy

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

Zaktualizuj 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>

questionAnswers(1)

yourAnswerToTheQuestion