Nicke
Nicke

Reputation: 365

Query RavenDB for child property collection that contains certain value

I have this class, let’s call it ”Device”. This class has several properties, among one which is a collection property (of string values).

In RavenDB there could be 5000 instances of “Device”, where each of them CAN have a list of string values in the collection property. Let’s call this property “MyStringValues”. My question revolves around the best possible way to search ravendb for a Device instance that contains a string value in it’s collection property.

A very simple example:

void Main()
{
    var d1 = new Device();
    d1.Id = "device-1";
    d1.MyStringValues.Add("123");
    d2.MyStringValues.Add("456");

    var d2 = new Device();
    d2.Id = "device-2";
    d2.MyStringValues.Add("789");
    d2.MyStringValues.Add("abc");
}

public class Device{
    public Device(){
        MyStringValues = new List<string>();
    }
    public string Id {get;set;}
    public IList<string> MyStringValues {get;set;}
}

In the method I seek to construct I pass a string value. Based on that string I want to receive a Device. What would be the best way to retrieve this “Device”? Since the number of devices can be up to 5000 I can’t fetch them all and start looping over them. There must be a better (faster) way of doing this. What do you say guys?

Upvotes: 0

Views: 953

Answers (2)

Rafael Marques
Rafael Marques

Reputation: 1872

You can create an index that match your MyStringValues list, and than query it using LINQ's Any.

Your Index will be something like:

public class Devices_ByStringValue : AbstractIndexCreationTask<Device>
{
    public override string IndexName => "Devices/ByStringValue";

    public Devices_ByStringValue()
    {
        Map = devices => from device in devices
                          select new { device.MyStringValues };
    }
}

Now you can query it like:

var devices = session.Query<Device>()
        .Where(x => x.MyStringValues.Any(s => s == searchTerm))
        .ToList();

Here is a complete console application example:

class Program
{
    static void Main(string[] args)
    {
        Console.Write("> Enter your search term: ");

        var searchTerm = Console.ReadLine();

        using (var session = DocumentStoreHolder.Instance.OpenSession())
        {
            var devices = session.Query<Device>()
                .Where(x => x.MyStringValues.Any(s => s == searchTerm))
                .ToList();

            foreach (var device in devices)
            {
                Console.WriteLine(device.Id);

                foreach (var s in device.MyStringValues)
                    Console.WriteLine($" - {s}");
            }
        }

        Console.ReadKey();
    }
}

public class Device
{
    public Device()
    {
        MyStringValues = new List<string>();
    }

    public string Id { get; set; }
    public IList<string> MyStringValues { get; set; }
}

public class Devices_ByStringValue : AbstractIndexCreationTask<Device>
{
    public override string IndexName => "Devices/ByStringValue";

    public Devices_ByStringValue()
    {
        Map = devices => from device in devices
                          select new { device.MyStringValues };
    }
}

public class DocumentStoreHolder
{
    static DocumentStoreHolder()
    {
        Instance = new DocumentStore
        {
            Url = "http://localhost:8080/",
            DefaultDatabase = "RavenTest",
        };

        Instance.Initialize();
        Serializer = Instance.Conventions.CreateSerializer();
        Serializer.TypeNameHandling = TypeNameHandling.All;

        Instance.Initialize();

        IndexCreation.CreateIndexes(typeof(Devices_ByStringValue).GetTypeInfo().Assembly, Instance);
    }

    public static DocumentStore Instance { get; }

    public static JsonSerializer Serializer { get; }
}

Upvotes: 4

Andrej Krivulč&#237;k
Andrej Krivulč&#237;k

Reputation: 387

Create an index which will contain the data from MyStringValues. It can contain multiple values in an array for each record.

Then you can create an index query and filter only records which contain a given value using .Where(x => x.MyStringValues.Contains(filteredValue)).

Then load all matching documents either using streaming (if the number of matching records can be high) or using Load (of you know the upper limit of documents to load).

To test the index in the studio, you can query the index using a simple MyStringValues:abc query.

Upvotes: 0

Related Questions