2011-01-20 4 views
2

В настоящее время мы используем LINQ для генерации SQL-запросов с небольшим количеством магии внутри для обработки запросов к конкретным случаям.Оптимизация конкатенации строк

До сих пор это нормально работало; очень быстро, почти никаких проблем. Недавно мы столкнулись с проблемами эффективности при запросе большого количества данных из базы данных.

Построим запрос как таковой:

var someIntList = new List<int> { 1,2,3,4,5 }; 
var query = dtx.Query.Containers.Where(c => c.ContainerID.IsIn(someIntList)); 

или

var someStringList = new List<int> {"a", "b", "c" }; 
query = dtx.Query.Containers.Where(c => c.BuildingName.IsIn(someStringList)); 

Какой бы генерировать (вместе с кучей других вещей, не связанных с этим):

SELECT * FROM Container WHERE ContainerID IN (1,2,3,4,5) 

и

SELECT * FROM Container WHERE BuildingName IN ('a','b','c') 

Теперь в этой конкретной ситуации нам нужно вернуть 50 000 строк, которые генерируются через 5 отдельных запросов, разделяя нагрузку. DB возвращается довольно быстро (в течение нескольких секунд), однако генерация запроса занимает long времени.

Вот самое последняя функция, которая вызывается для создания этого конкретного запроса:

private static string GetSafeValueForItem(object item) 
{ 
    if (item == null) 
     return "NULL"; 

    if (item is bool) 
     return ((bool)item ? "1" : "0"); 
    if (item is string) 
     return string.Format("'{0}'", item.ToString().Replace("'", "''")); 
    if (item is IEnumerable) 
     return ListToDBList((IEnumerable)item); 
    if (item is DateTime) 
     return string.Format("'{0}'", ((DateTime)item).ToString("yyyy-MM-dd HH:mm:ss")); 

    return item.ToString(); 
} 

private static string ListToDBList(IEnumerable list) 
{ 
    var str = list.Cast<object>().Aggregate("(", (current, item) => current + string.Format("{0},", GetSafeValueForItem(item))); 
    str = str.Trim(','); 
    str += ")"; 
    return str; 
} 

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

+0

Не знаю, почему ты 'do list.Cast , когда обычный IEnumerable будет иметь объекты в любом случае. – Massif

ответ

5

Ваш сводный код в основном представляет собой конкатенацию строк в цикле. Не делай этого.

Варианты:

  1. Использование StringBuilder
  2. Используйте string.join
+2

Чтобы объяснить причину этого: string concatenation выделяет новый буфер памяти для каждой конкатенации, копирует BOTH строку и назначает новый буфер переменной. Зацикливание по длинному списку приводит к массовому распределению памяти и копированию, что очень дорогостоящее performancevize. StringBuilder и string.join предварительно вычисляют общее пространство, необходимое для всех элементов, и копируют их только один раз. –

1

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

Используйте StringBuilder вместо String.Format и оператор + =. Известно, что оператор + = медленный. Я подозреваю, что String.Format тоже будет несколько медленным.

Вы также можете попробовать string.Join вместо ручного соединения массива. Он работает в IEnumerable в новых версиях .NET framework (4.0?).

+0

String.Format использует StringBuilder внутри (http://stackoverflow.com/questions/6785/is-string-format-as-efficient-as-stringbuilder), но поскольку он используется много раз, я согласен с вашей точкой. –

+0

Несколько string.format будут такими же медленными, как + =, поскольку вы повторно размещаете новую memry и копируете данные. String builder выделяет память и копирует в конце, когда вы вызываете вызов ToString. –

0

Не знаете, почему вы делаете list.Cast, когда обычный IEnumerable будет иметь объекты в любом случае. Но вся ваша ListToDBList может быть заменен

string.Format("({0})", string.Join(",",list.ToArray())); 

Не уверен, насколько быстро это будет, но это яснее, на мой взгляд.

+0

Листинг требуется, потому что IEnumerable не реализует IEnumerable . (Это не могло быть - это круговой граф наследования.) По крайней мере, для меня на .NET 3.5 я не могу применять любые методы расширения Linq для IEnumerable, не используя сначала Cast, чтобы преобразовать его в IEnumerable . – Weeble

2

Вот пример использования String.Присоединяйтесь, который выдает то же самое, как ваш ListToDBList:

String.Format("({0})", String.Join(",", list.Cast<object>().Select(item=>GetSafeValueForItem(item)).ToArray())); 

Смотрите здесь для объяснения, почему конкатенации в цикле, используя + (который является тем, что делает ваш вызов агрегат) медленно: http://www.yoda.arachsys.com/csharp/stringbuilder.html

Смежные вопросы