我有一个
Module
班、一个 User
、一个 UserModule
和一个 UserModuleLevel
班。
_module_objects
是一个静态的ObservableCollection
模块,在程序启动时创建,大约有10个。例如用户管理、客户服务等
User
,您可能会猜到是用户详细信息:ID、名称等。从数据库查询填充。
使用
UserModules
,我不会将模块信息保留在数据库中,只保留模块级别,这只是模块安全级别。这在数据库中保存为:User_ID、Module_ID、ModuleLevel、ModuleLevelAccess。
我想做的就是以最快的方式填充
ObservableCollection
的用户。我有大约 120,000 个用户,通常这些用户只能访问 10 个模块中的 2 到 3 个。
下面是我到目前为止所尝试过的,但是带有星号的部分是瓶颈,因为它会遍历每个用户的每个模块。
希望得到一些建议来加快速度。
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 中,绿色按钮显示对该模块有某种类型的访问权限,单击它将显示实际的安全设置。
为了提高性能,您可以做一些事情:
更改数据访问代码以在 SQL 中执行 JOIN,以将数据作为单个结果集获取。
SQL 在返回关系数据结果集方面往往比 C# 在事后将数据粘合在一起方面要快一些。这是因为它经过优化就是为了做到这一点,你应该利用这一点
您可能应该考虑对结果进行分页 - 任何说他们一次需要所有 120,000 个结果的用户都应该用一条大鳟鱼来打头。对结果进行分页将限制您需要在应用程序中执行的处理量
执行上述操作可能非常艰巨,因为您需要修改应用程序以包含分页 - 通常第 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);
}
请求包含页码和页面大小(来自我的网络应用程序),OrmLite 中的
Limit
扩展方法完成其余的工作。我可能应该补充一点,<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,并产生以下针对数据库的查询(在本示例中,我选择了客户列表中的第 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
这将查询时间缩短到不到一秒,并确保我不需要编写大量供应商特定的 SQL
这实际上取决于您已经获得了多少应用程序 - 如果您太深入,那么为 ORM 重构可能是一场噩梦,但对于其他项目来说值得考虑