Cosmos CreateDocumentQuery linq Производительность в зависимости от состояния

У меня есть коллекция космоса, имеющая где-то около 28000 документов, и я использую CreateDocumentQuery на DocumentClient с условием where для свойств типа 'T'. С различными типами использования, упомянутыми ниже, я получаю очень резкую разницу во времени при получении результатов.

Случай 1:

    var docs2 = 
    _documentClient.CreateDocumentQuery<HeartRateDayRecordIdentifierData>(collectionUri).Where(x =>
           x.SubjectDeviceInformation.StudyId == "TestStudy"
           && x.SubjectDeviceInformation.SiteId == "Site_._Street_23"
           && x.SubjectDeviceInformation.SubjectId == "Subject3"
           && x.SubjectDeviceInformation.DeviceId == "Device1"
           && x.DaySplit == "20181112").AsEnumerable().FirstOrDefault(); 

Случай 2: Это тот же код и условие, но на этот раз я использую переменную функции для удаления условия where.

Func<HeartRateDayRecordIdentifierData, bool> searchOptions = x =>
        x.SubjectDeviceInformation.StudyId == "TestStudy"
        && x.SubjectDeviceInformation.SiteId == "Site_._Street_23"
        && x.SubjectDeviceInformation.SubjectId == "Subject3"
        && x.SubjectDeviceInformation.DeviceId == "Device1"
        && x.DaySplit == "20181112";

var docs1 = _documentClient.CreateDocumentQuery<HeartRateDayRecordIdentifierData>(collectionUri)
                        .Where(searchOptions).AsEnumerable().FirstOrDefault();

Случай 1, который имеет встроенный метод, где условие возвращает результаты в промежуток времени менее секунды, где, как и в случае 2, результат занимает около 20-30 секунд, что кажется немного странным. Я не понимаю, в чём разница между наличием встроенного условия и передачей условия в качестве допустимого.

Если кто-то заинтересовался образцом космического документа:

{
    "id": "TestStudy_Site_._Street_21_Subject1_Device1_20181217",
    "AssemblyVersion": "1.2.3.0",
    "DataItemId": "20181217/TestStudy_Site_._Street_21_Subject1_Device1_20181217",
    "MessageType": "HeartRateDayDocumentIdentifier",
    "TimeStamp": "2018-12-14T00:00:00",
    "DaySplit": "20181217",
    "SubjectDeviceInformation": {
        "SubjectId": "Subject1",
        "DeviceId": "Device1",
        "StudyId": "TestStudy",
        "SiteId": "Site_._Street_21"
    }   
}

и Вот модель, используемая для десериализации документа: внутренний класс HeartRateDayRecordIdentifierData { public string id { get; задавать; }

    public string AssemblyVersion { get; set; }

    public string DataItemId { get; set; }

    public string MessageType { get; set; }

    public DateTime TimeStamp { get; set; }

    public string DaySplit { get; set; }

    public SubjectDeviceInformation SubjectDeviceInformation { get; set; }
}

internal class SubjectDeviceInformation
{
    public string SubjectId { get; set; }

    public string DeviceId { get; set; }

    public string StudyId { get; set; }

    public string SiteId { get; set; }
}

Любые предложения по поводу чего-то плохого я делаю здесь.

1 ответ

Решение

В обоих случаях вы делаете это неоптимальным образом.

Вы хотите сначала или ноль, если нет совпадения.

Однако вы делаете синхронный перекрестный запрос запроса, вызывая AsEnumerable().FirstOrDefault(),

Кроме того, ваш пункт where должен быть Expression<Func<HeartRateDayRecordIdentifierData, bool>> вместо Func,

В обоих случаях происходит то, что сначала вы возвращаете все данные в CosmosDB, а затем LINQ выполняет фильтрацию в памяти, чтобы вернуть вам данные.

Вместо этого вы должны использовать while(query.HasMoreResults) а также query.ExecuteNextAsync() способы вернуть ваши данные обратно.

Вот как должен выглядеть ваш запрос:

public async Task<HeartRateDayRecordIdentifierData> GetSomethingAsync()
{
    var query = 
        _documentClient.CreateDocumentQuery<HeartRateDayRecordIdentifierData>(collectionUri).Where(x =>
               x.SubjectDeviceInformation.StudyId == "TestStudy"
               && x.SubjectDeviceInformation.SiteId == "Site_._Street_23"
               && x.SubjectDeviceInformation.SubjectId == "Subject3"
               && x.SubjectDeviceInformation.DeviceId == "Device1"
               && x.DaySplit == "20181112").AsDocumentQuery();

    while(query.HasMoreResults)
    {
        var results = await query.ExecuteNextAsync();
        if(results.Any())
            return results.First();     
    }          

    return null;
}

Таким образом, SDK, который выполняет минимально необходимое количество вызовов для сопоставления данных и не будет запрашивать все возможные документы.

Дайте мне знать, если вам нужно какое-либо дальнейшее объяснение, потому что это довольно сложно, и примеры не очень помогают в этом.

Вы также можете абстрагировать все это и просто использовать ваши объекты и .FirstOrDefaultAsync метод, если вы используете космонавта. Таким образом, весь ваш код может измениться на это:

public async Task<HeartRateDayRecordIdentifierData> GetSomethingAsync()
{
    return await cosmosStore.Query().Where(x =>
                   x.SubjectDeviceInformation.StudyId == "TestStudy"
                   && x.SubjectDeviceInformation.SiteId == "Site_._Street_23"
                   && x.SubjectDeviceInformation.SubjectId == "Subject3"
                   && x.SubjectDeviceInformation.DeviceId == "Device1"
                   && x.DaySplit == "20181112").FirstOrDefaultAsync();
}

Вы можете сами выбрать, какой путь выбрать для вас.Отказ от ответственности, я создатель космонавта.

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