diff --git a/Examples/base_entity/Program.cs b/Examples/base_entity/Program.cs index 8df34c98..2cbe3f61 100644 --- a/Examples/base_entity/Program.cs +++ b/Examples/base_entity/Program.cs @@ -528,7 +528,7 @@ namespace base_entity //.UseSlaveWeight(10, 1, 1, 5) - //.UseConnectionString(FreeSql.DataType.Firebird, @"database=localhost:D:\fbdata\EXAMPLES.fdb;user=sysdba;password=123456;max pool size=5") + .UseConnectionString(FreeSql.DataType.Firebird, @"database=localhost:D:\fbdata\EXAMPLES.fdb;user=sysdba;password=123456;max pool size=5") //.UseQuoteSqlName(false) //.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;min pool size=1;Max pool size=2") @@ -569,6 +569,15 @@ namespace base_entity #endregion fsql.UseJsonMap(); + var updatejoin01 = fsql.Update() + .Join(fsql.Select(), (a, b) => a.GroupId == b.Id) + .Set((a, b) => a.Nickname == b.GroupName) + .ExecuteAffrows(); + var updatejoin02 = fsql.Update() + .Join((a, b) => a.GroupId == b.Id) + .Set((a, b) => a.Nickname == b.GroupName) + .ExecuteAffrows(); + fsql.Select().IncludeMany(a => a.Roles); var displayNameTb = fsql.CodeFirst.GetTableByEntity(typeof(DeviceCodes)); diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index 26522f10..537315e2 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -800,14 +800,5 @@ - - - 批量注入 Repository,可以参考代码自行调整 - - - - - - diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 48207741..32b185cc 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -2871,6 +2871,33 @@ 所有元素 + + + 联表更新(危险操作),支持更复杂的联表更新 + fsql.Update<T1>() + .Join(fsql.Select<T1>(), (a, b) => a.id == b.id) + .Set((a, b) => a.name == b.name) + .Set((a, b) => a.time == b.time2) + .ExecuteAffrows(); + + + + + + + + + 联表更新(危险操作) + fsql.Update<T1>() + .Join<T2>((a, b) => a.id == b.id) + .Set((a, b) => a.name == b.name) + .Set((a, b) => a.time == b.time2) + .ExecuteAffrows(); + + + + + 指定事务对象 @@ -3003,7 +3030,7 @@ - 设置列的的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 + 设置列的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 指定更新,格式:Set(a => new T { Clicks = a.Clicks + 1, Time = DateTime.Now }) 相当于 set clicks=clicks+1,time='2019-06-19....' @@ -3013,7 +3040,7 @@ - 设置列的的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 + 设置列的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 指定更新,格式:Set(a => new T { Clicks = a.Clicks + 1, Time = DateTime.Now }) 相当于 set clicks=clicks+1,time='2019-06-19....' @@ -3136,6 +3163,125 @@ + + + 指定事务对象 + + + + + + + 指定事务对象 + + + + + + + 命令超时设置(秒) + + + + + + + 设置列的固定新值,Set(a => a.Name, "newvalue") + + + lambda选择列 + 新值 + + + + + 设置列的固定新值,Set(a => a.Name, "newvalue") + + + true 时生效 + lambda选择列 + 新值 + + + + + 设置列的联表值,格式: + Set((a, b) => a.Clicks == b.xxx) + Set((a, b) => a.Clicks == a.Clicks + 1) + + + + + + + 设置列的联表值,格式: + Set((a, b) => a.Clicks == b.xxx) + Set((a, b) => a.Clicks == a.Clicks + 1) + + + true 时生效 + + + + + + 设置值,自定义SQL语法,SetRaw("title = @title", new { title = "newtitle" }) + 提示:parms 参数还可以传 Dictionary<string, object> + + sql语法 + 参数 + + + + + lambda表达式条件,仅支持实体基础成员(不包含导航对象) + + lambda表达式条件 + + + + + lambda表达式条件,仅支持实体基础成员(不包含导航对象) + + true 时生效 + lambda表达式条件 + + + + + 原生sql语法条件,Where("id = @id", new { id = 1 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + sql语法条件 + 参数 + + + + + 禁用全局过滤功能,不传参数时将禁用所有 + + 零个或多个过滤器名字 + + + + + 设置表名 + + + + + + + 返回即将执行的SQL语句 + + + + + + 执行SQL语句,返回影响的行数 + + + 主库连接池 diff --git a/FreeSql/Interface/Curd/IUpdate.cs b/FreeSql/Interface/Curd/IUpdate.cs index dee6e002..5810bd4c 100644 --- a/FreeSql/Interface/Curd/IUpdate.cs +++ b/FreeSql/Interface/Curd/IUpdate.cs @@ -10,6 +10,31 @@ namespace FreeSql { public interface IUpdate { + /// + /// 联表更新(危险操作),支持更复杂的联表更新 + /// fsql.Update<T1>() + /// .Join(fsql.Select<T1>(), (a, b) => a.id == b.id) + /// .Set((a, b) => a.name == b.name) + /// .Set((a, b) => a.time == b.time2) + /// .ExecuteAffrows(); + /// + /// + /// + /// + /// + IUpdateJoin Join(ISelect query, Expression> on) where T2 : class; + /// + /// 联表更新(危险操作) + /// fsql.Update<T1>() + /// .Join<T2>((a, b) => a.id == b.id) + /// .Set((a, b) => a.name == b.name) + /// .Set((a, b) => a.time == b.time2) + /// .ExecuteAffrows(); + /// + /// + /// + /// + IUpdateJoin Join(Expression> on) where T2 : class; /// /// 指定事务对象 @@ -134,7 +159,7 @@ namespace FreeSql /// IUpdate SetIf(bool condition, Expression> column, TMember value); /// - /// 设置列的的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 + /// 设置列的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 /// /// 指定更新,格式:Set(a => new T { Clicks = a.Clicks + 1, Time = DateTime.Now }) 相当于 set clicks=clicks+1,time='2019-06-19....' /// @@ -143,7 +168,7 @@ namespace FreeSql /// IUpdate Set(Expression> exp); /// - /// 设置列的的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 + /// 设置列的新值为基础上增加,格式:Set(a => a.Clicks + 1) 相当于 clicks=clicks+1 /// /// 指定更新,格式:Set(a => new T { Clicks = a.Clicks + 1, Time = DateTime.Now }) 相当于 set clicks=clicks+1,time='2019-06-19....' /// diff --git a/FreeSql/Interface/Curd/IUpdateJoin.cs b/FreeSql/Interface/Curd/IUpdateJoin.cs new file mode 100644 index 00000000..1ebecf36 --- /dev/null +++ b/FreeSql/Interface/Curd/IUpdateJoin.cs @@ -0,0 +1,127 @@ +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql +{ + public interface IUpdateJoin + { + /// + /// 指定事务对象 + /// + /// + /// + IUpdateJoin WithTransaction(DbTransaction transaction); + /// + /// 指定事务对象 + /// + /// + /// + IUpdateJoin WithConnection(DbConnection connection); + /// + /// 命令超时设置(秒) + /// + /// + /// + IUpdateJoin CommandTimeout(int timeout); + + /// + /// 设置列的固定新值,Set(a => a.Name, "newvalue") + /// + /// + /// lambda选择列 + /// 新值 + /// + IUpdateJoin Set(Expression> column, TMember value); + /// + /// 设置列的固定新值,Set(a => a.Name, "newvalue") + /// + /// + /// true 时生效 + /// lambda选择列 + /// 新值 + /// + IUpdateJoin SetIf(bool condition, Expression> column, TMember value); + /// + /// 设置列的联表值,格式: + /// Set((a, b) => a.Clicks == b.xxx) + /// Set((a, b) => a.Clicks == a.Clicks + 1) + /// + /// + /// + IUpdateJoin Set(Expression> exp); + /// + /// 设置列的联表值,格式: + /// Set((a, b) => a.Clicks == b.xxx) + /// Set((a, b) => a.Clicks == a.Clicks + 1) + /// + /// + /// true 时生效 + /// + /// + IUpdateJoin SetIf(bool condition, Expression> exp); + /// + /// 设置值,自定义SQL语法,SetRaw("title = @title", new { title = "newtitle" }) + /// 提示:parms 参数还可以传 Dictionary<string, object> + /// + /// sql语法 + /// 参数 + /// + IUpdateJoin SetRaw(string sql, object parms = null); + + /// + /// lambda表达式条件,仅支持实体基础成员(不包含导航对象) + /// + /// lambda表达式条件 + /// + IUpdateJoin Where(Expression> exp); + /// + /// lambda表达式条件,仅支持实体基础成员(不包含导航对象) + /// + /// true 时生效 + /// lambda表达式条件 + /// + IUpdateJoin WhereIf(bool condition, Expression> exp); + /// + /// 原生sql语法条件,Where("id = @id", new { id = 1 }) + /// 提示:parms 参数还可以传 Dictionary<string, object> + /// + /// sql语法条件 + /// 参数 + /// + IUpdateJoin Where(string sql, object parms = null); + + /// + /// 禁用全局过滤功能,不传参数时将禁用所有 + /// + /// 零个或多个过滤器名字 + /// + IUpdateJoin DisableGlobalFilter(params string[] name); + + /// + /// 设置表名 + /// + /// + /// + IUpdateJoin AsTable(string tableName); + /// + /// 返回即将执行的SQL语句 + /// + /// + string ToSql(); + /// + /// 执行SQL语句,返回影响的行数 + /// + /// + int ExecuteAffrows(); + +#if net40 +#else + Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default); +#endif + } +} \ No newline at end of file diff --git a/FreeSql/Internal/CommonProvider/UpdateJoinProvider.cs b/FreeSql/Internal/CommonProvider/UpdateJoinProvider.cs new file mode 100644 index 00000000..2e964195 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/UpdateJoinProvider.cs @@ -0,0 +1,306 @@ +using FreeSql; +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal.Model; +using FreeSql.Internal.ObjectPool; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql.Internal.CommonProvider +{ + public class UpdateJoinProvider : IUpdateJoin where T1 : class where T2 : class + { + public IUpdate _update; + public UpdateProvider _updateProvider; + public ISelect _query; + public Select0Provider _queryProvider; + public ISelect _query2; + public Select2Provider _query2Provider; + + public IFreeSql _orm; + public CommonUtils _commonUtils; + public CommonExpression _commonExpression; + public string _joinOn; + public string _tableName; + + public UpdateJoinProvider(IUpdate update, ISelect query, Expression> on) + { + _update = update; + _updateProvider = _update as UpdateProvider; + _orm = _updateProvider._orm; + _commonUtils = _updateProvider._commonUtils; + _commonExpression = _updateProvider._commonExpression; + + ValidateDataType(null, null, null, null, null); + _query = query; + _queryProvider = _query as Select0Provider; + _query2 = _orm.Select().DisableGlobalFilter().FromQuery(_query); + _query2Provider = _query2 as Select2Provider; + + _query2Provider._where.Clear(); + _query2.Where(on); + _joinOn = _query2Provider._where.ToString(); + if (_joinOn.StartsWith(" AND ")) _joinOn = _joinOn.Substring(5); + + _updateProvider.Where("1=1"); + } + + public void ValidateDataType(Action InterceptSqlServer, Action InterceptMySql, Action InterceptPostgreSQL, Action InterceptMergeInto, Action InterceptGBase) + { + switch (_orm.Ado.DataType) + { + case DataType.SqlServer: + case DataType.OdbcSqlServer: + case DataType.CustomSqlServer: + InterceptSqlServer?.Invoke(); break; + case DataType.MySql: + case DataType.OdbcMySql: + case DataType.CustomMySql: + case DataType.MsAccess: + InterceptMySql?.Invoke(); break; + case DataType.PostgreSQL: + case DataType.OdbcPostgreSQL: + case DataType.CustomPostgreSQL: + case DataType.KingbaseES: + case DataType.OdbcKingbaseES: + case DataType.ShenTong: + InterceptPostgreSQL?.Invoke(); break; + case DataType.Oracle: + case DataType.OdbcOracle: + case DataType.CustomOracle: + case DataType.Dameng: + case DataType.OdbcDameng: + case DataType.Firebird: + InterceptMergeInto?.Invoke(); break; + case DataType.GBase: + InterceptGBase?.Invoke(); break; + + default: throw new Exception($"{_orm.Ado.DataType} 暂时不支持 update join 操作。"); + } + } + + #region proxy IUpdate + public IUpdateJoin AsTable(string tableName) + { + _update.AsTable(tableName); + return this; + } + public IUpdateJoin WithConnection(DbConnection connection) + { + _update.WithConnection(connection); + return this; + } + + public IUpdateJoin WithTransaction(DbTransaction transaction) + { + _update.WithTransaction(transaction); + return this; + } + public IUpdateJoin CommandTimeout(int timeout) + { + _update.CommandTimeout(timeout); + return this; + } + public IUpdateJoin DisableGlobalFilter(params string[] name) + { + _update.DisableGlobalFilter(name); + return this; + } + public IUpdateJoin Set(Expression> column, TMember value) + { + _update.Set(column, value); + return this; + } + public IUpdateJoin SetIf(bool condition, Expression> column, TMember value) + { + _update.SetIf(condition, column, value); + return this; + } + public IUpdateJoin SetRaw(string sql, object parms = null) + { + _update.SetRaw(sql, parms); + return this; + } + public IUpdateJoin Where(string sql, object parms = null) + { + _update.Where(sql, parms); + return this; + } + #endregion + + public IUpdateJoin Where(Expression> exp) => WhereIf(true, exp); + public IUpdateJoin WhereIf(bool condition, Expression> exp) + { + if (condition == false) return this; + _query2Provider._where.Clear(); + _query2.Where(exp); + _updateProvider._where.Append(_query2Provider._where); + return this; + } + + public IUpdateJoin Set(Expression> exp) => SetIf(true, exp); + public IUpdateJoin SetIf(bool condition, Expression> exp) + { + var body = exp?.Body; + var nodeType = body?.NodeType; + if (nodeType == ExpressionType.Convert) + { + body = (body as UnaryExpression)?.Operand; + nodeType = body?.NodeType; + } + switch (nodeType) + { + case ExpressionType.Equal: + break; + default: + throw new Exception("格式错了,请使用 .Set((a,b) => a.name == b.xname)"); + } + + var equalBinaryExp = body as BinaryExpression; + var cols = new List(); + _commonExpression.ExpressionSelectColumn_MemberAccess(null, null, cols, SelectTableInfoType.From, equalBinaryExp.Left, true, null); + if (cols.Count != 1) return this; + var col = cols[0].Column; + var columnSql = $"{_commonUtils.QuoteSqlName(col.Attribute.Name)}"; + + _query2Provider._groupby = null; + var valueExp = Expression.Lambda>(equalBinaryExp.Right, exp.Parameters); + _query2.GroupBy(valueExp); + var valueSql = _query2Provider._groupby?.Remove(0, " \r\nGROUP BY ".Length); + if (string.IsNullOrEmpty(valueSql)) return this; + + switch (_orm.Ado.DataType) + { + case DataType.PostgreSQL: + case DataType.OdbcPostgreSQL: + case DataType.CustomPostgreSQL: + case DataType.KingbaseES: + case DataType.OdbcKingbaseES: + case DataType.ShenTong: + break; + default: + columnSql = $"{_query2Provider._tables[0].Alias}.{columnSql}"; //set a.name = b.name + break; + } + + _update.SetRaw($"{columnSql} = {valueSql}"); + return this; + } + + void InterceptSql(StringBuilder sb) + { + var sql = sb.ToString(); + if (!sql.StartsWith("UPDATE ")) return; + var setStartIndex = sql.IndexOf(" SET "); + if (setStartIndex == -1) return; + var sqltab = sql.Substring(7, setStartIndex - 7); + var sqlset = ""; + var sqlwhere = ""; + var sqltab2 = _query2Provider._tableRules.FirstOrDefault()?.Invoke(typeof(T2), null).Replace(" \r\n", " \r\n ") ?? _commonUtils.QuoteSqlName(_query2Provider._tables[1].Table?.DbName); + var whereStartIndex = sql.IndexOf(" \r\nWHERE ", setStartIndex); + if (whereStartIndex == -1) + { + sqlset = sql.Substring(setStartIndex + 5); + } + else + { + sqlset = sql.Substring(setStartIndex + 5, whereStartIndex - setStartIndex - 5); + sqlwhere = sql.Substring(whereStartIndex); + if (sqlwhere == " \r\nWHERE (1=1)") + sqlwhere = ""; + else if (sqlwhere.StartsWith(" \r\nWHERE (1=1) AND ")) + sqlwhere = $" \r\nWHERE {sqlwhere.Substring(" \r\nWHERE (1=1) AND ".Length)}"; + } + string t0alias = _query2Provider._tables[0].Alias; + string t1alias = _query2Provider._tables[1].Alias; + + ValidateDataType(InterceptSqlServer, InterceptMySql, InterceptPostgreSQL, InterceptMergeInto, InterceptGBase); + void InterceptSqlServer() + { + sb.Clear().Append("UPDATE ").Append(t0alias).Append(" SET ").Append(sqlset) + .Append(" \r\nFROM ").Append(sqltab).Append(_commonUtils.FieldAsAlias(t0alias)) + .Append(" \r\nINNER JOIN ").Append(sqltab2).Append(_commonUtils.FieldAsAlias(t1alias)).Append(" ON ").Append(_joinOn) + .Append(sqlwhere); + } + void InterceptMySql() + { + sb.Clear().Append("UPDATE ").Append(sqltab).Append(_commonUtils.FieldAsAlias(t0alias)) + .Append(" \r\nINNER JOIN ").Append(sqltab2).Append(_commonUtils.FieldAsAlias(t1alias)).Append(" ON ").Append(_joinOn) + .Append(" \r\nSET ").Append(sqlset) + .Append(sqlwhere); + } + void InterceptPostgreSQL() + { + sb.Clear().Append("UPDATE ").Append(sqltab).Append(_commonUtils.FieldAsAlias(t0alias)) + .Append(" \r\nSET ").Append(sqlset) + .Append(" \r\nFROM ").Append(sqltab2).Append(_commonUtils.FieldAsAlias(t1alias)) + .Append(sqlwhere); + if (string.IsNullOrEmpty(sqlwhere)) sb.Append(" \r\nWHERE ").Append(_joinOn); + else sb.Append(" AND ").Append(_joinOn); + } + void InterceptMergeInto() + { + sb.Clear().Append("MERGE INTO ").Append(sqltab).Append(_commonUtils.FieldAsAlias(t0alias)) + .Append(" \r\nUSING ").Append(sqltab2).Append(_commonUtils.FieldAsAlias(t1alias)).Append(" ON ").Append(_joinOn) + .Append(" \r\nWHEN MATCHED THEN") + .Append(" \r\nUPDATE SET ").Append(sqlset) + .Append(sqlwhere); + } + void InterceptGBase() + { + sb.Clear().Append("UPDATE ").Append(sqltab2).Append(_commonUtils.FieldAsAlias(t1alias)).Append(", ").Append(sqltab).Append(_commonUtils.FieldAsAlias(t0alias)) + .Append(" \r\nSET ").Append(sqlset) + .Append(sqlwhere); + if (string.IsNullOrEmpty(sqlwhere)) sb.Append(" \r\nWHERE ").Append(_joinOn); + else sb.Append(" AND ").Append(_joinOn); + } + } + + public string ToSql() + { + _updateProvider._interceptSql = InterceptSql; + try + { + return _update.ToSql(); + } + finally + { + _updateProvider._interceptSql = null; + } + } + public int ExecuteAffrows() + { + _updateProvider._interceptSql = InterceptSql; + try + { + return _update.ExecuteAffrows(); + } + finally + { + _updateProvider._interceptSql = null; + } + } +#if net40 +#else + async public Task ExecuteAffrowsAsync(CancellationToken cancellationToken = default) + { + _updateProvider._interceptSql = InterceptSql; + try + { + return await _update.ExecuteAffrowsAsync(cancellationToken); + } + finally + { + _updateProvider._interceptSql = null; + } + } +#endif + } +} diff --git a/FreeSql/Internal/CommonProvider/UpdateProvider.cs b/FreeSql/Internal/CommonProvider/UpdateProvider.cs index 5e786eff..934cfbc0 100644 --- a/FreeSql/Internal/CommonProvider/UpdateProvider.cs +++ b/FreeSql/Internal/CommonProvider/UpdateProvider.cs @@ -188,6 +188,15 @@ namespace FreeSql.Internal.CommonProvider _ignoreVersion = false; } + public IUpdateJoin Join(Expression> on) where T2 : class => Join(_orm.Select(), on); + public IUpdateJoin Join(ISelect query, Expression> on) where T2 : class + { + var ctor = typeof(UpdateJoinProvider<,>).MakeGenericType(typeof(T1), typeof(T2)) + .GetConstructor(new[] { typeof(IUpdate), typeof(ISelect), typeof(Expression>) }); + if (ctor == null) throw new Exception(CoreStrings.Type_Cannot_Access_Constructor("UpdateJoinProvider<>")); + return ctor.Invoke(new object[] { this, query, on }) as IUpdateJoin; + } + public IUpdate WithTransaction(DbTransaction transaction) { _transaction = transaction;