ФОН:SQL Sever 2008 и Entity Framework - Повышение производительность
У меня есть службы Windows, которая тяговая запись офф SQL таблицы (созданная с EF Code First Method). Записи добавляются очень часто (~ 10-20 в секунду) двумя клиентами, которые затем очищаются от базы данных и обрабатываются моей службой. Для резервирования есть два клиента, которые контролируют одни и те же системы и могут создавать дубликаты записей. Я ищу способ улучшить производительность программы, которая обрабатывает новые записи.
ПРОБЛЕМА:
Шаг 1: удалить дубликаты записей:
// get duplicate entries
var duplicateEntities = context.OpcTagValueLogs.GroupBy(x => new { x.OpcTagId, x.SourceTimeStamp }).SelectMany(x => x.OrderBy(y => y.Id).Skip(1));
foreach (var duplicateEntry in duplicateEntries)
{
// remove duplicate entries
context.OpcTagValueLogs.Remove(duplicateEntry);
}
Шаг 2: Получить остальные записи журнала
var logs = context.OpcTagValueLogs.Include(x => x.OpcTag).Include(x => x.OpcTag.RuleSets).ToList();
Шаг 3: Проверить соответствующие правила и выполнять события, чтобы обработать новые значения
Я пытаюсь оптимизировать свою программу столько, сколько po потому что прямо сейчас служба Windows, которая обрабатывает данные, работает не быстрее, чем создаются записи. Если скорость создания записей увеличивается, я беспокоюсь, что услуга не сможет идти в ногу со временем.
Это единственные запросы, которые я запускаю (помимо создания записей) в этой таблице. Структура таблицы:
- [INT] Идентификатор (Первичный ключ, Кластерный: Id)
- [INT] OpcTagId (IX_OpcTagId)
- [DATETIME] TimeStamp
- [NVARCHAR (MAX)] Значение
- [INT] SourceTimeStamp (IX_SourceTimeStamp)
- [NVARCHAR (MAX)] ClientCode
- [NVARCHAR (MAX)] PriorValue
Есть ли способ изменить мои индексы, чтобы улучшить производительность моих запросов?
EDIT: Это то, как бревна обрабатываются после того, как дубликаты удаляются:
foreach (var log in logs.ToList()) // because items will be removed from the list during the loop, it is important to update the list on
{ // each iteration, hence the .ToList()
if (log.PriorValue == log.Value) // check to see if the prior value equals to new value
{ // I am only interested in changing values, so delete the log entry
// remove the entity
_context.OpcTagValueLogs.Remove(log);
logs.Remove(log);
_context.SaveChanges();
}
else
{
// check rules and perform actions
var ruleSets = log.OpcTag.RuleSets.ToList();
foreach (var ruleSet in ruleSets)
{
if (ruleSet.CheckRule(log.PriorValue, log.Value))
{
// perform action
// convert source timestamp to datetime
DateTime srcTS = new DateTime(1970, 1, 1).AddSeconds(log.SourceTimeStamp);
var action = ActionFactory.CreateAction(ruleSet.Action, log.PriorValue, log.Value, log.OpcTag, srcTS);
action.Execute();
}
}
// remove entity from database
_context.OpcTagValueLogs.Remove(log);
_context.SaveChanges();
logs.Remove(log); // remove the entity from the local list as well
}
}
EDIT 2: Текущий метод
var ruleSets = _context.RuleSets.ToList(); // Get entire rulesets once into memory
var logsLocal = logs.ToList(); // bring all the logs into local memory
var maxIndex = logsLocal.Max(x => x.Id); // the last index of the local logs
foreach (var log in logsLocal)
{
if (log.PriorValue != log.Value)
{
foreach (var ruleSet in ruleSets.Where(x => x.OpcTagId == log.OpcTagId))
{
if (ruleSet.CheckRule(log.PriorValue, log.Value))
{
// perform action
var action = ActionFactory.CreateAction(ruleSet.Action, log.PriorValue, log.Value, log.OpcTag, srcTS);
action.Execute();
}
}
}
}
_context.OpcTagValueLogs.Where(x=>x.Id <= maxIndex).Delete(); // batch delete only the logs that were processed on this program loop
РЕДАКТИРОВАТЬ 3: Объект действия производится с помощью статического ActionFactory класса на основе значения ruleSet.Action.
public static Action CreateAction(ActionId pActionId, string pPrior, string pNew, OpcTag pOpcTag, DateTime pSourceTimestamp)
{
Action evt = null;
switch (pActionId)
{
case ActionId.A1000: evt = new A1000(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
case ActionId.A1001: evt = new A1001(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
case ActionId.A1002: evt = new A1002(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
case ActionId.A1003: evt = new A1003(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
case ActionId.A1004: evt = new A1004(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
}
return evt;
}
Каждый из этих действий представляет собой другое событие машины и может быть несколько сотен строк кода каждой (именно поэтому он был опущен).
При вызове 'ToList()' вы эффективно получить все дети (отношение), поэтому это не лень. Почему вы делаете 'ToList()'? Кроме того, для первой проблемы вы должны просто использовать библиотеку EF Extend и делать «массовое удаление» на всех дубликатах, а не делать это один за другим. – sed
Спасибо, я проверю эту библиотеку. Я понял, что, вызвав ToList, я приведу записи в память и уменьшу количество последующих запросов к базе данных. Итак, я думал, что это поможет производительности ... – jaredcnance
Да, но это действительно отстой, потому что он вытаскивает все, даже 9/10 вещей, которые вам на самом деле не нужны. Не беспокойтесь и не обходите ленивую загрузку, без 'ToList();' она будет фактически запрашивать базу данных ** только **, когда/если используются данные. – sed