关于Linq查询where条件可能为null时的诡异事件

群里一哥们有这样一个需求,有一张表结构如下:

  MenuId varchar(50) Unchecked
  MenuName varchar(50) Checked
  PatentMenuId varchar(50) Checked

测试数据如下:

 1 insert into MenuInfo values('100','父节点1',NULL)
 2 insert into MenuInfo values('101','子节点11','100')
 3 insert into MenuInfo values('102','子节点12','100')
 4 insert into MenuInfo values('103','子节点13','100')
 5 insert into MenuInfo values('104','父节点2',NULL)
 6 insert into MenuInfo values('105','子节点21','104')

这个表就不详细介绍了,相信大家都知道。

他的具体需求是根据一个字符串(strKey)作为条件查询,当strKey为null时,则只查出1和5这两条数据,如果strKey不为null时,则查出所有ParentMenuId为strKey的数据。

他原来的代码如下:

string strKey=null;

List<MenuInfo> menuList=new List<MenuInfo>();

if(strKey==null)

{

  menuList= (from menu in db.MenuInfo

         where menu.ParentMenuId==null

         select menu).ToList();

}

else

{

  menuList=(from menu in db.MenuInfo

        where menu.ParentMenuId==strKey

        select menu).ToList();

}

这样写肯定能实现前面说到的需求,现在就是如何去简化这段代码,即干掉if判断语句。

经过长时间激烈讨论,终于有人提出用三目表达式,代码如下:

string strKey=null;

List<MenuInfo> menuList=new List<MenuInfo>();

menuList=(from menu in db.MenuInfo

      where strKey==null?(menu.ParentMenuId==null: menu.ParentMenuId==strKey)

      select menu).ToList();

但是马上有人将其简化成以下代码:

string strKey=null;

List<MenuInfo> menuList=new List<MenuInfo>();

menuList=(from menu in db.MenuInfo

      where  menu.ParentMenuId==(strKey==null?null:strKey)

      select menu).ToList();

到这里貌似没有问题了,讨论到此结束。

但是这哥们经过测试,最终还是发现了问题。当strKey为null时,却查不到任何数据。。。这是怎么回事呢?

后来这哥们把解析后的sql语句发出来一看,三目表达式确实有问题

当where 条件为 where menu.ParentMenuId==null 时,解析的sql条件为  WHERE [t0].[PatentMenuId] IS NULL

而当where条件为 where menu.ParentMenuId==strKey(此时strKey为null)时,解析的sql条件为 WHERE [t0].[PatentMenuId] = @p0

这又是为何?这时另一哥们发话了,原因是:为null的变量和null在linq语句中意义不同。

真是一语惊醒梦中人呀,很有可能是linq在解析的时候对null值进行了判断,这样就很好解释了。前面简化胡的三木运算表达式(strKey==null?null:strKey)返回的不一定是null值,所以linq解析的时候把它当成一个变量来处理了,没有判断其结果是不是为null,所以就直接解析成 WHERE [t0].[PatentMenuId] = @p0了。

可是笔者还是不甘心,回来查资料,又尝试了equals方法,代码如下:

string strKey=null;

List<MenuInfo> menuList=new List<MenuInfo>();

menuList=(from menu in db.MenuInfo

      where  menu.PatentMenuId.Equals(strKey)

      select menu).ToList();

这样的结果跟前面用==的结果还是一样的,即:

当where 条件为 where menu.ParentMenuId.Equals(null) 时,解析的sql条件为  WHERE [t0].[PatentMenuId] IS NULL

而当where条件为 where menu.ParentMenuId.Equals(strKey)(此时strKey为null)时,解析的sql条件为 WHERE [t0].[PatentMenuId] = @p0

就这样胡乱尝试一通后又发现,由于menu.ParentMenuId是String类型,而String对象对Equals方法进行了重写,于是又一次尝试直接用object对象的Equals方法,简化成以下代码:

string strKey=null;

List<MenuInfo> menuList=new List<MenuInfo>();

menuList=(from menu in db.MenuInfo

      where Equals(menu.ParentMenuId,strKey)

      select menu).ToList();

终于搞定。但是这是为什么呢?

可能是string重写了equals,linq本身就对sting重写的equals方法制定了等同于“==”运算符的解析机制,而没有对object对象的equals方法制定机制。

用笔者自己的话来说,就是当用linq对象去主动比较其他对象的时候,会解析成linq对象=其他对象(@p0),结合前面那个哥们说的“为null的变量和null在linq语句中意义不同。”这个@p0虽然是C#变量值为null,但是与sql中的null不一样,所以在sql中这个where条件不成立,自然查不出数据。而用C#中object对象的Equals方法则不会解析成linq对象=其他对象@p0

原文地址:https://www.cnblogs.com/songjiali/p/2971201.html