在RavenDB 4(v4.0.3-patch-40031)中,我有两种文档类型:Apple
和Orange
。两者都具有相似但不同的属性。我在运行时遇到了代码中的错误,有时会提供Apple的ID,但会返回Orange。害怕!
潜入它,这有点道理。但我正在努力寻找合适的解决方案。
到此为止。在RavenDB中,我将一个Apple
存储为Document:
id: "078ff39b-da50-4405-9615-86b0d185ba17"
{
"Name": "Elstar",
"@metadata": {
"@collection": "Apples",
"Raven-Clr-Type": "FruitTest.Apple, FruitTest"
}
}
假设为了这个例子,我没有存储在数据库中的Orange
文档。我希望这个测试能够成功:
// arrange - use the ID of an apple, which does not exist in Orange collection
var id_of_apple = "078ff39b-da50-4405-9615-86b0d185ba17";
// act - load an Orange
var target = await _session.LoadAsync<Orange>("078ff39b-da50-4405-9615-86b0d185ba17");
// assert - should be null, because there is no Orange with that Id
target.Should().BeNull(because: "provided ID is not of an Orange but of an Apple");
......但它失败了。会发生什么是文档ID存在,因此RavenDB加载文档。不关心它是什么类型。它会尝试自动映射属性。我期望或假设错误,Load类型说明符会限制查找到该特定文档集合。相反,它会在整个数据库中抓取+映射,而不是将其约束到type <T>
。所以行为不同于.Query<T>
,它对集合做了约束。
需要注意的重要一点是,我使用 guids作为身份策略,将ID设置为
string.Empty
(符合the docs)。我假设默认ID策略(如entityname/1001
)不会出现此问题。
docs on Loading Entities并不是真的提到这是否是故意的。它只说:&#34; 从数据库下载文档并将它们转换为实体。&#34;。
但是,出于某些原因,我确实想将Load操作限制为单个集合。或者,最好尽可能有效地从特定集合中按ID加载文档。如果它不存在,则返回null。
AFAIK,有两种方法可以做到这一点:
.Query<T>.Where(x => x.Id == id)
,而不是.Load<T>(id)
.Load<T>(id)
,然后检查(〜某种程度上,见底部)是否属于集合T 我的问题可归纳为两个问题:
特别是对于第二个问题,很难正确地正确测量它。至于稳定性,例如没有副作用,我认为有更多深入了解或体验RavenDB内部的人可能会有所启发。
N.B。该问题假定所解释的行为是故意的而不是RavenDB错误。
〜不知何故会:
public async Task<T> Get(string id)
{
var instance = await _session.LoadAsync<T>(id);
if (instance == null) return null;
// the "somehow" check for collection
var expectedTypeName = string.Concat(typeof(T).Name, "s");
var actualTypeName = _session.Advanced.GetMetadataFor(instance)[Constants.Documents.Metadata.Collection].ToString();
if (actualTypeName != expectedTypeName)
{
// Edge case: Apple != Orange
return null;
}
return instance;
}
更新2018/04/19 - 在有用的评论之后添加了这个可重复的样本(感谢您)。
模型
public interface IFruit
{
string Id { get; set; }
string Name { get; set; }
}
public class Apple : IFruit
{
public string Id { get; set; }
public string Name { get; set; }
}
public class Orange : IFruit
{
public string Id { get; set; }
public string Name { get; set; }
}
测试
例如。在同一个会话(工作)中抛出InvalidCastException,但在第二个会话中它没有。
public class UnitTest1
{
[Fact]
public async Task SameSession_Works_And_Throws_InvalidCastException()
{
var store = new DocumentStore()
{
Urls = new[] {"http://192.168.99.100:32772"},
Database = "fruit"
}.Initialize();
using (var session = store.OpenAsyncSession())
{
var apple = new Apple
{
Id = Guid.NewGuid().ToString(),
Name = "Elstar"
};
await session.StoreAsync(apple);
await session.SaveChangesAsync();
await Assert.ThrowsAsync<InvalidCastException>(() => session.LoadAsync<Orange>(apple.Id));
}
}
[Fact]
public async Task Different_Session_Fails()
{
var store = new DocumentStore()
{
Urls = new[] {"http://192.168.99.100:32772"},
Database = "fruit"
}.Initialize();
using (var session = store.OpenAsyncSession())
{
var appleId = "ca5d9fd0-475b-41de-a1ab-57bb1e3ce018";
// this *should* break, because... it's an apple
// ... but it doesn't - it returns an ORANGE
var orange = await session.LoadAsync<Orange>(appleId);
await Assert.ThrowsAsync<InvalidCastException>(() => session.LoadAsync<Orange>(appleId));
}
}
}
答案 0 :(得分:3)
.Query<T>.Where(x => x.Id == id)
是要走的路。在RavenDB 4.0中,ID的查询直接由封面下的文档存储(而不是索引)处理,因此它与Load
的效率相同。
您的方案的优点是查询的范围仅限于指定的集合。
答案 1 :(得分:1)
将Id设置为string.Empty
但在示例中你写了Id = Guid.NewGuid().ToString()
;
在我的测试中,我明确地指定了string.Empty
,并且我获得了强制转换异常,当我将生成的Guid分配给实体(像你一样)时,我重现了你的情况。可能ravendb在这两种情况下会产生一些不同的考虑因素,这会产生这种行为,我不知道它是否会被视为一种错误。
然后使用string.Empty