前端时间在.NetCore项目中使用SqlSugar ORM框架(引用sqlSugarCore依赖包)的时候遇到了一个奇葩问题:对表进行数据更新操作的时候,报错 “ You cannot have no primary key and no conditions ”,即没有主键和条件的报错。

由于当时采用的更新方式是UpdateColumns()+WhereColumns(),所以排除了没有条件的问题,定位问题:缺少主键

SqlSugar框架在初始化DB对象时,为我们提供了两种获取主键的方式:

SqlSugarClient属性:InitKeyType

1.SysTable  表示通过数据库系统表查询表主键,这种需要数据库最高权限,并且数据库表需有主键或能获取到主键。

2.Attribute 表示通过实体  [SugarColumn(IsPrimaryKey = true)]标签获取主键,而无需通过数据库表。

在项目中我们并未指定InitKeyType属性值,也就是默认使用了 SysTable模式。

于是,首先查看数据表设计结构,发现表结构是有主键的,当时就懵逼了。。。赶紧去看了一下SqlSugar源码,发现ORM框架中是通过如下方式获取主键列的。

        protected override string GetColumnInfosByTableNameSql
{
get
{
string sql = @"SELECT sysobjects.name AS TableName,
syscolumns.Id AS TableId,
syscolumns.name AS DbColumnName,
systypes.name AS DataType,
syscolumns.length AS [Length],
sys.extended_properties.[value] AS [ColumnDescription],
syscomments.text AS DefaultValue,
syscolumns.isnullable AS IsNullable,
columnproperty(syscolumns.id,syscolumns.name,'IsIdentity')as IsIdentity,
(CASE
WHEN EXISTS
(
select 1
from sysindexes i
join sysindexkeys k on i.id = k.id and i.indid = k.indid
join sysobjects o on i.id = o.id
join syscolumns c on i.id=c.id and k.colid = c.colid
where o.xtype = 'U'
and exists(select 1 from sysobjects where xtype = 'PK' and name = i.name)
and o.name=sysobjects.name and c.name=syscolumns.name
) THEN 1
ELSE 0
END) AS IsPrimaryKey
FROM syscolumns
INNER JOIN systypes ON syscolumns.xtype = systypes.xtype
LEFT JOIN sysobjects ON syscolumns.id = sysobjects.id
LEFT OUTER JOIN sys.extended_properties ON (sys.extended_properties.minor_id = syscolumns.colid
AND sys.extended_properties.major_id = syscolumns.id)
LEFT OUTER JOIN syscomments ON syscolumns.cdefault = syscomments.id
WHERE syscolumns.id IN
(SELECT id
FROM sysobjects
WHERE xtype IN('u',
'v') )
AND (systypes.name <> 'sysname')
AND sysobjects.name='实际传入参数:表名称'
AND systypes.name<>'geometry'
AND systypes.name<>'geography'
ORDER BY syscolumns.colid";
return sql;
}
}

数据库中直接查询如上sql语句,发现并未找到主键列(IsPrimaryKey=1),神奇了。。。表结构明明有主键却查不到,折腾了一会儿才知道,项目中用的数据表竟然是同义词!!!

同义词是指向其他数据库表的别名,在当前数据库根本无法获取同义词对应的表结构属性,到这里也就恍然大悟了,原来是同义词导致框架SysTable模式下获取不到表主键。

执行框架中的如上Sql,还有其他几种情况会导致无法获取到主键:

1)表没建主键

2)表使用的同义词

3)当前数据库用户没有查询系统表sys级别权限

4)数据库排序规则:_CI(CS) 是否区分大小写,CI不区分,CS区分

如果设置的区分大小写,则上面sql中的如下部分将受影响,导致找不到数据。数据库实际存的值是大写的U,V

SELECT id
FROM sysobjects
WHERE xtype IN('u',
'v')

解决方案:

1)SqlSugar也支持手写sql语句,直接手写更新sql即可。

2)初始化SqlSugarClient DB对象时,选择InitKeyType.Attribute模式,通过实体特性获取主键。

