LINQ to Entities区分大小写的比较


115

在LINQ和Entities中,这不是区分大小写的比较:

Thingies.First(t => t.Name == "ThingamaBob");

如何使用LINQ to Entities实现区分大小写的比较?


@罗尼:你确定吗?您是说不区分大小写的比较吗?
Michael Petrotta

14
绝对可以 不,我不是那个意思。
罗尼·奥弗比

12
否,在运行带有SQL Server 2008 R2的EF 4.0的计算机上,以上内容不区分大小写。我知道很多地方都说EF是默认区分大小写的,但这不是我所经历的。
tster

3
那不依赖于基础数据库吗?
codymanix

1
@codymanix:这是一个很好的问题!Linq to EF是否会为数据库查询转换lambda表达式?我不知道答案。
Tergiver 2010年

Answers:


163

这是因为您使用的是LINQ To Entities,最终将Lambda表达式转换为SQL语句。这意味着区分大小写取决于您的SQL Server,默认情况下,SQL Server具有SQL_Latin1_General_CP1_CI_AS排序规则,并且不区分大小写。

使用ObjectQuery.ToTraceString查看实际上已提交给SQL Server的生成的SQL查询揭示了一个奥秘:

string sqlQuery = ((ObjectQuery)context.Thingies
        .Where(t => t.Name == "ThingamaBob")).ToTraceString();

创建LINQ to Entities查询时,LINQ to Entities利用LINQ解析器开始处理查询并将其转换为LINQ表达式树。然后将LINQ表达式树传递给Object Services API,后者将表达式树转换为命令树。然后将其发送到商店提供程序(例如SqlClient),后者将命令树转换为本地数据库命令文本。查询中获取的数据存储执行,结果被物化实体对象对象服务。两者之间没有考虑到区分大小写的逻辑。因此,无论您将谓词放在哪种情况下,除非您更改该列的SQL Server排序规则,否则SQL Server始终将其视为相同。

服务器端解决方案:

因此,最好的解决方案是将Thingies表中“ 名称”列的排序规则更改为COLLATE Latin1_General_CS_AS,这是区分大小写的,方法是在SQL Server上运行以下命令:

ALTER TABLE Thingies
ALTER COLUMN Name VARCHAR(25)
COLLATE Latin1_General_CS_AS

有关SQL Server 排序规则的更多信息,请查看SQL SERVER排序规则区分大小写的SQL查询搜索。

客户端解决方案:

您可以在客户端应用的唯一解决方案是使用LINQ to Objects进行另一个比较,这似乎并不十分优雅:

Thingies.Where(t => t.Name == "ThingamaBob")
        .AsEnumerable()
        .First(t => t.Name == "ThingamaBob");

我正在使用Entity Framework生成数据库架构,因此使用我的调用代码的解决方案将是最好的。我想我会在结果恢复后再进行检查。谢谢。
罗尼·奥弗比

没问题。是的,这是正确的,并且我已经使用客户端解决方案更新了答案,但是它不是很好,我仍然建议使用数据存储解决方案。
Morteza Manavi 2010年

18
@eglasius这并非完全正确:它不会获取所有数据,只会以不区分大小写的方式获取与大小写匹配的数据,然后再以敏感方式在客户端大小写上对其进行过滤。当然,如果您碰巧有成千上万个不区分大小写的条目,但其中只有一个是正确的,区分大小写,那么这会产生很多开销。但是我不认为现实会出现这种情况……:)
阿奇姆(Achim)2014年

1
@MassoodKhaari您发布的解决方案会使它不区分大小写,因为您在比较的两面都比较低。OP需要区分大小写的比较。
乔尼2014年

1
“因此,最好的解决方案是将Thingies表中“名称”列的排序规则更改为COLLATE Latin1_General_CS_AS。”-我认为这不是最好的方法。大多数时候,我需要不区分大小写的LIKE过滤器(.Contains()),但有时它应该区分大小写。我将尝试您的“客户端解决方案”-对于我认为的用例来说,它要优雅得多(很高兴了解它的作用,但您不能全部使用它:))。
令人难以置信的

11

您可以为EF6 +代码优先添加[CaseSensitive]批注

添加此类

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class CaseSensitiveAttribute : Attribute
{
    public CaseSensitiveAttribute()
    {
        IsEnabled = true;
    }
    public bool IsEnabled { get; set; }
}

