使用实体框架代码优先与列表时无法解释的行为.包含的方法

本文关键字:无法解释 方法 包含 string 框架 实体 代码 列表 | 更新日期: 2023-09-27 17:49:40

问题

我们在一些查询中遇到超时错误,经过调查和测试,我们已经将问题缩小到由实体框架代码首先创建的动态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字符串。由于表中的documententid列是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的查询如下所示。请注意,N前缀不包括在in子句中的字符串中:

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,而测试服务器没有。

使用实体框架代码优先与列表<string>时无法解释的行为.包含的方法

我已经能够纠正这种行为与DataAnnotations:

public class Reference
{
    [Column(TypeName = "varchar"), MaxLength(100)]
    public string DocumentID { get; set; }
    [Column(TypeName = "varchar"), MaxLength(50)]
    public string DetailID { get; set; }
    [Column(TypeName = "char"), MaxLength(5)]
    public string TransactionSet { get; set; }
    [Column(TypeName = "varchar"), MaxLength(80)]
    public string Qualifier { get; set; }
    [MaxLength(30)]
    public string ReferenceID { get; set; }
    [MaxLength(80)]
    public string Description { get; set; }
    public int ID { get; set; }
}

EF6以后,您可以使用Custom Conventions更改此DbContext -wide。