我已经实现了RavenDB非规范化参考 http://ravendb.net/docs/faq/denormalized-updates图案。我正在努力将静态索引和补丁更新请求连接在一起,以确保在引用的实例值更改时更新我的非规范化引用属性值。
这是我的域名:
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 包含引用的 User 的 Id 和 UserName。所以现在,如果我更新给定用户实例的用户名,那么我希望所有用户引用中引用的用户名值也更新。为了实现这一点,我编写了一个静态索引和一个补丁请求,如下所示:
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);
}
最后,由于更新未按预期工作,单元测试失败。
[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);
}
}
一切运行良好,索引就在那里,但我怀疑这没有返回补丁请求所需的关系,但老实说,我已经没有人才了。你能帮忙吗?
Edit 1
@马特沃伦allowStale=true
没有帮助。然而我注意到了一个潜在的线索。
因为这是一个单元测试,所以我使用 InMemory、嵌入式 IDocumentSession -Fake.Db()
在上面的代码中。然而,当调用静态索引时,即执行以下操作时UpdateByIndex(...)
,它使用通用的IDocumentStore,而不是特定的假IDocumentSession。
当我更改索引定义类然后运行单元测试时,索引会在“真实”数据库中更新,并且可以通过 Raven Studio 查看更改。然而,假域名实例(mentor
, mentee
等)“保存”到 InMemory 数据库中的数据并未存储在实际数据库中(如预期),因此无法通过 Raven Studio 查看。
难道我打电话给UpdateByIndex(...)
正在针对不正确的 IDocumentSession(“真实”会话(没有保存的域实例),而不是假会话)运行?
Edit 2- @西蒙
我已经对上面编辑 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 为空并且索引被认为是“新鲜”?
Edit 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);
}
从上面编号的评论来看:
放入一个虚拟查询以强制索引等待,直到它不再过时为止。消除了有关过时索引的错误。
-
这是一条测试线,以确保我的索引正常工作。看来还好。返回的结果是所提供的 Mentor.Id ('users-1') 的正确关系实例。
{
“导师”: {
"Id": "用户-1",
"用户名": "导师先生"
},
“学员”:{
“Id”:“用户2”,
“用户名”:“学员先生”
}
...
}
尽管索引未过时并且看起来运行正常,但实际的补丁请求似乎没有执行任何操作。导师非规范化参考中的用户名保持不变。
因此,现在的怀疑落在了补丁请求本身上。为什么这不起作用?这可能是我设置要更新的 UserName 属性值的方式吗?
...
new PatchRequest
{
Type = PatchCommandType.Set,
Name = "UserName",
Value = userName
}
...
您会注意到我只是分配了一个字符串值userName
参数直接到Value
属性,其类型为RavenJToken
。这会是一个问题吗?
Edit 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", "[email protected] /cdn-cgi/l/email-protection", "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] /cdn-cgi/l/email-protection", "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 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<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
},
}
}
};
}