2015-04-08 2 views
0

У меня есть класс модуля, Пользователь, UserModule и класс UserModuleLevel.Как более эффективно заполнять коллекцию деталей пользователя

_module_objects является статическим ObservableCollection модулей и создается при запуске программы, из них около 10. например Управление пользователями, Обслуживание клиентов и т. Д.

Пользователь, как вы можете догадаться, это данные пользователя: ID, имя и т. Д. Заполнено из запроса db.

С помощью UserModules я не сохраняю информацию о модуле в db, только уровень модуля, который является только уровнем безопасности модуля. это хранится в db как: User_ID, Module_ID, ModuleLevel, ModuleLevelAccess.

Что я пытаюсь сделать, это заполнить ObservableCollection пользователей самым быстрым способом. У меня около 120 000 пользователей, обычно эти пользователи имеют доступ только к 2 или 3 из 10 модулей.

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

Надеясь на некоторые советы, чтобы ускорить процесс.

public class UserRepository 
{ 
    ObservableCollection<User> m_users = new ObservableCollection<User>(); 

    public UserRepository(){} 

    public void LoadUsers() 
    { 
     var users = SelectUsers(); 
     foreach (var u in users) 
     { 
      m_users.Add(u); 
     } 
    } 

    public IEnumerable<User> SelectUsers() 
    { 
     var userModulesLookup = GetUserModules(); 
     var userModuleLevelsLookup = GetUserModuleLevels().ToLookup(x => Tuple.Create(x.User_ID, x.Module_ID)); 

     clsDAL.SQLDBAccess db = new clsDAL.SQLDBAccess("DB_USERS"); 
     db.setCommandText("SELECT * FROM USERS"); 
     using (var reader = db.ExecuteReader()) 
     { 
      while (reader.Read()) 
      { 
       var user = new User(); 
       var userId = NullSafeGetter.GetValueOrDefault<int>(reader, "USER_ID"); 
       user.User_ID = userId; 
       user.Username = NullSafeGetter.GetValueOrDefault<string>(reader, "USERNAME"); 
       user.Name = NullSafeGetter.GetValueOrDefault<string>(reader, "NAME"); 
       user.Job_Title = NullSafeGetter.GetValueOrDefault<string>(reader, "JOB_TITLE"); 
       user.Department = NullSafeGetter.GetValueOrDefault<string>(reader, "DEPARTMENT"); 
       user.Company = NullSafeGetter.GetValueOrDefault<string>(reader, "COMPANY"); 
       user.Phone_Office = NullSafeGetter.GetValueOrDefault<string>(reader, "PHONE_OFFICE"); 
       user.Phone_Mobile = NullSafeGetter.GetValueOrDefault<string>(reader, "PHONE_MOBILE"); 
       user.Email = NullSafeGetter.GetValueOrDefault<string>(reader, "EMAIL"); 

       user.UserModules = new ObservableCollection<UserModule>(userModulesLookup); 

       //**************** BOTTLENECK ********************************** 
       foreach (var mod in user.UserModules) 
       { 
        mod.UserModuleLevels = new ObservableCollection<UserModuleLevel>(userModuleLevelsLookup[Tuple.Create(userId, mod.Module.Module_ID)]); 
       } 
       //************************************************************** 

       yield return user; 
      } 
     } 
    } 

    private static IEnumerable<Users.UserModule> GetUserModules() 
    { 
     foreach (Module m in ModuleKey._module_objects) 
     { 
      //Set a reference in the UserModule to the original static module. 
      var user_module = new Users.UserModule(m); 
      yield return user_module; 
     } 
    } 

    private static IEnumerable<Users.UserModuleLevel> GetUserModuleLevels() 
    { 
     clsDAL.SQLDBAccess db_user_module_levels = new clsDAL.SQLDBAccess("DB_USERS"); 
     db_user_module_levels.setCommandText(@"SELECT * FROM USER_MODULE_SECURITY"); 
     using (var reader = db_user_module_levels.ExecuteReader()) 
     { 
      while (reader.Read()) 
      { 
       int u_id = NullSafeGetter.GetValueOrDefault<int>(reader, "USER_ID"); 
       int m_id = NullSafeGetter.GetValueOrDefault<int>(reader, "MODULE_ID"); 
       int ml_id = NullSafeGetter.GetValueOrDefault<int>(reader, "MODULE_LEVEL_ID"); 
       int mla = NullSafeGetter.GetValueOrDefault<int>(reader, "MODULE_LEVEL_ACCESS"); 

       yield return new Users.UserModuleLevel(u_id, m_id, ml_id, mla); 
      } 
     } 
    } 
} 

В конце концов я положу пользователь в DataGrid с безопасностью модуля отображается кнопки с зеленым шоу есть некоторый тип доступа к этому модулю, щелкнув на нем появятся фактические параметры безопасности. enter image description here

+0

