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);
}
из пронумерованных комментариев выше:
ввод фиктивного запроса, чтобы заставить индекс ждать, пока он не будет устаревшим. Ошибка, касающаяся несвежего индекса, устранена.
-
это тест, чтобы убедиться, что мой индекс работает правильно. Это, кажется, нормально. Возвращаемый результат является правильным экземпляром отношения для предоставленного наставника.Id (пользователей-1').
{ "Наставник": { "Id": "пользователи-1", "Имя": "Г-Н Ментор" }, "Подопечный": { "Id": "пользователи-2", "Имя": "Г-Н Подопечный" } ... }
несмотря на то, что индекс не является устаревшим и, по-видимому, функционирует правильно, фактический запрос патча, по-видимому, ничего не делает. Имя Пользователя в Денормализованной ссылке для наставника остается неизменным.
таким образом, подозрение теперь падает на запрос патча себя. Почему это не работает? Может быть, я устанавливаю значение свойства 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...
}
не могу поверить, что я не заметил этого раньше, извините.