后续:

项目中问题虽然解决了,但是后来还是比较好奇:使用了WhereColumns()条件列,为什么还要去找主键呢?直接根据条件列更新不可以么???于是决定一探究竟。。。

定位到获取主键的方法:

        private List<string> GetPrimaryKeys()
{
if (this.WhereColumnList.HasValue())
{
return this.WhereColumnList;
}
if (this.Context.IsSystemTablesConfig)
{
return this.Context.DbMaintenance.GetPrimaries(this.Context.EntityMaintenance.GetTableName(this.EntityInfo.EntityName));
}
else
{
return this.EntityInfo.Columns.Where(it => it.IsPrimarykey).Select(it => it.DbColumnName).ToList();
}
}

我们会发现方法中,首先判断的是this.WhereColumnList是否有数据,如果有数据就不会再通过下面的两种SysTableAttribute模式找主键;

这里的WhereColumnList数据来自我们的更新条件WhereColumns()

        public IUpdateable<T> WhereColumns(string[] columnNames)
{
if (this.WhereColumnList == null) this.WhereColumnList = new List<string>();
foreach (var columnName in columnNames)
{
this.WhereColumnList.Add(columnName);
}
return this;
}

显然当前对象的WhereColumnList并没有获取成功,于是继续向上层探索

定位到异步执行方法ExecuteCommandAsync

        public Task<int> ExecuteCommandAsync()
{
Task<int> result = new Task<int>(() =>
{
IUpdateable<T> asyncUpdateable = CopyUpdateable();
return asyncUpdateable.ExecuteCommand();
});
TaskStart(result);
return result;
}

到这里我们发现实际上异步的时候重新复制了一下当前Updateable对象,即CopyUpdateable(),接下来我们去看看这个方法到底做了什么操作?

        private IUpdateable<T> CopyUpdateable()
{
var asyncContext = this.Context.Utilities.CopyContext(true);
asyncContext.CurrentConnectionConfig.IsAutoCloseConnection = true;
asyncContext.IsAsyncMethod = true; var asyncUpdateable = asyncContext.Updateable<T>(this.UpdateObjs);
var asyncUpdateableBuilder = asyncUpdateable.UpdateBuilder;
asyncUpdateableBuilder.DbColumnInfoList = this.UpdateBuilder.DbColumnInfoList;
asyncUpdateableBuilder.IsNoUpdateNull = this.UpdateBuilder.IsNoUpdateNull;
asyncUpdateableBuilder.Parameters = this.UpdateBuilder.Parameters;
asyncUpdateableBuilder.sql = this.UpdateBuilder.sql;
asyncUpdateableBuilder.WhereValues = this.UpdateBuilder.WhereValues;
asyncUpdateableBuilder.TableWithString = this.UpdateBuilder.TableWithString;
asyncUpdateableBuilder.TableName = this.UpdateBuilder.TableName;
asyncUpdateableBuilder.PrimaryKeys = this.UpdateBuilder.PrimaryKeys;
asyncUpdateableBuilder.IsOffIdentity = this.UpdateBuilder.IsOffIdentity;
asyncUpdateableBuilder.SetValues = this.UpdateBuilder.SetValues;
if (this.RemoveCacheFunc != null)
{
asyncUpdateable.RemoveDataCache();
}
return asyncUpdateable;
}

从上面这个方法中,我们能够看出异步的更新对象asyncUpdateable并没有绑定WhereColumnList,所以在调用asyncUpdateable.ExecuteCommand()GetPrimaryKeys方法中当前对象的WhereColumnList没有值

        public virtual int ExecuteCommand()
{
PreToSql();
AutoRemoveDataCache();
Check.Exception(UpdateBuilder.WhereValues.IsNullOrEmpty() && GetPrimaryKeys().IsNullOrEmpty(), "You cannot have no primary key and no conditions");
string sql = UpdateBuilder.ToSqlString();
ValidateVersion();
RestoreMapping();
Before(sql);
var result = this.Ado.ExecuteCommand(sql, UpdateBuilder.Parameters == null ? null : UpdateBuilder.Parameters.ToArray());
After(sql);
return result;
}

