使用SQL Server,EF6保存静态列表的最佳方法

时间:2014-11-26 21:12:34

标签: c# sql entity-framework tsql search

我开始重写现有的应用程序。当前性能瓶颈之一是将用户生成的Employees列表保存到静态列表中,以便他们可以在以后返回并查看相同的员工列表。

以下是我正在寻找的功能性的简单示例。生成的列表将通过比示例中更复杂的查询。

场景: 用户在夜班时搜索所有员工,并希望保存此列表以便稍后加载。他们希望列表始终返回结果,因为这是他们第一次运行搜索。即如果新员工被添加到夜班,他们在上拉时不应出现在列表中。


我尝试过:

目前,将结果列表中的所有ID存储为字符串数组,然后使用这些ID重建查询的解决方案非常糟糕。这是非常低效的并且导致具有太多参数的大列表的问题。

我还玩过从已保存的ID数组中构建表然后加入,但是,这在大型列表(20,000多名员工)上非常慢,并且经常导致超时。

我目前的想法是为每个列表创建一个新表,然后在该表和Employee表上调用JOIN。但是,如果100个用户各自保存10个大型列表(20,000多名员工),它很快就会成为存储和表管理的噩梦。

我认为这是一个解决方案相当普遍的问题。但是,我还没有找到任何关于如何存储静态列表的示例或最佳实践(我可能正在寻找错误的东西)。有没有人对如何最好地处理这种用例场景有任何一般概念?

更新 我想我以前尝试过以下设置,但是由于某种原因它不起作用,这是几年前的事了;但回顾它似乎最有意义。我认为我遇到的问题是NHibernate在Linq中遇到子选择问题。但是,这不再是一种限制。

我想我有一个StaticSavedLists的表和索引表链接Person(前一个例子中的Employee),它将List链接到Employee中的多对多映射。 c#中的类看起来像这样:

public class StaticSavedList : BaseModel
{
    public string Name { get; set; }
    public IList<StaticSavedListPersonIdx> PersonsIdx { get; set; } //Has many persons
}

public class StaticSavedListPersonIdx : BaseModel
{
    public StaticSavedList StaticSavedList { get; set; }
    public Person Person { get; set; }
}

2 个答案:

答案 0 :(得分:2)

您可能需要有1个表格,其中包含搜索的“标题”详细信息,其中包含搜索的ID,然后是包含每个搜索条目的第二个表。然后你只需要以某种方式获取ID(可能是通过用户ID和转移日期)并使用它来加入结果和员工表。

以下是架构的外观

ShiftSearches

SearchID    int (PK)
ShiftDate   datetime

SearchResult所

SearchID    int (PK)
EmployeeID  int (PK)

员工

EmployeeID  int (PK)
FirstName   varchar
etc ...

可能的LINQ查询

DateTime shiftDate = new DateTime(2014,11,26);
int searchId = db.ShiftSearches.Single(s => s.ShiftDate == shiftDate).SearchID;

var results = from r in db.SearchResults where r.SearchID == searchId
    join e in Employees on r.EmployeeID equals e.EmployeeID
    select e;

这样你只需要一张桌子,它非常薄,所以不应占用太多空间 - 因为你只是存储了所需的搜索和员工ID,你无论如何都无法真正获得更小的数据。

您发布的类结构几乎与此概念相符。

答案 1 :(得分:1)

在所有情况下,实体框架可能不是适当的技术选择,并且一次处理20k行的批次可能是这些情况之一。

但是,我相信您的数据模型设计是一个很好的设计。下面,在Sql Only上,可以看出包含(ListId, EmployeeId)对的60k行可以在一秒钟内插入到一个明智的聚类ListSearchEmployee表中,随后,一个20k行的列表可以是从冷启动开始,在1.8秒内连接回Employee行。

性能瓶颈更可能是原始用户搜索 - 可能这可能是针对您的Employees +相关表执行的几乎任意查询,这对于所有排列都很难索引。

一些性能建议(对于List save + refetch):

  • 使用SqlBulkCopy将EmployeeId批量转储到List表
  • 使用低级别的内容,例如基本的SqlReader来获取用户界面的列表数据(虽然我猜它会被分页? - 如果是这样,DbSet.SqlQuery AsNoTracking()关闭就足够了。
  • 不要打扰列表中的外键 - 这会减慢插入速度。
  • 不要尝试同步清除不需要的旧List搜索 - 将它们排队等待删除,并有后台进程进行删除。
  • 因此,ListSearchEmployee表经常需要重新索引,因为它有大量的流失。
-- Sample data setup - not considered in the timing
CREATE TABLE Employee
(
  EmployeeID INT identity(1,1),
  Name NVARCHAR(100),
  SomeOtherFieldToLessenTheDensityOfEmployee CHAR(500),

  PRIMARY KEY(EmployeeID)
);

CREATE TABLE ListSearch
(
  ListSearchID INT IDENTITY(1,1) PRIMARY KEY
  -- Other fields you may want to identify the search, e.g. date, which user, which filters etc
)

CREATE TABLE ListSearchEmployee
(
  ListSearchID INT,
  EmployeeID INT, -- Don't bother Foreign Keying for performance

  PRIMARY KEY CLUSTERED (ListSearchID, EmployeeID)
);

-- Insert 1M Employees
WITH cteData AS
(
   SELECT top 1000000 sc1.name, ROW_NUMBER() OVER (ORDER BY sc1.object_id) AS rn
   FROM sys.columns sc1 CROSS JOIN sys.columns sc2 CROSS JOIN sys.columns sc3
)
INSERT INTO Employee(Name)
SELECT name + CAST(rn AS VARCHAR)
FROM cteData;

-- Timing : 0.972 seconds on SQLExpress 2012 on an i3
-- Inserting 3 x 20 k lists of pseudo random employees (but not contigious on the EmployeeId Cluster)
WITH cteData AS
(
   SELECT top 20000 1 as listid, ROW_NUMBER()  OVER (ORDER BY sc1.object_id) * 50 AS empid
   FROM sys.columns sc1 CROSS JOIN sys.columns sc2

  UNION ALL

   SELECT top 20000 2 as listid, ROW_NUMBER()  OVER (ORDER BY sc1.object_id) * 30 AS empid
   FROM sys.columns sc1 CROSS JOIN sys.columns sc2

  UNION ALL

   SELECT top 20000 3 as listid, ROW_NUMBER()  OVER (ORDER BY sc1.object_id) * 41 AS empid
   FROM sys.columns sc1 CROSS JOIN sys.columns sc2

)
INSERT INTO ListSearchEmployee(ListSearchID, EmployeeID)
SELECT listid, empid
FROM cteData;

DBCC DROPCLEANBUFFERS;

-- Timing : 1.751 seconds on SQLExpress 2012 on an i3
-- Joining 20k rows
SELECT * 
FROM ListSearchEmployee el INNER JOIN Employee e on el.EmployeeID = e.EmployeeID
WHERE el.ListSearchID = 2