当不使用默认ID生成策略时,如何加载<t> RavenDB文档,约束到集合

时间:2018-04-19 12:02:16

标签: ravendb ravendb4

在RavenDB 4(v4.0.3-patch-40031)中,我有两种文档类型:AppleOrange。两者都具有相似但不同的属性。我在运行时遇到了代码中的错误,有时会提供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,有两种方法可以做到这一点:

  1. 使用更昂贵的.Query<T>.Where(x => x.Id == id),而不是.Load<T>(id)
  2. 首先执行.Load<T>(id),然后检查(〜某种程度上,见底部)是否属于集合T
  3. 我的问题可归纳为两个问题

    1. 与上述两个选项相比,还有其他更高效,更稳定的方式吗?
    2. 如果没有,在两个选项中 - 建议在性能和稳定性方面?
    3. 特别是对于第二个问题,很难正确地正确测量它。至于稳定性,例如没有副作用,我认为有更多深入了解或体验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));
              }
          }
      }
      

2 个答案:

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