C# 优化日历应用程序的查询和/或数据模型
我们的日历应用程序将约会域表示为: 预约C# 优化日历应用程序的查询和/或数据模型,c#,sql,sql-server,linq-to-sql,database-schema,C#,Sql,Sql Server,Linq To Sql,Database Schema,我们的日历应用程序将约会域表示为: 预约 ID(主键) 起始日期 结束日期时间 任命角色 任命ID(FK) PersonOrGroupID(FK)/*加入一个人/团体,超出此问题的范围*/ 角色 约会与AppointmentRoles具有1对多关系。每个任命角色代表特定角色中的一个人或一组人(如下车、接车、参加等) 这种关系有两个目的: 它定义了访问控制列表-经过身份验证的主体只有在其访问控制列表与关联人员或组匹配时才能查看约会 它记录了谁参加了预约以及担任什么角色 还有第三个表用于
- ID(主键)
- 起始日期
- 结束日期时间
- 任命ID(FK)
- PersonOrGroupID(FK)/*加入一个人/团体,超出此问题的范围*/
- 角色
- 任命ID(FK)
List<IAppointment> GetAppointments(IAccess acl, DateTime start, DateTime end, ...
{
// Retrieve distinct appointments that are visible to the acl
var visible = (from appt in dc.Appointments
where !(appt.StartDateTime >= end || appt.EndDateTime <= start)
join role in
(from r in dc.Roles
where acl.ToIds().Contains(r.PersonOrGroupID)
select new { r.AppointmentID })
on appt.ID equals role.AppointmentID
select new
{
...
}).Distinct();
...
最后,我们枚举查询,希望LINQtoSQL将生成一个非常优化的查询(没有后面讨论的幸运)
//将匿名类型封送到IAppoint中
//IAppoint有一个角色和注释集合
var result=新列表();
foreach(q中的var记录)
{
IAppointment a=新任命();
a、 StartDateTime=record.StartDateTime;
...
a、 角色=封送员(record.Roles);
a、 注释=封送员(record.Notes);
结果.添加(a);
}
LINQtoSQL生成的查询非常健谈。它生成一个查询来确定可见的约会。但在每次迭代中,它都会生成三个查询:一个用于选择约会字段,第二个用于选择角色,第三个用于选择备注。where子句始终是可见的约会id
因此,我们正在重构GetAppointment,并认为我们可以从So社区的专业知识中获益
我们希望将所有内容都移动到一个T-SQL存储过程中,这样我们就有了更多的控制权。你能谈谈你如何解决这个问题的想法吗?对数据模型的更改、T-SQL和Linq对SQL的修改都是公平的。我们还希望得到有关索引的建议。我们使用的是MS SqlServer 2008和.NET 4.0。如果我理解正确的话,
约会
有一组角色
和一组注释
。如果是这种情况(并且您在designer中对此进行了正确建模),则在约会
类中有这些角色
和注释
属性。当您更改q
查询的投影(选择select
)并选择约会本身时,您可以帮助LINQ to SQL为您获取以下集合。在这种情况下,您应该按如下方式编写查询:
var q =
from appt in visible
...
select appt;
using (var db = new AppointmentContext())
{
db.LoadOptions.LoadWith<Appointment>(a => a.Roles);
// Do the rest here
}
在此之后,您可以使用DataContext
的LoadOptions
属性为您预取子集合,如下所示:
var q =
from appt in visible
...
select appt;
using (var db = new AppointmentContext())
{
db.LoadOptions.LoadWith<Appointment>(a => a.Roles);
// Do the rest here
}
使用(var db=new AppointmentContext())
{
LoadOptions.LoadWith(a=>a.Roles);
//其余的在这里做
}
然而,这里的一个问题是,我认为LoadWith
仅限于加载一个子集合,而不是两个子集合
您可以通过在两个查询中写出它来解决这个问题。第一个查询是获取约会并使用LoadWith
获取所有角色。然后使用第二个查询(在新的DataContext
中)并使用LoadWith
获取所有注释
祝你好运。我想说,万恶之源始于此:
where acl.ToIds().Contains(r.PersonOrGroupID)
acl.ToIds().Contains(…)
是一个无法在服务器端解析的表达式,因此必须在客户端解析visible
查询(非常不有效),更糟糕的是,必须在客户端保留结果,然后在迭代时,对于每个可见的约会(约会字段、角色和备注),必须向服务器发送不同的查询。如果我有自己的想法,我会创建一个存储过程,它接受ACL列表作为一个索引,并在服务器端执行所有加入/过滤操作
我将从这个模式开始:
create table Appointments (
AppointmentID int not null identity(1,1),
Start DateTime not null,
[End] DateTime not null,
Location varchar(100),
constraint PKAppointments
primary key nonclustered (AppointmentID));
create table AppointmentRoles (
AppointmentID int not null,
PersonOrGroupID int not null,
Role int not null,
constraint PKAppointmentRoles
primary key (PersonOrGroupID, AppointmentID),
constraint FKAppointmentRolesAppointmentID
foreign key (AppointmentID)
references Appointments(AppointmentID));
create table AppointmentNotes (
AppointmentID int not null,
NoteId int not null,
Note varchar(max),
constraint PKAppointmentNotes
primary key (AppointmentID, NoteId),
constraint FKAppointmentNotesAppointmentID
foreign key (AppointmentID)
references Appointments(AppointmentID));
go
create clustered index cdxAppointmentStart on Appointments (Start, [End]);
go
并检索任意ACL的约会,如下所示:
create type AccessControlList as table
(PersonOrGroupID int not null);
go
create procedure usp_getAppointmentsForACL
@acl AccessControlList readonly,
@start datetime,
@end datetime
as
begin
set nocount on;
select a.AppointmentID
, a.Location
, r.Role
, n.NoteID
, n.Note
from @acl l
join AppointmentRoles r on l.PersonOrGroupID = r.PersonOrGroupID
join Appointments a on r.AppointmentID = a.AppointmentID
join AppointmentNotes n on n.AppointmentID = a.AppointMentID
where a.Start >= @start
and a.[End] <= @end;
end
go
1.2秒(在冷缓存上,在热缓存上会变为224毫秒)。嗯,那不太好。问题是约会表中出现了9829页。为了改进这一点,我们希望同时具有两个筛选条件(acl和日期)。也许是索引视图
create view vwAppointmentAndRoles
with schemabinding
as
select r.PersonOrGroupID, a.AppointmentID, a.Start, a.[End]
from dbo.AppointmentRoles r
join dbo.Appointments a on r.AppointmentID = a.AppointmentID;
go
create unique clustered index cdxVwAppointmentAndRoles on vwAppointmentAndRoles (PersonOrGroupID, Start, [End]);
go
alter procedure usp_getAppointmentsForACL
@acl AccessControlList readonly,
@start datetime,
@end datetime
as
begin
set nocount on;
select ar.AppointmentID
, a.Location
, r.Role
, n.NoteID
, n.Note
from @acl l
join vwAppointmentAndRoles ar with (noexpand) on l.PersonOrGroupID = ar.PersonOrGroupID
join AppointmentNotes n on n.AppointmentID = ar.AppointMentID
join Appointments a on ar.AppointmentID = a.AppointmentID
join AppointmentRoles r
on ar.AppointmentID = r.AppointmentID
and ar.PersonOrGroupID = r.PersonOrGroupID
where ar.Start >= @start
and ar.Start <= @end
and ar.[End] <= @end;
end
go
这将返回相同@acl列表中77毫秒内相同日期范围内的约会(在热缓存上)
现在,当然,您应该使用的实际模式取决于许多没有考虑的因素。但我希望这能给你一些想法,让你知道现在应该采取什么样的行动来获得好的表现。将表值参数添加到客户机执行上下文并将其传递给过程,以及LINQ集成,留给读者作为练习
where !(appt.StartDateTime >= end || appt.EndDateTime <= start)
将其从查询中拉出来,要求数据库执行操作毫无意义
List<int> POGIDs = acl.ToIds();
您希望将角色用作筛选器。如果您选择了where,而不是join,那么以后就不必进行区分了
尝试此操作,使用或不使用DataLoadOptions。如果查询没有DataLoadOptions,那么可以使用另一种(更手动的)方法来加载相关行
DataLoadOptions myOptions = new DataLoadOptions();
myOptions.LoadWith<Appointment>(appt => appt.Roles);
myOptions.LoadWith<Appointment>(appt => appt.Notes);
dc.LoadOptions = myOptions;
List<int> POGIDs = acl.ToIds();
IQueryable<Roles> roleQuery = dc.Roles
.Where(r => POGIDs.Contains(r.PersonOrGroupId));
IQueryable<Appointment> visible =
dc.Appointments
.Where(appt => appt.StartDateTime < end && start < appt.EndDateTime)
.Where(appt => appt.Roles.Any(r => roleQuery.Contains(r));
IQueryable<Appointment> q =
visible.OrderBy(appt => appt.StartDateTime);
List<Appointment> rows = q.ToList();
仅供参考:我正计划增加一笔赏金,但看来我得等两天。请把你的问题减到最低限度。如果你想让别人帮助你,请把问题缩短。谢谢。我将使用LoadOptions,看看linq会生成什么。哇。回答得很好。有很多可以玩的。这就是为什么我在上面贴了这么多。你能指出吗
where !(appt.StartDateTime >= end || appt.EndDateTime <= start)
where appt.StartDateTime < end && start < appt.EndDateTime
acl.ToIds().
List<int> POGIDs = acl.ToIds();
join role in
DataLoadOptions myOptions = new DataLoadOptions();
myOptions.LoadWith<Appointment>(appt => appt.Roles);
myOptions.LoadWith<Appointment>(appt => appt.Notes);
dc.LoadOptions = myOptions;
List<int> POGIDs = acl.ToIds();
IQueryable<Roles> roleQuery = dc.Roles
.Where(r => POGIDs.Contains(r.PersonOrGroupId));
IQueryable<Appointment> visible =
dc.Appointments
.Where(appt => appt.StartDateTime < end && start < appt.EndDateTime)
.Where(appt => appt.Roles.Any(r => roleQuery.Contains(r));
IQueryable<Appointment> q =
visible.OrderBy(appt => appt.StartDateTime);
List<Appointment> rows = q.ToList();
List<int> POGIDs = acl.ToIds();
List<Role> visibleRoles = dc.Roles
.Where(r => POGIDs.Contains(r.PersonOrGroupId)
.ToList()
List<int> apptIds = visibleRoles.Select(r => r.AppointmentId).ToList();
List<Appointment> appointments = dc.Appointments
.Where(appt => appt.StartDateTime < end && start < appt.EndDate)
.Where(appt => apptIds.Contains(appt.Id))
.OrderBy(appt => appt.StartDateTime)
.ToList();
ILookup<int, Roles> appointmentRoles = dc.Roles
.Where(r => apptIds.Contains(r.AppointmentId))
.ToLookup(r => r.AppointmentId);
ILookup<int, Notes> appointmentNotes = dc.AppointmentNotes
.Where(n => apptIds.Contains(n.AppointmentId));
.ToLookup(n => n.AppointmentId);
foreach(Appointment record in appointments)
{
int key = record.AppointmentId;
List<Roles> theRoles = appointmentRoles[key].ToList();
List<Notes> theNotes = appointmentNotes[key].ToList();
}
Roles.PersonOrGroupId
Appointments.AppointmentId (should be PK already)
Roles.AppointmentId
Notes.AppointmentId