Как улучшить производительность кода Entity Framework? - PullRequest
0 голосов
/ 22 февраля 2019

В проекте мне нужно вызывать внешний API в зависимости от времени.Таким образом, в течение одного дня мне может понадобиться вызвать API 24 раза, один вызов - один час.Результатом API является файл XML, который имеет 6 полей.Мне нужно будет вставить эти данные в таблицу.В среднем за каждый час в нем содержится около 20 000 строк данных.

Таблица имеет следующие 6 столбцов:

col1, col2, col3, col4, col5, col6

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

Я использую C # и Entity Framework для этого:

foreach (XmlNode node in nodes)
{
    try
    {
        count++;

        CallData data = new CallData();
        ...
        // get all data and set in 'data'

        // check whether in database already                        
        var q = ctx.CallDatas.Where(x => x.col1 == data.col1
                    && x.col2 == data.col2
                    && x.col3 == data.col3
                    && x.col4 == data.col4
                    && x.col5 == data.col5
                    && x.col6 == data.col6
                ).Any();
        if (q)
        {
            // exists in database, skip
            // log info
        }
        else
        {
            string key = $"{data.col1}|{data.col2}|{data.col3}|{data.col4}|{data.col5}|{data.col6}";
            // check whether in current chunk already
            if (dic.ContainsKey(key))
            {
                // in current chunk, skip
                // log info
            }
            else
            {
                // insert
                ctx.CallDatas.Add(data);

                // update dic
                dic.Add(key, true);
            }
        }
    }
    catch (Exception ex)
    {
        // log error
    }
}
Logger.InfoFormat("Saving changes ...");
if (ctx.ChangeTracker.HasChanges())
{
    await ctx.SaveChangesAsync();
}
Logger.InfoFormat("Saving changes ... Done.");

Код работает нормально.Однако нам нужно будет использовать этот код для запуска в течение последних нескольких месяцев.Проблема в том, что код работает медленно, поскольку для каждой строки необходимо проверить, существует ли он уже.

Есть ли какие-либо предложения по повышению производительности?

Спасибо

Ответы [ 3 ]

0 голосов
/ 22 февраля 2019

Другой подход:

Сохранение всех строк с дубликатами - должно быть очень эффективным

Когда вы используете данные из таблицы, используйте DISTINCT для всех полей.

0 голосов
/ 22 февраля 2019

Для необработанных, массовых операций, подобных этой, я хотел бы рассмотреть возможность избегать EF-сущностей и отслеживания контекста и просто выполнять SQL через контекст:

var sql = $"IF NOT EXISTS(SELECT 1 FROM CallDates WHERE Col1={data.Col1} AND Col2={data.Col2} AND Col3={data.Col3} AND Col4={data.Col4} AND Col5={data.Col5} AND Col6={data.Col6}) INSERT INTO CallDates(Col1,Col2,Col3,Col4,Col5,Col6) VALUES ({data.Col1},{data.Col2},{data.Col3},{data.Col4},{data.Col5},{data.Col6})";
context.Database.ExeculeSqlCommand(sql);

Это обходится без дополнительных проверок и журналирования, просто эффективно обрабатывает SQL собнаружение дубликатов.

0 голосов
/ 22 февраля 2019

Код не отображается при создании контекста или жизненном цикле.Я склонен указывать вам на ваши индексы на столе.Если это не первичные ключи, то вы можете увидеть проблему с производительностью.Если вы выполняете полное сканирование таблицы, оно будет постепенно замедляться.С учетом вышесказанного существует два отдельных способа обработки

Собственного способа EF: вы можете явно создавать новое соединение при каждом взаимодействии (избегая отслеживания изменений для всех записей, уменьшая прогрессивное замедление).Кроме того, ваше сохранение является асинхронным, но ваше * Любое утверждение является синхронизированным.Использование async для этого также может помочь снять некоторое давление с текущего потока, если он ожидает.

// Start your context scope closer to the data call, as if the look is long 
// running you could be building up tracked changes in the cache, this prevents 
// that situation.
using (YourEntity ctx = new YourEntity())
{
    CallData data = new CallData();
    if (await ctx.CallDatas.Where(x => x.col1 == data.col1
        && x.col2 == data.col2
        && x.col3 == data.col3
        && x.col4 == data.col4
        && x.col5 == data.col5
        && x.col6 == data.col6
        ).AnyAsync()
        )
    { 
        // exists in database, skip
        // log info
    }
    else
    {
        string key = $"{data.col1}|{data.col2}|{data.col3}|{data.col4}|{data.col5}|{data.col6}";
        // check whether in current chunk already
        if (dic.ContainsKey(key))
        {
            // in current chunk, skip
            // log info
        }
        else
        {
            // insert
            ctx.CallDatas.Add(data);
            await ctx.SaveChangesAsync();
            // update dic
            dic.Add(key, true);
        }
    }
}

Необязательный способ: посмотрите на вставку данных с помощью массовой операции через процедуру сохранения.20 тыс. Строк - это тривиально, и вы все равно можете использовать для этого и структуру сущностей.См. https://stackoverflow.com/a/9837927/1558178

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

Iиспользовал эту идеологию для вставки 100 тыс. записей одновременно.У меня есть логика в хранимой процедуре проверки на наличие дубликатов, которая дает мне лучший контроль, а также сокращает вызов по проводной связи до 0 операций чтения и 1 записи.Это займет всего одну или две секунды, если ваша хранимая процедура оптимизирована.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...