【发布时间】:2014-03-12 16:26:35
【问题描述】:
问题
我们的一些查询遇到超时错误,经过调查和测试,我们已将问题缩小到由 Entity Framework Code First 创建的动态 Sql 查询。
背景
我们正在使用实体框架 v4.3.1,代码优先模型 使用 VS 2010 Premium 编写的代码
我们在数据库中有一个表定义如下,ID为PK
CREATE TABLE [dbo].[Reference](
[DocumentID] [varchar](100) NOT NULL,
[DetailID] [varchar](50) NULL,
[TransactionSet] [char](5) NULL,
[Qualifier] [varchar](80) NULL,
[ReferenceID] [nvarchar](30) NULL,
[Description] [nvarchar](80) NULL,
[ID] [int] IDENTITY(1,1) NOT NULL
我们有一个实体类定义如下,对应表:
public class Reference
{
public string DocumentID { get; set; }
public string DetailID { get; set; }
public string TransactionSet { get; set; }
public string Qualifier { get; set; }
public string ReferenceID { get; set; }
public string Description { get; set; }
public int ID { get; set; }
}
我们有如下定义的数据上下文(为简洁起见显示部分类):
public class DataContext : DbContext
{
public DataContext() : base("Name=DataContext")
{
this.Configuration.AutoDetectChangesEnabled = false;
Database.SetInitializer<DataContext>(null);
}
public DbSet<Reference> References { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Reference>().HasKey(pk => pk.ID);
}
}
最后调用代码如下:
private List<Reference> getDocumentReferences(List<string> documentIds)
{
List<Reference> result = null;
using (var context = new DataContext())
{
if (context != null)
{
var q = context.References.Where(r => documentIds.Contains(r.DocumentID) && r.Qualifier == "AFN");
Logger.WriteLogEntry("Sql query:\r\n {0}", q.ToString()); //Added to see the query generated by EF
result = q.ToList();
}
}
return result;
}
说明
代码执行时,会向Sql Server发送如下查询:
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[DocumentID] AS [DocumentID],
[Extent1].[DetailID] AS [DetailID],
[Extent1].[TransactionSet] AS [TransactionSet],
[Extent1].[Qualifier] AS [Qualifier],
[Extent1].[ReferenceID] AS [ReferenceID],
[Extent1].[Description] AS [Description]
FROM [dbo].[Reference] AS [Extent1]
WHERE ([Extent1].[DocumentID] IN (N'A2014011300028343869701A020',N'A2014011300028343869701A021')) AND (N'AFN' = [Extent1].[Qualifier])
请注意,由于代码使用 List 的 Contains 方法,因此它被 EF 转换为 sql 查询中的 IN 子句。 IN 子句中的每个字符串都以 'N' 为前缀,表示一个 unicode 字符串。由于表中的 DocumentID 列是 VARCHAR(不是 NVARCHAR),因此 Sql 必须进行隐式转换才能执行查询。经过调查和测试,我们发现在这种情况下,它不会选择相关的索引,导致表扫描,并且经常出现超时异常。
我们发现,通过删除查询中的 N 前缀,使用了正确的索引并且查询的运行速度提高了几个数量级。
所以为了让 EF 不给字符串加 N 前缀,我在 OnModelCreating 方法的数据上下文类中添加了下面两行:
modelBuilder.Entity<Reference>().Property(r => r.DocumentID).HasColumnType("VARCHAR");
modelBuilder.Entity<Reference>().Property(r => r.Qualifier).HasColumnType("VARCHAR");
当我在本地机器上测试它时,它达到了预期的效果。提交给 Sql Server 的查询如下。请注意,IN 子句中的字符串中不包含 N 前缀:
SELECT
[Extent1].[ID] AS [ID],
[Extent1].[DocumentID] AS [DocumentID],
[Extent1].[DetailID] AS [DetailID],
[Extent1].[TransactionSet] AS [TransactionSet],
[Extent1].[Qualifier] AS [Qualifier],
[Extent1].[ReferenceID] AS [ReferenceID],
[Extent1].[Description] AS [Description]
FROM [dbo].[Reference] AS [Extent1]
WHERE ([Extent1].[DocumentID] IN ('A2014011300028343869701A020','A2014011300028343869701A021')) AND ('AFN' = [Extent1].[Qualifier])
我以为我的问题已经解决了,但是当我将此代码部署到测试环境时,N个前缀没有被删除,我不知道为什么。
我可以通过手动创建 Sql 查询来解决这个问题,但是我在这个程序中还有一些查询需要类似的更改,如果我只需要更改 OnModelCreating 方法而不是手动创建每个查询会更容易.
有人对为什么会发生这种情况有任何想法吗?
顺便说一下,我的本地机器使用 Sql Server 2008,而测试环境(和生产环境)使用 Sql 2005。我不确定这是否会影响 EF 生成的查询,因为我的印象是查询是在生成之前生成的提交到数据库。
更新
如果我对测试数据库执行代码在我的盒子上,它可以正常工作。如果代码在测试盒上执行,即使它们都访问同一个数据库,它也不会。
更新 2
代码能否成功运行取决于执行的位置。
EXE Location | EXE Executed From | Database Server (Version) | Result
--------------------------------------------------------------------------
My Box | My Box | My Box (2008) | Success
Test Server | My Box | Test DB Server (2005) | Success
Test Server | Test Server | Test DB Server (2005) | Fail
根据上面的结果,我的本地机器上的某些东西与测试机器上的不同,这似乎是合乎逻辑的。但我不知道如何诊断。我能想到的唯一区别是我的盒子安装了 .Net 4.5 而测试服务器没有。
【问题讨论】:
标签: c# sql-server entity-framework ef-code-first