public class CustomSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    protected override void Generate(AlterColumnOperation alterColumnOperation)
    {
        base.Generate(alterColumnOperation);
        AnnotationValues values;
        if (alterColumnOperation.Column.Annotations.TryGetValue("CaseSensitive", out values))
        {
            if (values.NewValue != null && values.NewValue.ToString() == "True")
            {
                using (var writer = Writer())
                {
                    //if (System.Diagnostics.Debugger.IsAttached == false) System.Diagnostics.Debugger.Launch();

                    // https://github.com/mono/entityframework/blob/master/src/EntityFramework.SqlServer/SqlServerMigrationSqlGenerator.cs
                    var columnSQL = BuildColumnType(alterColumnOperation.Column); //[nvarchar](100)
                    writer.WriteLine(
                        "ALTER TABLE {0} ALTER COLUMN {1} {2} COLLATE SQL_Latin1_General_CP1_CS_AS {3}",
                        alterColumnOperation.Table,
                        alterColumnOperation.Column.Name,
                        columnSQL,
                        alterColumnOperation.Column.IsNullable.HasValue == false || alterColumnOperation.Column.IsNullable.Value == true ? " NULL" : "NOT NULL" //todo not tested for DefaultValue
                        );
                    Statement(writer);
                }
            }
        }
    }
}

public class CustomApplicationDbConfiguration : DbConfiguration
{
    public CustomApplicationDbConfiguration()
    {
        SetMigrationSqlGenerator(
            SqlProviderServices.ProviderInvariantName,
            () => new CustomSqlServerMigrationSqlGenerator());
    }
}

修改您的DbContext,添加

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Add(new AttributeToColumnAnnotationConvention<CaseSensitiveAttribute, bool>(
                "CaseSensitive",
                (property, attributes) => attributes.Single().IsEnabled));
        base.OnModelCreating(modelBuilder);
    }

然后做

添加迁移案例敏感

更新数据库

根据文章https://milinaudara.wordpress.com/2015/02/04/case-sensitive-search-using-entity-framework-with-custom-annotation/进行了一些错误修复


11

WHERE默认情况下,SQL Server中的条件不区分大小写。通过将列的默认排序规则(SQL_Latin1_General_CP1_CI_AS)更改为,使其区分大小写SQL_Latin1_General_CP1_CS_AS

执行此操作的脆弱方法是使用代码。添加一个新的迁移文件,然后将其添加到Up方法中:

public override void Up()
{
   Sql("ALTER TABLE Thingies ALTER COLUMN Name VARCHAR(MAX) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL");
}

您可以使用新的EF6功能创建名为“ CaseSensitive”的自定义注释,并可以如下装饰属性:

[CaseSensitive]
public string Name { get; set; }

这篇博客文章解释了如何做到这一点。


在那篇文章中有一个错误
RouR

3

@Morteza Manavi给出的答案解决了这个问题。不过,对于客户端解决方案,一种优雅的方法是进行以下操作(添加双重检查)。

var firstCheck = Thingies.Where(t => t.Name == "ThingamaBob")
    .FirstOrDefault();
var doubleCheck = (firstCheck?.Name == model.Name) ? Thingies : null;

-4

我喜欢Morteza的答案,通常更喜欢在服务器端进行修复。对于客户端,我通常使用:

Dim bLogin As Boolean = False

    Dim oUser As User = (From c In db.Users Where c.Username = UserName AndAlso c.Password = Password Select c).SingleOrDefault()
    If oUser IsNot Nothing Then
        If oUser.Password = Password Then
            bLogin = True
        End If
    End If

基本上,首先检查是否有符合要求的用户,然后检查密码是否相同。有点冗长,但是当涉及到很多标准时,我觉得它更容易阅读。


2
此答案表明您将密码以纯文本格式存储在数据库中,这是一个巨大的安全漏洞。
杰森·科恩

2
@JasonCoyne与他比较的密码可能已经被散列了
Peter Morris

-4

两者都不StringComparison.IgnoreCase适合我。但这确实是:

context.MyEntities.Where(p => p.Email.ToUpper().Equals(muser.Email.ToUpper()));

2
这无助于提出的问题,即How can I achieve case sensitive comparison
Reg Edit

-4

使用string.Equals

Thingies.First(t => string.Equals(t.Name, "ThingamaBob", StringComparison.CurrentCulture);

同样,您不必担心null并只获取所需的信息。

使用StringComparision.CurrentCultureIgnoreCase区分大小写。

Thingies.First(t => string.Equals(t.Name, "ThingamaBob", StringComparison.CurrentCultureIgnoreCase);

Equals()无法转换为SQL。此外,如果尝试使用实例方法,则将忽略StringComparison。
LMK

您是否尝试过此解决方案?我最终在与EF一起工作时尝试了这一点。
Darshan Joshi

-6

不确定EF4,但EF5支持以下功能:

Thingies
    .First(t => t.Name.Equals(
        "ThingamaBob",
        System.StringComparison.InvariantCultureIgnoreCase)

好奇生成什么sql。
罗尼·奥弗比

我用EF5进行了检查,它只是在SQL中生成了WHERE ... =...。同样,这取决于SQL Server端的排序规则设置。
阿奇姆2014年

即使在数据库中使用区分大小写的排序规则,我也无法做到这一点或任何其他StringComparison枚举来有所作为。我见过足够多的人暗示这种事情应该努力思考的问题是某处EDMX文件(DB-第一),但stackoverflow.com/questions/841226/...
drzaus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.