针对如上框架问题,我们可以:

1)IUpdateable接口添加属性:List<string> WhereColumnList { get; set; }

2)CopyUpdateable()方法中,给异步更新对象asyncUpdateable绑定WhereColumnList。

   private IUpdateable<T> CopyUpdateable()
{
var asyncContext = this.Context.Utilities.CopyContext(true);
asyncContext.CurrentConnectionConfig.IsAutoCloseConnection = true;
asyncContext.IsAsyncMethod = true; var asyncUpdateable = asyncContext.Updateable<T>(this.UpdateObjs);
var asyncUpdateableBuilder = asyncUpdateable.UpdateBuilder;
asyncUpdateableBuilder.DbColumnInfoList = this.UpdateBuilder.DbColumnInfoList;
asyncUpdateableBuilder.IsNoUpdateNull = this.UpdateBuilder.IsNoUpdateNull;
asyncUpdateableBuilder.Parameters = this.UpdateBuilder.Parameters;
asyncUpdateableBuilder.sql = this.UpdateBuilder.sql;
asyncUpdateableBuilder.WhereValues = this.UpdateBuilder.WhereValues;
asyncUpdateableBuilder.TableWithString = this.UpdateBuilder.TableWithString;
asyncUpdateableBuilder.TableName = this.UpdateBuilder.TableName;
asyncUpdateableBuilder.PrimaryKeys = this.UpdateBuilder.PrimaryKeys;
asyncUpdateableBuilder.IsOffIdentity = this.UpdateBuilder.IsOffIdentity;
asyncUpdateableBuilder.SetValues = this.UpdateBuilder.SetValues;
asyncUpdateable.WhereColumnList = this.WhereColumnList;
if (this.RemoveCacheFunc != null)
{
asyncUpdateable.RemoveDataCache();
}
return asyncUpdateable;
}

至此,关于框架中主键的问题终于搞清楚来龙去脉了。。。

还没结束,哈哈!!!

既然我们都发现了这个问题,你说人家框架发布者能没发现么。。。于是,在sqlSugarCore4.9.9.9版本之后,作者对异步更新操作ExecuteCommandAsync这一问题进行了修复。

我们简单来看看是怎么优化的:

        public async Task<int> ExecuteCommandAsync()
{
string sql = _ExecuteCommand();
var result =await this.Ado.ExecuteCommandAsync(sql, UpdateBuilder.Parameters == null ? null : UpdateBuilder.Parameters.ToArray());
After(sql);
return result;
}
        private string _ExecuteCommand()
{
PreToSql();
AutoRemoveDataCache();
Check.Exception(UpdateBuilder.WhereValues.IsNullOrEmpty() && GetPrimaryKeys().IsNullOrEmpty(), "You cannot have no primary key and no conditions");
string sql = UpdateBuilder.ToSqlString();
ValidateVersion();
RestoreMapping();
Before(sql);
return sql;
}

我们会发现ExecuteCommandAsync异步方法中,不再通过复制异步更新对象的方式,而是直接采用了当前调用的更新对象UpdateableProvider,该对象已实现WhereColumnList属性赋值。

好了,下次大家如果遇到同样的问题,可以直接升级到最新版本;当然,也可以参考下文章中说的几种简单的方案哦。

