7

我正在从数据库验证用户名(不区分大小写)和密码(区分大小写)

我正在使用 Entity Framework 5.0 与数据库交互

在数据库密码是

“0x11A46971EFF1E1A2CA228CF592CA37DC77E7CCF759602D53629C22B693AEEE96CCD9F889D8E9A92C19391E6BD2DD07E741E9B7AA07E391ACDC939B993C9D7F5D”

当我将密码的大小写更改为更低时,我期望 null 以换取我的以下代码块

“0x11a46971eff1e1a2ca228cf592ca37dc77e7ccf759602d53629c22b693aeee96ccd9f889d8e9a92c19391e6bd2dd07e741e9b7aa07e391acdc939b993c9d7f5d”

但它不会失败并返回正确的用户实体。

using (var context = new MyDbContext(ConnectionString))
{
  return context.Users.Where(x => (x.Name.Equals(userName, StringComparison.OrdinalIgnoreCase) && x.Password.Equals(password))).FirstOrDefault();
}

而如果我得到所有用户然后比较它会给出正确的输出,即 user == null

using (var context = new MyDbContext(ConnectionString))
{
  var users = context.Users.ToList();

  return users.Where(x => (x.Name.Equals(userName,StringComparison.OrdinalIgnoreCase) && x.Password.Equals(password))).FirstOrDefault();
}

这很奇怪?为什么会这样?如何从 LINQ 编写区分大小写的查询到 SQL?

4

6 回答 6

12

SQL 不区分大小写。如果您查看生成的 SQL,它看起来像这样:

EXISTS(SELECT * FROM Users WHERE userName = 'foo' AND Password = '0x11a46971eff1e1a2ca228cf592ca37dc77e7ccf759602d53629c22b693aeee96ccd9f889d8e9a92c19391e6bd2dd07e741e9b7aa07e391acdc939b993c9d7f5d')

无论大小写,它都会返回 true。第二个例子调用 ToList 所以它现在正在做一个 .net 字符串比较什么是区分大小写的。

于 2013-05-24T12:29:15.067 回答
7

使用时

return context.Users.Where(x => (x.Name.Equals(userName, StringComparison.OrdinalIgnoreCase) && x.AuthenticationSecret.Equals(password))).FirstOrDefault();

您在延迟加载的 EntityCollection 上工作。因为 SQL 不区分大小写,所以每次都会返回 true。

如果您正在使用

var users = context.Users.ToList();

您从 切换LINQ To EntitiesLINQ To Objects,因此您现在可以进行区分大小写的比较。

最大的缺点是每次您使用ToList()查询时都会立即执行,并且您将从数据库中加载完整的用户列表。

于 2013-05-24T12:29:02.920 回答
2

SQL 查找不区分大小写,当您第ToList一次查找时,您最终会进行区分大小写的比较。但是ToList将从数据库返回所有内容。而是Where在数据库上执行,然后进一步比较以确保密码是正确的大小写。

using (var context = new MyDbContext(ConnectionString))
{
  var users = context.Users.Where(x => x.Name == userName && x.Password == password).ToList();
  return users.FirstOrDefault(x =>  x.Password.Equals(password)));
}
于 2013-05-24T13:01:14.350 回答
2

正如@nmclean 提到的,这是因为您的数据库配置为执行不区分大小写的比较。下面的小程序展示了 EF 如何完全忽略StringComparison传递给String.Equals. 它还显示了更改数据库列的排序规则如何实现您所需要的。

在使用 EF(以及其他 LINQ 提供程序)时,这是一个重要的概念。您编写的查询看起来像普通的 .NET 代码。然而,事实并非如此!许多提供程序将代码翻译成完全不同的东西,在这种情况下是 SQL(对于 SQL Server,Transact-SQL)。只有一部分可用的 .NET 方法完全可以工作(这就是为什么不能Regex在 EF 查询中使用的原因),而那些可以工作的方法可能会表现出与您预期的略有不同的行为。

ToList()查询中使用然后应用过滤器的地方,实际上是使用 LINQ to Objects 而不是 EF 来应用过滤器。LINQ to Objects“真正的”.NET,因此您可以使用所有的 .NET 功能。当然,您失去了让代码在 SQL Server 上运行的好处,因此这通常不是一种选择。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;

namespace ConsoleApplication2
{
    public class Initializer<T> : DropCreateDatabaseAlways<T> where T : DbContext
    {
        protected override void Seed(T context)
        {
            base.Seed(context);

            // Comment out this line and note how the count changes.
            context.Database.ExecuteSqlCommand("ALTER TABLE MyEntity ALTER COLUMN MyString nvarchar(MAX) COLLATE Latin1_General_CS_AS");
        }
    }

    [Table("MyEntity")]
    public class MyEntity
    {
        [Key]
        public virtual int MyEntityId { get; set; }

        [Required]
        public virtual string MyString { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<MyEntity> Entities
        {
            get { return this.Set<MyEntity>(); }
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            var e = modelBuilder.Entity<MyEntity>();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Database.SetInitializer(new Initializer<MyContext>());

            using (MyContext context = new MyContext())
            {
                context.Entities.Add(new MyEntity { MyString = "aaa" });
                context.Entities.Add(new MyEntity { MyString = "AAA" });

                context.SaveChanges();
            }

            using (MyContext context = new MyContext())
            {
                var caseSensitiveQuery = from e in context.Entities
                                         where e.MyString.Equals("aaa", StringComparison.Ordinal)
                                         select e;

                var caseInsensitiveQuery = from e in context.Entities
                                           where e.MyString.Equals("aaa", StringComparison.OrdinalIgnoreCase)
                                           select e;

                // Note how the StringComparison parameter is completely ignored.  Both EF queries are identical.
                Console.WriteLine("Case sensitive query (count = {0}):\r\n{1}", caseSensitiveQuery.Count(), caseSensitiveQuery);
                Console.WriteLine();

                Console.WriteLine("Case insensitive query (count = {0}):\r\n{1}", caseInsensitiveQuery.Count(), caseInsensitiveQuery);
            }

            Console.ReadLine();
        }
    }
}
于 2013-05-24T13:15:39.977 回答
1

这里有一个更详细的答案:

https://stackoverflow.com/a/3843382/933416

如果需要,您可以在服务器端将列配置为区分大小写。但是在您的情况下,我不明白您为什么甚至需要查询密码。当然,每个用户名只有一个密码,并且用户名是不区分大小写的——那么为什么不直接通过用户名查询,然后检查结果中的密码呢?

于 2013-05-24T13:01:45.040 回答
-1

正如其他人已经提到的,问题的原因是 SQL Server 默认不区分大小写,而 C# 默认区分大小写。

您可以将密码字段从 更改varcharvarbinary来解决此问题。


为什么匿名反对票?使用字符串存储和比较看似 64 字节的哈希值并不是最好的方法。不需要对哈希进行 base 64 编码来存储它(除非 Entity Framework 不能处理它?)。

http://support.microsoft.com/kb/307020 - 如何使用 Visual C# 比较哈希密码来计算和比较哈希值

于 2013-05-24T13:49:00.477 回答