Модель один ко многим в RavenDb для лучшей производительности

Я подхожу к базе данных документов, и я немного запутался, как отобразить отношения документов, в ситуации, как следует

public class Person
{
    public Person()
    {
    }
    public int Id { get; set; }
    public string Name { get;set;}
    public string Surname { get; set; }
    public DateTime? BirthDate { get; set; }
}


public class Car
{
    public Car() { }
    public int Id { get; set; }
    public string Name { get; set; }
    public int PersonId { get; set;}
}

У человека есть одна или несколько машин, например, таким образом, я могу запросить БД следующим образом

public Car Get(int id)
    {
        Car car = null;
        using (IDocumentSession session = store.OpenSession())
        {
            car = session.Include<Car, Person>(x => x.PersonId).Load<Car>(id);
            bool isLoaded = session.Advanced.IsLoaded("people/" + car.PersonId); // true!
        }
        return car;
    }

и все в порядке, клиент делает только один запрос, но если у меня есть человек, и я хочу показать все его машины, как я могу запросить БД, чтобы сделать только запрос? Я думаю, что я должен изменить модель, поставив List<int> Cars в Person для справки его авто. Обратите внимание, что я не хочу вставлять Cars в Person документ, потому что Cars могут быть ссылки из других документов.

Благодарю.

2 ответа

Решение

Вы можете проиндексировать коллекцию автомобилей и загрузить все автомобили из индекса.

Индекс будет выглядеть так:

public class CarIndex : AbstractIndexCreationTask<Car, CarView>
{
    public CarIndex()
    {
        Map = cars => from car in cars
                      select new
                      {
                          car.Id,
                          car.Name,
                          car.PersonId,
                      };
    }
}

Класс CarView идентичен классу Car, но его можно изменить, чтобы лучше соответствовать потребностям индексации.

public class CarView
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int PersonId { get; set; }
}

Вам нужно будет выполнить индекс, прежде чем использовать его:

new CarIndex().Execute(store);

Загрузка машины для определенного человека будет выглядеть так:

using (IDocumentSession session = store.OpenSession())
{
    session.Store(new Person { Id = 1, Name = "A", Surname = "A" });
    session.Store(new Car { Id = 1, Name = "A", PersonId = 1 });
    session.Store(new Car { Id = 2, Name = "B", PersonId = 1 });
    session.Store(new Car { Id = 3, Name = "C", PersonId = 2 });
    session.SaveChanges();
}

WaitForIndexing(store); // from RavenTestBase

using (IDocumentSession session = store.OpenSession())
{
    var resultsForId1 = session
        .Query<CarView, CarIndex>()
        .ProjectFromIndexFieldsInto<CarView>()
        .Where(x => x.PersonId == 1);
    Assert.Equal(2, resultsForId1.Count());
    var resultsForId2 = session
        .Query<CarView, CarIndex>()
        .ProjectFromIndexFieldsInto<CarView>()
        .Where(x => x.PersonId == 2);
    Assert.Equal(1, resultsForId2.Count());
}

Если вы хотите загрузить человека и его автомобили в одном запросе к базе данных, используйте отложенную загрузку:

var resultsForId1 = session
    .Query<CarView, CarIndex>()
    .ProjectFromIndexFieldsInto<CarView>()
    .Where(x => x.PersonId == 1).Lazily();
var person = session.Advanced.Lazily.Load<Person>(1);

var personValue = person.Value;
var resultsValue = resultsForId1.Value;

Завершить тест (требуется нюгет xunit и RavenDB.Tests.Helpers):

using Raven.Client;
using Raven.Client.Indexes;
using Raven.Tests.Helpers;
using System;
using System.Linq;
using Xunit;

namespace SO41547501Answer
{
    public class SO41547501 : RavenTestBase
    {
        [Fact]
        public void SO41547501Test()
        {
            using (var server = GetNewServer())
            using (var store = NewRemoteDocumentStore(ravenDbServer: server))
            {
                new CarIndex().Execute(store);

                using (IDocumentSession session = store.OpenSession())
                {
                    session.Store(new Person { Id = 1, Name = "A", Surname = "A" });
                    session.Store(new Car { Id = 1, Name = "A", PersonId = 1 });
                    session.Store(new Car { Id = 2, Name = "B", PersonId = 1 });
                    session.Store(new Car { Id = 3, Name = "C", PersonId = 2 });
                    session.SaveChanges();
                }

                WaitForAllRequestsToComplete(server);
                WaitForIndexing(store);

                using (IDocumentSession session = store.OpenSession())
                {
                    var resultsForId1 = session
                        .Query<CarView, CarIndex>()
                        .ProjectFromIndexFieldsInto<CarView>()
                        .Where(x => x.PersonId == 1);
                    Assert.Equal(2, resultsForId1.Count());
                    var resultsForId2 = session
                        .Query<CarView, CarIndex>()
                        .ProjectFromIndexFieldsInto<CarView>()
                        .Where(x => x.PersonId == 2);
                    Assert.Equal(1, resultsForId2.Count());
                }

                using (IDocumentSession session = store.OpenSession())
                {
                    server.Server.ResetNumberOfRequests();
                    var resultsForId1 = session
                        .Query<CarView, CarIndex>()
                        .ProjectFromIndexFieldsInto<CarView>()
                        .Where(x => x.PersonId == 1).Lazily();
                    var person = session.Advanced.Lazily.Load<Person>(1);

                    var personValue = person.Value;
                    var resultsValue = resultsForId1.Value;
                    Assert.Equal("A", personValue.Name); // person data loaded
                    Assert.Equal("A", resultsValue.First().Name); // cars data loaded
                    Assert.Equal(1, server.Server.NumberOfRequests); // only one request sent to the server
                }
            }
        }
    }

    public class CarIndex : AbstractIndexCreationTask<Car, CarView>
    {
        public CarIndex()
        {
            Map = cars => from car in cars
                          select new
                          {
                              car.Id,
                              car.Name,
                              car.PersonId,
                          };
        }
    }

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public DateTime? BirthDate { get; set; }
    }

    public class Car
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int PersonId { get; set; }
    }

    public class CarView
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int PersonId { get; set; }
    }
}

Вы можете сделать это так:

using (IDocumentSession session = store.OpenSession())
{
    var carsForOne = session.Query<Car>()
             .Include(x=>x.PersonId)
             .Where(x=>x.PersonId == "people/1")
             .ToList();

    var person = session.Load<Person>("people/1");
}

Это делает только один запрос БД.

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