SQL Server的SQL查询不区分大小写,而LINQ查询区分大小写,所以在写LINQ代码时需要注意的是——如果这段LINQ代码将会被Entity Framework解析为SQL语句(LINQ to Entities),则不用考虑大小写问题;如果这段LINQ代码在内存中执行,就要考虑大小写的问题。

比如下面的LINQ to Entities(不用考虑大小写):

//代码自来CNBlogsTagService
_unitOfWork.Set<Tag>().Where(x => tagNames.Contains(x.TagName))

而如果是LINQ,则需要这么写(通过StringComparer.OrdinalIgnoreCase忽略大小写):

content.Tags.RemoveAll(x => tagNames.Contains(x.TagName, StringComparer.OrdinalIgnoreCase) == false);

这种不一致带来的问题是——同样是写LINQ,你却要区别对待,你要考虑这段LINQ代码是在内存中执行,还是会被解析为SQL执行。

这个大小写问题是大家熟知的,解决起来也不困难。

而我们最近在实际项目中遇到了一个神奇的问题,与大小写问题是同一类问题——在SQL Server中进行SQL查询时竟然不区分全角半角,而在LINQ中是区分的。

下面我们通过CNBlogsTagService项目(一个基于Entity Framework实现的为前端应用提供Tag服务的后端服务)中的一个实际场景感受一下。

先看一段LINQ to Entities代码:

public List<Tag> GetTags(IEnumerable<string> tagNames)
{
var existedTags = _unitOfWork.Set<Tag>().Where(x => tagNames.Contains(x.TagName)).ToList();
//...
}

上面的代码是根据TagName从数据库中查询记录,然后得到对应的Tag实体。

我们遭遇问题时,tagNames的值是{ "C++" },注意这里的加号是全角,数据库中存储的TagName的值是"c++"(这里的加号是半角)。上面的代码执行后得到的结果是——existedTags[0].TagName的值为"c++"。SQL查询竟然能自动匹配全角半角,当时发现这个也是第1次知道这回事,不由感叹——好智能的SQL Server。

但是这种智能带来的不一致却让我们经历了一次艰难的问题排查过程。

再看后续的LINQ代码:

var createdTags = tagNames.Where(x => existedTags.Select(y => y.TagName)
.Contains(x, StringComparer.InvariantCultureIgnoreCase) == false)
.Select(x => new Tag { TagName = x }).ToList();

这段代码是在内存中进行LINQ查询操作的代码,用途是找出tagNames(类型是IEnumerable<string>)中存在,而且existedTags(EF的实体)不存在的TagName(也就是找出在数据库中不存在的TagName)。

根据之前的场景,tagNames的值是{ "C++" },existedTags[0].TagName的值是"c++"。既然数据库中已存在这个Tag,我们所期望的是createdTags中没有数据,但是由于LINQ区分全角半角,得到的结果却是——createdTags[0].TagName的值为"C++",在通过Entity Framework进行SaveChanges时引发了异常:

System.Data.SqlClient.SqlException: Cannot insert duplicate key row in object 'dbo.Tags' with unique index 'IX_Tags_TagName'. The duplicate key value is (C++).

本来这里的代码的目的是如果指定名称的Tag在数据库中不存在,就创建它,并保存至数据库。对应现在的场景,变成了——"C++"这个Tag在数据库中存在吗?数据库说:存在,名叫"c++";{ "C++" } 中有哪些是 { "c++" }所没有的?LINQ说:"C++";于是,EF将"C++"保存数据库,数据库却说:我这已经有了c++,"C++"请滚开。于是就有了上面的异常。

问题就出在SQL与LINQ的不一致行为上。如果事先不知道不一致的情况,出现bug时,往往最难对付!在博客中写出来看上去问题似乎很简单,但我们纠缠于这个问题时,猜测了成千上万的原因,也没想到是这个原因。最后发现时不由感叹——真是一次奇遇!

那如何解决这个问题呢?

我们想到的最简单的方法是在LINQ查询时忽略全角半角。

那如何以最简单的方法实现在LINQ查询时忽略全角半角呢?

