Как загрузить<T> документ RavenDB, ограниченный коллекцией, если не используется стратегия генерации идентификатора по умолчанию

В RavenDB 4 (v4.0.3-patch-40031) у меня есть два типа документов: Apple а также Orange, Оба имеют схожие, но отличающиеся свойства. Я сталкиваюсь с ошибкой в ​​моем коде во время выполнения, когда иногда предоставляется идентификатор Apple, но возвращается Orange. Страшно!

Погружаясь в это, это имеет некоторый смысл. Но я борюсь с соответствующим решением.

Вот оно. В RavenDB я сохранил один Apple как документ:

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

... но это не удается. Что происходит, так это то, что идентификатор документа существует, поэтому RavenDB загружает документ. Неважно, что это за тип. И он пытается сопоставить свойства автоматически. Я ожидал или предположил, что спецификатор типа загрузки ограничит поиск этой конкретной коллекцией документов. Вместо этого он захватывает + отображает его по всей базе данных, не ограничивая его type <T>, Так что поведение отличается от .Query<T>, что делает ограничение на сбор.

Важно отметить, что я использую направляющие как стратегию идентификации, устанавливая Id в string.Empty (соответствуют документам). Я предполагаю стратегию идентификатора по умолчанию, которая похожа entityname/1001, не было бы этой проблемы.

В документах по загрузке сущностей не упоминается, является ли это намеренным или нет. В нем только сказано: "скачать документы из базы данных и преобразовать их в сущности".

Однако по ряду причин я хочу ограничить операцию Load одной коллекцией. Или, лучше сказать, максимально эффективно загружать документ по идентификатору из определенной коллекции. И если он не существует, вернуть ноль.

AFAIK, есть два варианта для достижения этой цели:

  1. Используйте более дорогой .Query<T>.Where(x => x.Id == id), вместо .Load<T>(id)
  2. Сделать .Load<T>(id) сначала, а затем проверьте (~ как-то, см. внизу), является ли он частью коллекции T

Моя проблема может быть обобщена в двух вопросах:

  1. Есть ли другой, более производительный или стабильный способ, чем два вышеупомянутых варианта?
  2. Если нет, то из двух вариантов - что рекомендуется с точки зрения производительности и стабильности?

Особенно по второму вопросу, очень трудно правильно измерить это правильно. Что касается стабильности, например, отсутствия побочных эффектов, я думаю, что кто-то, обладающий более глубокими знаниями или опытом работы с внутренностями RavenDB, может пролить некоторый свет.

NB. Вопрос предполагает, что описанное поведение является преднамеренным, а не ошибкой 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 ответа

Решение

Ну, я нашел, в чем проблема, но я не понимаю, почему.

вы сказали:

установив Id в string.Empty

но в примере вы написали Id = Guid.NewGuid().ToString(); в моих тестах я явно назначаю string.Empty и я получаю исключение приведения, когда я назначил сгенерированный Guid сущности (как вы), я воспроизвел ваши ситуации. Вероятно, ravendb делает несколько разных соображений в этих двух случаях, которые создают такое поведение, я не знаю, можно ли это считать ошибкой.

Тогда используйте string.Empty

.Query<T>.Where(x => x.Id == id) это путь В RavenDB 4.0 запросы по идентификатору обрабатываются непосредственно хранилищем документов под обложками (а не индексом), так что это так же эффективно, как Load,

Преимущество вашего сценария заключается в том, что запросы ограничиваются только указанной коллекцией.

Другие вопросы по тегам