记SqlSugarORM框架之不到主键问题
前端时间在.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],
< 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 lid = c.colid
pe = '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 pe = pe
LEFT JOIN sysobjects ON syscolumns.id = sysobjects.id
LEFT OUTER ded_properties ON (ded_properties.minor_id = lid
ded_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 lid";
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是否有数据,如果有数据就不会再通过下⾯的两种SysTable和Attribute模式主键;
这⾥的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属性赋值。
好了,下次⼤家如果遇到同样的问题,可以直接升级到最新版本;当然,也可以参考下⽂章中说的⼏种简单的⽅案哦。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。