diff --git a/FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs b/FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs index 914f75161..dea7c54ee 100644 --- a/FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs +++ b/FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs @@ -11,7 +11,31 @@ namespace FreeSql /// /// 工作单元管理器 /// - public class UnitOfWorkManager : IDisposable + public interface IUnitOfWorkManager : IDisposable + { + IFreeSql Orm { get; } + /// + /// 当前的工作单元 + /// + IUnitOfWork Current { get; } + /// + /// 将仓储的事务交给我管理 + /// + /// + void Binding(IBaseRepository repository); + /// + /// 创建工作单元 + /// + /// 事务传播方式 + /// 事务隔离级别 + /// + IUnitOfWork Begin(Propagation propagation = Propagation.Required, IsolationLevel? isolationLevel = null); + } + + /// + /// 工作单元管理器 + /// + public class UnitOfWorkManager : IUnitOfWorkManager { internal DbContextScopedFreeSql _ormScoped; internal IFreeSql OrmOriginal => _ormScoped?._originalFsql; diff --git a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLInsertOrUpdateTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLInsertOrUpdateTest.cs index fb0b8be92..45f141891 100644 --- a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLInsertOrUpdateTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLInsertOrUpdateTest.cs @@ -98,11 +98,37 @@ ON CONFLICT(""id"") DO UPDATE SET var lst = fsql.Select().Where(a => new[] { 1, 2, 3, 4 }.Contains(a.id)).ToList(); Assert.Equal(4, lst.Where(a => a.name == "00" + a.id).Count()); } + class tbiou02 { public int id { get; set; } public string name { get; set; } } + + [Fact] + public void InsertOrUpdate_TempPrimary() + { + fsql.Delete().Where("1=1").ExecuteAffrows(); + var iou = fsql.InsertOrUpdate().SetSource(new tbiou_temp { name = "01", description = "testval" }, m => new { m.name }); + var sql = iou.ToSql(); + Assert.Equal(@"INSERT INTO ""tbiou_temp""(""name"", ""description"") VALUES('01', 'testval') +ON CONFLICT(""name"") DO UPDATE SET +""description"" = EXCLUDED.""description""", sql); + Assert.Equal(1, iou.ExecuteAffrows()); + + var iou2 = fsql.InsertOrUpdate().SetSource(new tbiou_temp { name = "01", description = "testval2" }, m => new { m.name }).ExecuteAffrows(); + Assert.Equal(1, iou2); + + } + [Index("uix_tbiou_temp_name", "name", true)] + class tbiou_temp + { + [Column(IsPrimary = true, IsIdentity = true)] + public int id { get; set; } + + public string name { get; set; } + public string description { get; set; } + } [Fact] public void InsertOrUpdate_OnePrimaryAndIdentity() { diff --git a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs index 14f8eb1d5..135953b65 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs @@ -420,21 +420,102 @@ public static partial class FreeSqlGlobalExtensions case DataType.SqlServer: case DataType.OdbcSqlServer: case DataType.CustomSqlServer: + { + var oldalias = selectProvider._aliasRule; + selectProvider._aliasRule = (type, old) => + { + if (oldalias != null) old = oldalias(type, old); + if (string.IsNullOrWhiteSpace(indexName) == false && type == selectProvider._tables[0].Table.Type) return LocalAppendWithString(old, $"index={indexName}"); + if (rule == null) return old; + return rule.TryGetValue(type, out var tryidxName) && string.IsNullOrWhiteSpace(tryidxName) == false ? LocalAppendWithString(old, $"index={tryidxName}") : old; + }; + } + break; + case DataType.MySql: + case DataType.OdbcMySql: + case DataType.CustomMySql: + { + var oldalias = selectProvider._aliasRule; + selectProvider._aliasRule = (type, old) => + { + if (oldalias != null) old = oldalias(type, old); + if (string.IsNullOrWhiteSpace(indexName) == false && type == selectProvider._tables[0].Table.Type) return LocalAppendMySqlIndex(old, indexName); + if (rule == null) return old; + return rule.TryGetValue(type, out var tryidxName) && string.IsNullOrWhiteSpace(tryidxName) == false ? LocalAppendMySqlIndex(old, indexName) : old; + }; + } + break; + case DataType.Oracle: + case DataType.OdbcOracle: + case DataType.CustomOracle: + { + var hintParts = new List(); + if (!string.IsNullOrEmpty(indexName) && !string.IsNullOrEmpty(selectProvider._tables[0].Alias)) + { + string alias = selectProvider._tables[0].Alias; + hintParts.Add($"INDEX({alias} {indexName})"); + } + if (rule != null && rule.Count > 0) + { + var tablesMap = selectProvider._tables + .Skip(1) + .Where(t => !string.IsNullOrEmpty(t.Alias) && t.Table?.Type != null) + .ToDictionary(t => t.Table.Type, t => t); + foreach (var indexRule in rule) + { + if (tablesMap.TryGetValue(indexRule.Key, out var tableInfo)) + { + string otherIndexName = indexRule.Value; + if (!string.IsNullOrEmpty(otherIndexName)) + hintParts.Add($"INDEX({tableInfo.Alias} {otherIndexName})"); + } + } + } + if (hintParts.Count > 0) + { + string finalHint = $"/*+ {string.Join(" ", hintParts)} */"; + var _select = selectProvider._select; + int selectKeywordIndex = _select.IndexOf("SELECT ", StringComparison.OrdinalIgnoreCase); + if (selectKeywordIndex != -1) + { + int insertionPoint = selectKeywordIndex + "SELECT ".Length; + selectProvider._select = _select.Insert(insertionPoint, $"{finalHint} "); + } + } + } + break; + case DataType.Sqlite: + { + var oldalias = selectProvider._aliasRule; + selectProvider._aliasRule = (type, old) => + { + if (oldalias != null) old = oldalias(type, old); + if (string.IsNullOrWhiteSpace(indexName) == false && type == selectProvider._tables[0].Table.Type) return LocalAppendSqliteIndex(old, indexName); + if (rule == null) return old; + return rule.TryGetValue(type, out var tryidxName) && string.IsNullOrWhiteSpace(tryidxName) == false ? LocalAppendSqliteIndex(old, indexName) : old; + }; + } break; - default: - return query; } - var oldalias = selectProvider._aliasRule; - selectProvider._aliasRule = (type, old) => - { - if (oldalias != null) old = oldalias(type, old); - if (string.IsNullOrWhiteSpace(indexName) == false && type == selectProvider._tables[0].Table.Type) return LocalAppendWithString(old, $"index={indexName}"); - if (rule == null) return old; - return rule.TryGetValue(type, out var tryidxName) && string.IsNullOrWhiteSpace(tryidxName) == false ? LocalAppendWithString(old, $"index={tryidxName}") : old; - }; + return query; } static string LocalAppendWithString(string old, string str) => old?.Contains(" With(") == true ? old.Replace(" With(", $" With({str}, ") : $"{old} With({str})"; + static string LocalAppendMySqlIndex(string old, string indexName) + { + if (string.IsNullOrEmpty(old)) return $"FORCE INDEX({indexName})"; + int forceIndexPosition = old.IndexOf("FORCE INDEX(", StringComparison.OrdinalIgnoreCase); + if (forceIndexPosition == -1) return $"{old} FORCE INDEX({indexName})"; + int closingParenPosition = old.IndexOf(')', forceIndexPosition); + if (closingParenPosition != -1) return old.Insert(closingParenPosition, $",{indexName}"); + return old; + } + static string LocalAppendSqliteIndex(string old, string indexName) + { + if (string.IsNullOrEmpty(old)) return $"INDEXED BY {indexName}"; + if (old.IndexOf("INDEXED BY", StringComparison.OrdinalIgnoreCase) != -1) return old; + return $"{old} INDEXED BY {indexName}"; + } /// /// 设置全局 SqlServer: with(nolock) diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs index 4997b0c9b..3731b762b 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseInsert.cs @@ -185,14 +185,21 @@ namespace FreeSql.ClickHouse.Curd before = new Aop.CurdBeforeEventArgs(_table.Type, _table, Aop.CurdType.Insert, null, _params); _orm.Aop.CurdBeforeHandler?.Invoke(this, before); var data = ToDataTable(); + var columns = new string[_table.ColumnsByPosition.Length]; + for (var i = 0; i < columns.Length; i++) + { + columns[i] = _table.ColumnsByPosition[i].CsName; + } using (var conn = await _orm.Ado.MasterPool.GetAsync()) { using (var bulkCopyInterface = new ClickHouseBulkCopy(conn.Value as ClickHouseConnection) { DestinationTableName = data.TableName, - BatchSize = _source.Count + BatchSize = _source.Count, + ColumnNames = columns, }) { + await bulkCopyInterface.InitAsync(); await bulkCopyInterface.WriteToServerAsync(data, default); } } @@ -201,7 +208,7 @@ namespace FreeSql.ClickHouse.Curd catch (Exception ex) { exception = ex; - throw ex; + throw; } finally { diff --git a/Providers/FreeSql.Provider.Custom/PostgreSQL/Curd/CustomPostgreSQLInsertOrUpdate.cs b/Providers/FreeSql.Provider.Custom/PostgreSQL/Curd/CustomPostgreSQLInsertOrUpdate.cs index ed3716f17..03725d1ea 100644 --- a/Providers/FreeSql.Provider.Custom/PostgreSQL/Curd/CustomPostgreSQLInsertOrUpdate.cs +++ b/Providers/FreeSql.Provider.Custom/PostgreSQL/Curd/CustomPostgreSQLInsertOrUpdate.cs @@ -56,7 +56,7 @@ namespace FreeSql.Custom.PostgreSQL if (IdentityColumn != null && flagInsert) sql = insert.ToSql(); else { - var ocdu = new CustomPostgreSQLOnConflictDoUpdate(insert.InsertIdentity()); + var ocdu = new CustomPostgreSQLOnConflictDoUpdate(_tempPrimarys?.Length > 0 ? insert : insert.InsertIdentity()); ocdu._tempPrimarys = _tempPrimarys; var cols = _table.Columns.Values.Where(a => _updateSetDict.ContainsKey(a.Attribute.Name) || _tempPrimarys.Contains(a) == false && a.Attribute.CanUpdate == true && a.Attribute.IsIdentity == false && _updateIgnore.ContainsKey(a.Attribute.Name) == false); diff --git a/Providers/FreeSql.Provider.Duckdb/FreeSql.Provider.Duckdb.csproj b/Providers/FreeSql.Provider.Duckdb/FreeSql.Provider.Duckdb.csproj index 68526bf4d..32fce5cd2 100644 --- a/Providers/FreeSql.Provider.Duckdb/FreeSql.Provider.Duckdb.csproj +++ b/Providers/FreeSql.Provider.Duckdb/FreeSql.Provider.Duckdb.csproj @@ -28,7 +28,7 @@ - + diff --git a/Providers/FreeSql.Provider.KingbaseES/Curd/KingbaseESInsertOrUpdate.cs b/Providers/FreeSql.Provider.KingbaseES/Curd/KingbaseESInsertOrUpdate.cs index 511c09617..13da52b11 100644 --- a/Providers/FreeSql.Provider.KingbaseES/Curd/KingbaseESInsertOrUpdate.cs +++ b/Providers/FreeSql.Provider.KingbaseES/Curd/KingbaseESInsertOrUpdate.cs @@ -56,7 +56,7 @@ namespace FreeSql.KingbaseES if (IdentityColumn != null && flagInsert) sql = insert.ToSql(); else { - var ocdu = new KingbaseESOnConflictDoUpdate(insert.InsertIdentity()); + var ocdu = new KingbaseESOnConflictDoUpdate(_tempPrimarys?.Length > 0 ? insert : insert.InsertIdentity()); ocdu._tempPrimarys = _tempPrimarys; var cols = _table.Columns.Values.Where(a => _updateSetDict.ContainsKey(a.Attribute.Name) || _tempPrimarys.Contains(a) == false && a.Attribute.CanUpdate == true && a.Attribute.IsIdentity == false && _updateIgnore.ContainsKey(a.Attribute.Name) == false); diff --git a/Providers/FreeSql.Provider.KingbaseES/KingbaseESCodeFirst.cs b/Providers/FreeSql.Provider.KingbaseES/KingbaseESCodeFirst.cs index 588b91f1f..1a2e09de6 100644 --- a/Providers/FreeSql.Provider.KingbaseES/KingbaseESCodeFirst.cs +++ b/Providers/FreeSql.Provider.KingbaseES/KingbaseESCodeFirst.cs @@ -149,8 +149,17 @@ namespace FreeSql.KingbaseES protected override string GetComparisonDDLStatements(params TypeSchemaAndName[] objects) { InitIsSysV8R3(); + var builder = new System.Data.Common.DbConnectionStringBuilder + { + ConnectionString = _orm.Ado.ConnectionString + }; + var searchPath = builder.ContainsKey("SearchPath") ? builder["SearchPath"].ToString() : "PUBLIC"; //读取链接字符串中的SearchPath 来确定架构模式 + if (searchPath.Contains(',')) + { + searchPath = searchPath.Split(',')[0]; + } var pg_ = _isSysV8R3 == true ? "sys_" : "pg_"; - var public_ = _isSysV8R3 == true ? "PUBLIC" : "public"; + var public_ = _isSysV8R3 == true ? searchPath.ToUpper() : searchPath; var sb = new StringBuilder(); var seqcols = new List>(); //序列 @@ -182,11 +191,11 @@ namespace FreeSql.KingbaseES var sbalter = new StringBuilder(); var istmpatler = false; //创建临时表,导入数据,删除旧表,修改 - if (_orm.Ado.ExecuteScalar(CommandType.Text, string.Format($" select 1 from {pg_}tables a inner join {pg_}namespace b on b.nspname = a.schemaname where b.nspname || '.' || a.tablename = '{{0}}.{{1}}'", tbname)) == null) + if (_orm.Ado.ExecuteScalar(CommandType.Text, string.Format($" select 1 from {pg_}tables a inner join {pg_}namespace b on b.nspname = a.schemaname where b.nspname ='{{0}}' && a.tablename = '{{1}}'", tbname)) == null)//原判断V9版本存在问题 { //表不存在 if (tboldname != null) { - if (_orm.Ado.ExecuteScalar(CommandType.Text, string.Format($" select 1 from {pg_}tables a inner join {pg_}namespace b on b.nspname = a.schemaname where b.nspname || '.' || a.tablename = '{{0}}.{{1}}'", tboldname)) == null) + if (_orm.Ado.ExecuteScalar(CommandType.Text, string.Format($" select 1 from {pg_}tables a inner join {pg_}namespace b on b.nspname = a.schemaname where b.nspname ='{{0}}' && a.tablename = '{{1}}'", tboldname)) == null) //旧表不存在 tboldname = null; } @@ -388,7 +397,7 @@ from {pg_}class a inner join {pg_}namespace b on b.oid = a.relnamespace left join {pg_}description d on d.objoid = a.oid and objsubid = 0 where upper(b.nspname) not in ('SYS_CATALOG', 'INFORMATION_SCHEMA', 'TOPOLOGY', 'SYSAUDIT', 'SYSLOGICAL', 'SYS_TEMP_1', 'SYS_TOAST', 'SYS_TOAST_TEMP_1', 'XLOG_RECORD_READ') and a.relkind in ('r') and b.nspname = {{0}} and a.relname = {{1}} -and upper(b.nspname || '.' || a.relname) not in ('PUBLIC.GEOGRAPHY_COLUMNS','PUBLIC.GEOMETRY_COLUMNS','PUBLIC.RASTER_COLUMNS','PUBLIC.RASTER_OVERVIEWS')", tbname[0], tbname[1]))); +and upper(text(b.nspname || '.' || a.relname)) not in ('PUBLIC.GEOGRAPHY_COLUMNS','PUBLIC.GEOMETRY_COLUMNS','PUBLIC.RASTER_COLUMNS','PUBLIC.RASTER_OVERVIEWS')", tbname[0], tbname[1])));//解决报错 function upper(boolean) is not unique 错误的问题 if (dbcomment != (tb.Comment ?? "")) sbalter.Append("COMMENT ON TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" IS ").Append(_commonUtils.FormatSql("{0}", tb.Comment)).Append(";\r\n"); diff --git a/Providers/FreeSql.Provider.KingbaseES/lib/Kdbndp.dll b/Providers/FreeSql.Provider.KingbaseES/lib/Kdbndp.dll index b2884ab9a..a60fab345 100644 Binary files a/Providers/FreeSql.Provider.KingbaseES/lib/Kdbndp.dll and b/Providers/FreeSql.Provider.KingbaseES/lib/Kdbndp.dll differ diff --git a/Providers/FreeSql.Provider.Odbc/PostgreSQL/Curd/OdbcPostgreSQLInsertOrUpdate.cs b/Providers/FreeSql.Provider.Odbc/PostgreSQL/Curd/OdbcPostgreSQLInsertOrUpdate.cs index e0a60d74e..340db0ce1 100644 --- a/Providers/FreeSql.Provider.Odbc/PostgreSQL/Curd/OdbcPostgreSQLInsertOrUpdate.cs +++ b/Providers/FreeSql.Provider.Odbc/PostgreSQL/Curd/OdbcPostgreSQLInsertOrUpdate.cs @@ -56,7 +56,7 @@ namespace FreeSql.Odbc.PostgreSQL if (IdentityColumn != null && flagInsert) sql = insert.ToSql(); else { - var ocdu = new OdbcPostgreSQLOnConflictDoUpdate(insert.InsertIdentity()); + var ocdu = new OdbcPostgreSQLOnConflictDoUpdate(_tempPrimarys?.Length > 0 ? insert : insert.InsertIdentity()); ocdu._tempPrimarys = _tempPrimarys; var cols = _table.Columns.Values.Where(a => _updateSetDict.ContainsKey(a.Attribute.Name) || _tempPrimarys.Contains(a) == false && a.Attribute.CanUpdate == true && a.Attribute.IsIdentity == false && _updateIgnore.ContainsKey(a.Attribute.Name) == false); diff --git a/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLInsertOrUpdate.cs b/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLInsertOrUpdate.cs index da45a72ca..52e6bd4c4 100644 --- a/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLInsertOrUpdate.cs +++ b/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLInsertOrUpdate.cs @@ -58,7 +58,7 @@ namespace FreeSql.PostgreSQL.Curd if (IdentityColumn != null && flagInsert) sql = insert.ToSql(); else { - var ocdu = new OnConflictDoUpdate(insert.InsertIdentity()); + var ocdu = new OnConflictDoUpdate(_tempPrimarys?.Length > 0 ? insert : insert.InsertIdentity()); ocdu._tempPrimarys = _tempPrimarys; var cols = _table.Columns.Values.Where(a => _updateSetDict.ContainsKey(a.Attribute.Name) || _tempPrimarys.Contains(a) == false && a.Attribute.CanUpdate == true && a.Attribute.IsIdentity == false && _updateIgnore.ContainsKey(a.Attribute.Name) == false);