Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/277.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 优化日历应用程序的查询和/或数据模型_C#_Sql_Sql Server_Linq To Sql_Database Schema - Fatal编程技术网

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)/*加入一个人/团体,超出此问题的范围*/
  • 角色
约会与AppointmentRoles具有1对多关系。每个任命角色代表特定角色中的一个人或一组人(如下车、接车、参加等)

这种关系有两个目的:

  • 它定义了访问控制列表-经过身份验证的主体只有在其访问控制列表与关联人员或组匹配时才能查看约会
  • 它记录了谁参加了预约以及担任什么角色
  • 还有第三个表用于跟踪与约会相关的注释/评论。这是一对多预约关系的多方面:

    委任书

    • 任命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