记SqlSugar ORM框架之找不到主键问题的更多相关文章

  1. SqlSugar ORM框架文档

    http://www.codeisbug.com/Doc/8/1141 SqlSugar入门级教程+实例 (.net core下的)https://www.cnblogs.com/rulasann/p ...

  2. ASP.NET - SqlSugar ORM框架 更新列表

    以后SqlSugar所有更新都会在这个贴子更新 SqlSugar是一款轻量级的MSSQL ORM ,除了具有媲美ADO的性能外还具有和EF相似简单易用的语法. 学习列表 0.功能更新 1.SqlSug ...

  3. .Net Core 3.0 MVC 中使用 SqlSugar ORM框架

    介绍 SqlSugar 是一款简单易用的ORM ,在国内市场占有率也比较高, 在今年10月份官网改版后  提供了完整的服务,让您的项目没有后顾之忧 下载地址 :https://github.com/s ...

  4. Hibernate框架--配置,映射,主键

    SSH框架: Struts框架, 基于mvc模式的应用层框架技术! Hibernate,    基于持久层的框架(数据访问层使用)! Spring,   创建对象处理对象的依赖关系以及框架整合! Da ...

  5. hibernate框架学习笔记4:主键生成策略、对象状态

    创建一个实体类: package domain; public class Customer { private Long cust_id; private String cust_name; pri ...

  6. MyBatis框架——mybatis插入数据返回主键(mysql、oracle)

    向数据库中插入数据时,大多数情况都会使用自增列或者UUID做为主键.主键的值都是插入之前无法知道的,但很多情况下我们在插入数据后需要使用刚刚插入数据的主键,比如向两张关联表A.B中插入数据(A的主键是 ...

  7. yii2框架中获取添加元素的主键

    控制器层: 模型层: 返回结果是:

  8. 高性能ORM 框架之 MySqlSugar

    mysql 3.X API地址:  http://www.cnblogs.com/sunkaixuan/p/5987308.html MySqlSugar 1.5 API 一.介简 SqlSugar ...

  9. SqlSugar ORM 入门到精通【一】入门篇

    背景 SqlSugar是一款国产ORM,除了拥有媲美原生的性能 另外还有满足各种需求的功能,简单好用一分钟就能够轻松上手. 2.x版本正式在自已公司内部项目使用 3.x版本得到了更多公司的喜欢不过也会 ...

随机推荐

  1. 东方步进电机马达驱动板CVK系列说明书

    东方步进电机马达驱动板CVK系列说明书

  2. psp表格

    陈康杰psp表格 PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 10 10 Estimate 估计这个任务 ...

  3. Java基础-Java中transient有什么用-序列化有那几种方式

    此文转载于知乎的一篇文章,看着写的非常全面,分享给大家. 先解释下什么是序列化 我们的对象并不只是存在内存中,还需要传输网络,或者保存起来下次再加载出来用,所以需要Java序列化技术. Java序列化 ...

  4. 06.DBUnit实际运用

    在上面的代码中 package com.fjnu.service; import java.io.FileWriter; import java.sql.SQLException; import st ...

  5. 四层发现-TCP和UDP发现简介

    虽然这里使用到了端口发现,但是四层发现阶段并不对端口进行解析,而是通过端口进行对ip是否存活的判断. 这里是对主机的发现,而不是对端口的识别. 四层发现的结果比三层发现的结果更加精确,基本不会被防火墙 ...

  6. npm -v 报错:Error: EPERM: operation not permitted, mkdir 'C:\soft\nodejs'

    npm -v 报错:Error: EPERM: operation not permitted, mkdir 'C:\soft\nodejs' 起因:原本安装node在C盘soft文件夹下,按node ...

  7. python+opencv实现图像自适应阈值的均衡化

    内容涉及:列表遍历,图像均衡化,图像通道分离与合并 import cv2 import numpy as np import os for path in open("org_junheng ...

  8. 新手安装配置git简洁教程

    第一步,下载安装git 打开 [git官网] https://git-scm.com/,下载git对应操作系统的版本. 所有东西下载慢的话就可以去找镜像!官网下载太慢,我们可以使用淘宝镜像下载:htt ...

  9. cbitmap 获取RGB

    CBitMap的用法   MFC提供了位图处理的基础类CBitmap,可以完成位图(bmp图像)的创建.图像数据的获取等功能.虽然功能比较少,但是在对位图进行一些简单的处理时,CBitmap类还是可以 ...

  10. ASP.NET MVC 四种Controller向View传值方法

    控制器: // Get: Data public ActionResult Index() { //ViewData 方式 ViewData["UserName"] = " ...