Кэширование большого количества сущностей в WPF

Я пишу приложение в WPF. Я использую Entity Framework 5 и хотел бы узнать, можете ли вы дать мне совет, как справиться со следующей ситуацией.

У меня есть в основном только три таблицы:

Элемент {ID, имя}

Атрибут {ID, имя, тип}

AttributeValue {ItemID, AttributeID, Значение}

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

Теперь вопрос заключается в том, как отображать элементы в WPF DataGrid и отображать все атрибуты в виде столбцов. Вот что мы имеем сейчас:

Генерируем столбцы программно:

foreach (var attribute in db.Attributes)
{
    datagrid.Columns.Add(new DataGridTextColumn
    {
        Header = attribute.Name,
        Binding = new Binding { Path = new PropertyPath(string.Format("[{0}]", attribute.ID)) }
    });
}

Это прекрасно работает. Теперь вопрос в том, как реализовать индексатор в Items-Class. У нас есть один вариант, который работает довольно медленно при большом количестве данных (20 000 элементов, 400 атрибутов, каждый элемент имеет 100 значений):

public AttributeValue this[int i]
{
    get
    {
        return AttributeValues.FirstOrDefault(aa => aa.AttributID == i);
    }
}

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

var items = db.Items.AsNoTracking().ToArray();
CachedValues.Values = new Dictionary<int, Dictionary<int, AttributeValue>>(items.Length);
foreach (var item in items)
{
    var attributevalues = db.AttributeValue.AsNoTracking().Where(w => w.ArtikelID == item.ID).ToArray();
    CachedValues.Values[item.ID] = new Dictionary<int, AttributeValue>(attributevalues.Length);
    foreach (var value in attributevalues)
    {
        CachedValues.Values[item.ID][value.AttributeID] = value;
    }
}

Я использую статический класс в качестве кеша:

public static class CachedValues
{
    public static Dictionary<int, Dictionary<int, ArtikelAttribut>> Values;
}

И в Items-Class я могу получить доступ к кешу:

public AttributeValue this[int i]
{
    get
    {
        AttributeValue val = null;
        CachedValues.Values[ID].TryGetValue(i, out val);
        return val;
    }
}

Очевидно, что для инициализации кеша требуется некоторое время (15 секунд), но потом это происходит намного быстрее. Сортировка элементов по атрибуту в сетке данных занимает всего секунду. При другом подходе это занимает целую вечность.

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

Спасибо,

Томас

ИЗМЕНИТЬ

Чтобы сделать первый вопрос более понятным, небольшой пример:

Элементы: Элемент1, Элемент2, Элемент3,... Атрибуты: Ширина, Высота, Скорость, ... Значения атрибутов: (Элемент1, Ширина, 100), (Элемент1, Высота, 200), (Элемент2, Ширина, 100), (Элемент3 , Высота, 200), (Элемент3,Скорость,40)

Итак, это классическая связь «многие ко многим». Атрибут может появиться в 0-многих элементах, а элемент может иметь 0-много атрибутов.


person tomfroehle    schedule 23.01.2013    source источник
comment
Эй, просто быстрое замечание, было бы намного быстрее получить все за один раз (с .Include), а не выполнять подзапросы для каждого атрибута.   -  person Not loved    schedule 23.01.2013
comment
ИМХО, вы должны использовать виртуализированное управление (или как оно называется в WPF). Это означает не загружать все данные сразу, а загружать, например, 100 строк и загружать следующие 100 только тогда, когда пользователь прокручивает страницу вниз. Это похоже на пейджинг без списка страниц. WPF должен иметь некоторую поддержку для этого.   -  person Ladislav Mrnka    schedule 23.01.2013
comment
Я использую функции виртуализации DataGrid: (EnableColumnVirtualization=True, EnableRowVirtualization=True)   -  person tomfroehle    schedule 24.01.2013


Ответы (2)


Ваша модель данных - это очень общее решение, и есть аргументы за и против, но трудно судить без дополнительной информации, поэтому я просто пропущу эту часть вопроса.

Я бы, вероятно, реализовал такой же кеш, как и вы, но с небольшими изменениями.

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

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

Последняя мысль - 20 000 предметов с 400 атрибутами каждый? Какой пользователь должен иметь возможность работать с приложением, отображающим до 8 000 000 значений одновременно? Возможно, вам также следует подумать о редизайне пользовательского интерфейса и логики взаимодействия — никому не нужно и не может обрабатывать столько информации одновременно.

person Daniel Brückner    schedule 23.01.2013
comment
Спасибо. Сегодня мы изменили дизайн и используем пейджинг, чтобы показать пользователю только небольшую часть элементов. Я рассмотрю ваши мысли о механизме кэширования. - person tomfroehle; 24.01.2013

Чтобы ответить на ваш первый вопрос: почему Attribute и AttributeValue не объединены? Может ли AttributeValue иметь больше атрибутов? Я предполагаю, что это Предмет, который может иметь несколько Атрибутов.

Элемент {ID, имя}

Атрибут {ID, ItemID, имя, тип, значение}


Я пытался написать вам ответ на ваш вопрос, но, копаясь в вашей проблеме, я начал сомневаться в том, что вы делаете. Вы пытаетесь создать динамическую таблицу в базе данных?*

Вот остальная часть моего ответа:


Вам нужно выяснить, где у вас узкое место в производительности. Получает ли он данные или показывает данные.

15 секунд звучат как много для отображаемых данных. ИМХО, это не должно занимать более 1-2 секунд.

Вот несколько советов:

DataGrid — это тяжеловесный элемент управления, который может многое. Я бы посоветовал вам использовать ListView с GridView и настроить вас в зависимости от того, что вам нужно.

ListView виртуализируется по умолчанию. В 4.0, если вы применяете групповую виртуализацию, она будет отключена. В 4.5 может применяться виртуализация с группировкой.

Оптимизируйте запрос к базе данных с помощью SQL Profiler. Посмотрите, какие запросы выполняются. Если выполняется много запросов, это может привести к снижению производительности. Оперативная загрузка в EF (с .Include) может помочь ускорить процесс. Запомните правильные индексы.

Комментировать комментарий Ладислава Ммки - это не вся правда. Элементы управления WPF не поддерживают виртуализацию данных (данные извлекаются только при отображении). Они поддерживают только обычную виртуализацию, когда все данные присутствуют, но визуализируется только видимая часть элементов. Большая разница между ними. Существуют решения, которые пытаются выполнять виртуализацию данных в WPF.

person Michael    schedule 23.01.2013
comment
На мой взгляд, их не следует объединять. Я объяснил это более подробно в своем отредактированном посте. Я посмотрю на ListView и SQL Profiler. - person tomfroehle; 24.01.2013