Я реализовал шаблон 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 содержит идентификатор и имя пользователя указанного пользователя. Итак, теперь, если я обновлю имя пользователя для данного экземпляра пользователя, я хочу, чтобы указанное значение имени пользователя во всех ссылках пользователя также обновлялось. Для этого я написал статический индекс и запрос на исправление следующим образом:
public class Relationships_ByMentorId : AbstractIndexCreationTask
{
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);
}
И, наконец, модульный тест, который не работает, потому что обновление не работает должным образом.
[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.Id);
relationship.ShouldNotBe(null);
relationship.Mentor.ShouldNotBe(null);
relationship.Mentor.Id.ShouldBe(mentor.Id);
relationship.Mentor.UserName.ShouldBe(userName);
mentor = db.Load(mentor.Id);
mentor.ShouldNotBe(null);
mentor.UserName.ShouldBe(userName);
}
}
Все работает нормально, индекс есть, но я подозреваю, что он не возвращает отношения, требуемые запросом на исправление, но, честно говоря, у меня закончился талант. Можете ли вы помочь, пожалуйста?
Редактировать 1
@MattWarren allowStale=true
не помогло. Однако я заметил потенциальную подсказку.
Поскольку это модульный тест, я использую InMemory, встроенный IDocumentSession -Fake.Db()
в приведенном выше коде. Тем не менее, когда вызывается статический индекс, то есть при выполнении UpdateByIndex(...)
, он использует общий IDocumentStore, а не конкретный поддельный IDocumentSession.
Когда я изменяю свой класс определения индекса, а затем запускаю свой модульный -тест, индекс обновляется в «реальной» базе данных, и изменения можно увидеть через Raven Studio. Однако поддельные экземпляры домена (mentor
, mentee
и т. д. ), которые «сохраняются» в базе данных InMemory, не хранятся в фактической базе данных (, как ожидалось ), и поэтому их нельзя увидеть через Raven Studio.
Могло ли быть так, что мой вызов UpdateByIndex(...)
выполняется против неправильного IDocumentSession, «настоящего» (без сохраненных экземпляров домена ), а не поддельного?
Редактировать 2-@Simon
Я реализовал ваше решение проблемы, описанной в Редактировании 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
Я считаю, что мы решили первую проблему, и теперь я использую правильный Fake.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(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);
}
Из пронумерованных комментариев выше:
Добавление фиктивного запроса, чтобы заставить индекс ждать, пока он не не -устаревшие работы. Устранена ошибка относительно устаревшего индекса.
Это тестовая строка, чтобы убедиться, что мой индекс работает правильно. Кажется, все в порядке. Возвращаемый результат является правильным экземпляром отношения для предоставленного Mentor.Id ('users -1' ).
{ "Наставник" :{ "Идентификатор" :"пользователи -1", "Имя пользователя" :"Мистер Ментор" }, "Подопечный" :{ "Идентификатор" :"пользователи -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(relationshipId);
relationship.ShouldNotBe(null);
relationship.Mentor.ShouldNotBe(null);
relationship.Mentor.Id.ShouldBe(mentorId);
relationship.Mentor.UserName.ShouldBe(userName);
var mentor = db.Load(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(mentorId), db.Load(menteeId));
db.Store(relationship);
db.SaveChanges();
return relationship;
}
Raven Session Provider для модульных тестов
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
{
public Relationships_ByMentorId()
{
Map = relationships => from relationship in relationships
select new { Mentor_Id = relationship.Mentor.Id };
}
}
public class AllDocuments : AbstractIndexCreationTask
{
public AllDocuments()
{
Map = documents => documents.Select(entity => new {});
}
}
}
Обновление денормализованной ссылки
public static void SetUserName(IDocumentSession db, string mentorId, string userName)
{
var mentor = db.Load(mentorId);
mentor.UserName = userName;
db.Store(mentor);
db.SaveChanges();
//Don't want this is production code
db.Query(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
},
}
}
};
}