Как вы определили свой 'USER_MODULE_SECURITY'? если вы сохраняете только уровни безопасности модулей для каждого пользователя, тогда, когда вы присоединяетесь к 'USERS' с' USER_MODULE_SECURITY', вы можете просто работать с одной таблицей? – Bolu

+0

[this] (http://www.codeproject.com/Articles/34405/WPF-Data-Virtualization) может помочь, но требует ваших усилий, потому что это длинная статья. – kennyzx

+1

Похоже, что вы можете легко получить эту информацию в одном запросе, а не запрашивать эти две таблицы и использовать подход scissors/glue в коде приложения (SQL может быть на порядок быстрее при склеивании вещей). Как долго это происходит, чтобы пройти через список - и почему вам нужно обрабатывать всех 120 000 пользователей одновременно? Как насчет стратегии поискового вызова или частичной загрузки? – Charleh

ответ

2

Для прироста производительности вы можете сделать несколько вещей:

  • Изменить код доступа к данным для выполнения JOIN и в SQL, чтобы получить данные в виде одного набора результатов.

    SQL имеет тенденцию быть справедливым бит быстрее при возврате результирующего набора реляционных данных, чем C# при склеивании данных вместе после факта. Это связано с тем, что оно оптимизировано для этого, и вы должны это использовать, и вы должны воспользоваться этим.

  • Возможно, вам стоит подумать о подкачке результатов - любой пользователь, который говорит, что им нужно все 120 000 результатов сразу, следует ударить по голове большой форелью , Пейджинг результатов будет ограничивать объем обработки, что вам нужно сделать в приложении

Doing выше, может быть весьма сложными, как вам нужно будет модифицировать приложение, чтобы включить подкачку - часто 3-й контроль партии, такие как сетки и т. д., в наши дни большинство программ ORM имеют некоторую поддержку подкачки, которая переводит ваш код C# на правильный диалект для выбранной вами RDBMS.

Хороший пример (я работал немного в последнее время) является ServiceStack OrmLite.

Я считаю, что это бесплатно, если вы используете устаревшую версию V3 (которая довольно чертовски хороша ..https://github.com/ServiceStackV3/ServiceStackV3), и я видел некоторые вилы него на GitHub, которые в настоящее время поддерживается (http://www.nservicekit.com/)

Существует небольшой кривой обучения, но ничего примеры/документы не могут сказать вам

Вот метод расширения Я использую на странице мои вопросы в моем слое службы:

public static SqlExpressionVisitor<T> PageByRequest<T>(this SqlExpressionVisitor<T> expr, PagedRequest request) 
{ 
    return expr.Limit((request.PageNumber - 1) * request.PageSize, request.PageSize); 
} 

запрос содержит номер страницы и размер страницы (с моей веб-приложение), а также метод Limit расширение в OrmLite делает все остальное. Вероятно, я должен добавить, что общий параметр <T> - это тип объекта, который OrmLite будет отображать после запроса.

Вот пример того, что (его просто POCO с некоторыми аннотациями)

[Alias("Customers")] 
public class Customer : IHasId<string> 
{ 
    [Alias("AccountCode")] 
    public string Id { get; set; } 

    public string CustomerName { get; set; } 
    // ... a load of other fields 
} 

Метод переведен на T-SQL и результаты в следующем запросе против DB (для этого примера я выбрал страница 4 в моем списке клиентов с размером страницы 10):

SELECT <A big list of Fields> FROM 
(SELECT ROW_NUMBER() OVER (ORDER BY AccountCode) As RowNum, * FROM "Customers") 
AS RowConstrainedResult 
WHERE RowNum > 40 AND RowNum <= 50 

Это сохраняет время запроса до пути меньше, чем второй и обеспечивает мне не нужно писать shedload от конкретного поставщика SQL

Это действительно зависит от того, сколько приложений у вас уже есть - если вы слишком далеко, это может быть кошмар для рефакторинга для ORM, но его стоит рассмотреть для других проектов.

+0

Хорошо, что это может быть слишком далеко вперед, однако я собирался внедрить инструмент типа multiedit, чтобы выбрать диапазон пользователей и установить на них защиту пользовательского модуля. Это было бы довольно просто, если бы у меня были пользователи в памяти, если я загружаю и выгружаю пользователей, это все еще возможно? Я еще не прочитал много, но это первое, что приходит на ум. – Hank

+0

Возможно, вы все равно можете загрузить всех пользователей - 120 000 записей на самом деле не так много, если предположить, что вы возвращаете только небольшое количество полей (например, имя пользователя и пару строк наборов разрешений). Когда вы говорите, что ваш код медленный, сколько времени требуется, чтобы завершить сборку списка пользователей/разрешений? Возможно, из-за ручного хруста в .NET вы наблюдаете массовую потерю эффективности. – Charleh

+0

Я вернусь к вам завтра с некоторыми цифрами. Midnight in Aussie прямо сейчас – Hank

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