园子里2005年空军写的一篇博文(C#中直接调用VB.NET的函数,兼论半角与全角、简繁体中文互相转化)让我们很快有了答案——在C#中调用VB.NET中的函数Strings.StrConv(x, VbStrConv.Narrow);

具体实现方法如下:

1. 在Visual Studio中为项目添加Microsoft.VisualBasic的引用

2. 将上面的LINQ代码改为如下的代码:

var createdTags = tagNames.Where(x => existedTags.Select(y => Strings.StrConv(y.TagName, VbStrConv.Narrow))
.Contains(Strings.StrConv(x, VbStrConv.Narrow), StringComparer.InvariantCultureIgnoreCase) == false)
.Select(x => new Tag { TagName = x }).ToList();

写好这篇博客后,突然觉得也算不上什么奇遇记,可能很多朋友早就知道了这个情况。标题党只是为了表达一下解决问题后的那种兴奋的感觉。

解决问题是一种快乐,那有没有比解决问题更快乐的事情呢?有,那就是在解决问题后写一篇博客!

全角半角符号引发的Entity Framework奇遇记的更多相关文章

  1. Java如何判断字符串中包含有全角,半角符号

    首先介绍下全角跟半角之间的区别: 在计算机屏幕上,一个汉字要占两个英文字符的位置,人们把一个英文字符所占的位置称为"半角",相对地把一个汉字所占的位置称为"全角" ...

  2. python实现全角半角的相互转换

    缘起 在自然语言处理过程中,全角.半角的的不一致会导致信息抽取不一致,因此需要统一. 转换说明 全角半角转换说明 有规律(不含空格): 全角字符unicode编码从65281~65374 (十六进制 ...

  3. 我的Android进阶之旅------>Java全角半角的转换方法

    一中文全角和半角输入的区别 1全角指一个字符占用两个标准字符位置 2半角指一字符占用一个标准的字符位置 3全角与半角各在什么情况下使用 4全角和半角的区别 5关于全角和半角 6全角与半角比较 二转半角 ...

  4. SQL转换全角/半角函数

    /****** SQL转换全角/半角函数 开始******/ CREATE FUNCTION ConvertWordAngle ( ), --要转换的字符串 @flag bit --转换标志,0转换成 ...

  5. web页面全角&半角

    根据Unicode编码,全角空格为12288,半角空格为32 : 其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248  全角-->半角函数  //半角转换 ...

  6. C#全角半角转换函数

    Code#region 全角半角转换 /// <summary> /// 转全角的函数(SBC case) /// </summary> /// <param name= ...

  7. php字符串处理之全角半角转换

    半角全角的处理是字符串处理的常见问题,本文尝试为大家提供一个思路. 一.概念 全角字符unicode编码从65281~65374 (十六进制 0xFF01 ~ 0xFF5E)半角字符unicode编码 ...

  8. java中全角半角字符的相互转换的代码

    如下内容是关于java中全角半角字符的相互转换的内容.package com.whatycms.common.util; import org.apache.commons.lang.StringUt ...

  9. 转: js实现全角半角检测的方法

    //全角半角校验 function issbccase(strTmp) { for (var i=0; i<strTmp.length; i++) { if (strTmp.charCodeAt ...

随机推荐

  1. fprintf宏

    最近在调试程序,使用printf函数和调试信息都不能在终端输出,所以使用比较笨的方法.将调试信息写到文件中,再查看文件.由于要多次使用fprintf函数,所以将其写成宏. 参考链接: http://w ...

  2. e641. 使一个组件成为拖放目标

    public class DropTargetComponent extends JComponent implements DropTargetListener { public DropTarge ...

  3. 【Java面试题】49 垃圾回收的优点和原理。并考虑2种回收机制。

    1.Java语言最显著的特点就是引入了垃圾回收机制,它使java程序员在编写程序时不再考虑内存管理的问题. 2.由于有这个垃圾回收机制,java中的对象不再有“作用域”的概念,只有引用的对象才有“作用 ...

  4. Windows下基于eclipse的Storm应用开发与调试

    原创文章,转载请注明: 转载自http://www.cnblogs.com/tovin/p/3971113.html 本文以一个简单的example来讲解如何开发storm应用程序 1.创建maven ...

  5. Matlab中imread函数使用报错“不应为MATLAB 表达式”分析

    问题描述: 使用imread读取特定路径下的文件时,会提示出错! >> mytest错误: 文件:mytest.m 行:10 列:87不应为 MATLAB 表达式. 出错行: Images ...

  6. mysql数据库中,通过一条insert into语句,同时插入多个值

    需求描述: 今天在看一本mysql的书籍,发现一个mysql中insert into好用的技巧,就是通过 1条insert into语句,插入多行数据,而不是多个insert into语句.在此记录下 ...

  7. oracle 日期函数 求年的最后一天、第一天,月的最后一天

    add_months(trunc(to_date('2013','yyyy') ,'yyyy'),12)-1  2013年最后一天 trunc(to_date('2013','yyyy') ,'yyy ...

  8. 通过Nagios监控weblogic服务

    1.前言      前段时间搭建了一套Nagios监控服务,心血来潮想自己写一个脚本,拓展Nagios插件来监控公司的weblogic服务. 环境:weblogic10.3.3.0 . CentOS6 ...

  9. iOS 数据类型转换

    1.NSString转化为UNICODE String:(NSString*)fname = @“Test”; char fnameStr[10]; memcpy(fnameStr, [fname c ...

  10. 如何提高AJAX客户端响应速度

    AJAX的出现极大的改变了Web应用客户端的操作模式,它使的用户可以在全心工作时不必频繁的忍受那令人厌恶的页面刷新.理论上AJAX技术在很大的程度上可以减少用户操作的等待时间,同时节约网络上的数据流量 ...