如何加载<T>当不使用默认 ID 生成策略时,一个 RavenDB 文档,限制为一个集合 [英] How to Load&lt;T&gt; a RavenDB Document, constrained to a Collection, when not using default ID generation strategy

查看:44
本文介绍了如何加载<T>当不使用默认 ID 生成策略时,一个 RavenDB 文档,限制为一个集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 RavenDB 4 (v4.0.3-patch-40031) 中,我有两种文档类型:AppleOrange.两者都有相似但又不同的属性.我在运行时在我的代码中遇到了一个错误,有时提供了 Apple 的 ID,但返回了一个 Orange.害怕!

In RavenDB 4 (v4.0.3-patch-40031) I have two Document types: Apple and Orange. Both have similar, but also distinct, properties. I run into a bug in my code at runtime where sometimes an the ID of an Apple is provided, but an Orange is returned. Scary!

深入研究,有点道理.但我正在努力寻找合适的解决方案.

Diving into it, it somewhat makes sense. But I'm struggling with an appropriate solution.

来了.在 RavenDB 中,我将单个 Apple 存储为文档:

Here goes. In RavenDB, I have stored a single Apple as a Document:

id: "078ff39b-da50-4405-9615-86b0d185ba17"
{
    "Name": "Elstar",
    "@metadata": {
        "@collection": "Apples",
        "Raven-Clr-Type": "FruitTest.Apple, FruitTest"
    }
}

为了这个例子,假设我没有存储在数据库中的 Orange 文档.我希望这个测试成功:

Assume for the sake of this example that I have no Orange documents stored in the database. I would expect this test to succeed:

// 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 .所以行为与.Query不同,后者对集合进行约束.

... but it fails. What happens is that the Document ID exists, so the RavenDB loads the document. Not caring what type it is. And it attempts to map the properties automatically. I expected, or assumed incorrectly, that the Load type specifier would limit the lookup to that particular document collection. Instead, it grabs + maps it throughout the entire database, not constraining it to type <T>. So the behaviour is different from .Query<T>, which does constraint to collection.

需要注意的是,我使用 guids 作为身份策略,通过将 Id 设置为 string.Empty(符合 文档).我假设默认的 ID 策略,比如 entityname/1001,不会有这个问题.

Important to note is that I'm using guids as identity strategy, by setting the Id to string.Empty (conform the docs). I assume the default ID strategy, which is like entityname/1001, would not have this issue.

有关加载实体的文档 不要真正提及这是故意的还是无意的.它只说:从数据库下载文档并将它们转换为实体.".

The docs on Loading Entities don't really mention if this is intentional, or not. It only says: "download documents from a database and convert them to entities.".

但是,出于某些原因,我确实想将 Load 操作限制为单个集合.或者,最好尽可能高效地从特定集合中按 ID 加载文档.如果不存在,则返回 null.

However, for reasons, I do want to constrain the Load operation to a single collection. Or, better put, as efficiently as possible load a document by ID, from a specific collection. And if it does not exist, return null.

AFAIK,有两种选择可以实现这一点:

AFAIK, there are two options to achieve this:

  1. 使用更昂贵的.Query.Where(x => x.Id == id),而不是.Load(id)>
  2. 首先执行 .Load(id) 然后检查(~不知何故,见底部)它是否是集合 T 的一部分
  1. Use the more expensive .Query<T>.Where(x => x.Id == id), instead of .Load<T>(id)
  2. Do the .Load<T>(id) first and then check (~somehow, see bottom) if it is part of collection T

我的问题可以概括为两个问题:

  1. 除了上面提到的两个选项之外,还有其他更高效或更稳定的方法吗?
  2. 如果没有,从两个选项中选择哪个 - 在性能和稳定性方面推荐哪个?

特别是对于第二个问题,很难正确测量它.至于稳定性,例如没有副作用,我猜对 RavenDB 内部结构有更深入了解或经验的人可能会对此有所了解.

Especially for the second question, it is very hard to correctly measure this properly. As for stability, e.g. not having side effects, that is something that I guess someone with more in-depth knowledge or experience of the RavenDB internals might shed some light on.

注意该问题假设所解释的行为是有意为之,而不是 RavenDB 错误.

N.B. The question assumes that the explained behaviour is intentional and not a RavenDB bug.

~不知何故:

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 - 在有用的评论后添加了这个可重现的示例(谢谢).

How to reproduce

UPDATE 2018/04/19 - Added this reproducible sample after helpful comments (thanks for that).

模型

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(有效),但在第二次它没有.

Tests
E.g. throws InvalidCastException in same session (works), but in second it doesn't.

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));
        }
    }
}

推荐答案

好吧,我发现应该是什么问题,但我不明白为什么.

well, i found what should be the problem but i don't understand why.

你说:

通过将 Id 设置为 string.Empty

by setting the Id to string.Empty

但是在你写的例子中 Id = Guid.NewGuid().ToString();在我的测试中,我明确分配了 string.Empty 并且我得到了强制转换异常,当我将生成的 Guid 分配给实体(如您)时,我重现了您的情况.可能 ravendb 在这两种情况下做了一些不同的考虑,导致了这种行为,我不知道它是否可以被认为是一个错误.

but in the example you wrote Id = Guid.NewGuid().ToString(); in my tests i explicitly assign string.Empty and i get the cast exception, when i assigned the generated Guid to the entity (like you) i reproduced your situations. Probably ravendb makes some different considerations in these two cases that creates this behavior, i don't know if it could be considered a bug.

然后使用string.Empty

这篇关于如何加载<T>当不使用默认 ID 生成策略时,一个 RavenDB 文档,限制为一个集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