diff --git a/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs b/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs index 5133d1fd..0b82e5c7 100644 --- a/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs +++ b/FreeSql.Tests/MySql/MySqlCodeFirstTest.cs @@ -9,6 +9,25 @@ using Xunit; namespace FreeSql.Tests.MySql { public class MySqlCodeFirstTest { + [Fact] + public void AddUniques() { + var sql = g.mysql.CodeFirst.GetComparisonDDLStatements(); + g.mysql.CodeFirst.SyncStructure(); + } + [Table(Name = "AddUniquesInfo", OldName = "AddUniquesInfo2")] + class AddUniquesInfo { + public Guid id { get; set; } + [Column(Unique = "uk_phone")] + public string phone { get; set; } + + [Column(Unique = "uk_group_index")] + public string group { get; set; } + [Column(Unique = "uk_group_index11")] + public int index { get; set; } + [Column(Unique = "uk_group_index222")] + public string index22 { get; set; } + } + [Fact] public void AddField() { var sql = g.mysql.CodeFirst.GetComparisonDDLStatements(); diff --git a/FreeSql.Tests/Oracle/OracleCodeFirstTest.cs b/FreeSql.Tests/Oracle/OracleCodeFirstTest.cs index c75d472a..4a70294c 100644 --- a/FreeSql.Tests/Oracle/OracleCodeFirstTest.cs +++ b/FreeSql.Tests/Oracle/OracleCodeFirstTest.cs @@ -9,6 +9,24 @@ using Xunit; namespace FreeSql.Tests.Oracle { public class OracleCodeFirstTest { + [Fact] + public void AddUniques() { + var sql = g.oracle.CodeFirst.GetComparisonDDLStatements(); + g.oracle.CodeFirst.SyncStructure(); + } + [Table(Name = "AddUniquesInfo", OldName = "AddUniquesInfo2")] + class AddUniquesInfo { + public Guid id { get; set; } + [Column(Unique = "uk_phone")] + public string phone { get; set; } + + [Column(Unique = "uk_group_index")] + public string group { get; set; } + [Column(Unique = "uk_group_index11")] + public int index { get; set; } + [Column(Unique = "uk_group_index222")] + public string index22 { get; set; } + } [Fact] public void AddField() { var sql = g.oracle.CodeFirst.GetComparisonDDLStatements(); diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLCodeFirstTest.cs b/FreeSql.Tests/PostgreSQL/PostgreSQLCodeFirstTest.cs index b3f8f830..354c0a55 100644 --- a/FreeSql.Tests/PostgreSQL/PostgreSQLCodeFirstTest.cs +++ b/FreeSql.Tests/PostgreSQL/PostgreSQLCodeFirstTest.cs @@ -16,6 +16,25 @@ using Xunit; namespace FreeSql.Tests.PostgreSQL { public class PostgreSQLCodeFirstTest { + [Fact] + public void AddUniques() { + var sql = g.pgsql.CodeFirst.GetComparisonDDLStatements(); + g.pgsql.CodeFirst.SyncStructure(); + } + [Table(Name = "AddUniquesInfo", OldName = "AddUniquesInfo2")] + class AddUniquesInfo { + public Guid id { get; set; } + [Column(Unique = "uk_phone")] + public string phone { get; set; } + + [Column(Unique = "uk_group_index")] + public string group { get; set; } + [Column(Unique = "uk_group_index11")] + public int index { get; set; } + [Column(Unique = "uk_group_index222")] + public string index22 { get; set; } + } + [Fact] public void AddField() { var sql = g.pgsql.CodeFirst.GetComparisonDDLStatements(); diff --git a/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs b/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs index b4a02f0a..382556b2 100644 --- a/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs +++ b/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs @@ -19,6 +19,25 @@ namespace FreeSql.Tests.SqlServer { _sqlserverFixture = sqlserverFixture; } + [Fact] + public void AddUniques() { + var sql = _sqlserverFixture.SqlServer.CodeFirst.GetComparisonDDLStatements(); + _sqlserverFixture.SqlServer.CodeFirst.SyncStructure(); + } + [Table(Name = "AddUniquesInfo", OldName = "AddUniquesInfo2")] + class AddUniquesInfo { + public Guid id { get; set; } + [Column(Unique = "uk_phone")] + public string phone { get; set; } + + [Column(Unique = "uk_group_index")] + public string group { get; set; } + [Column(Unique = "uk_group_index11")] + public int index { get; set; } + [Column(Unique = "uk_group_index222")] + public string index22 { get; set; } + } + [Fact] public void AddField() { var sql = _sqlserverFixture.SqlServer.CodeFirst.GetComparisonDDLStatements(); diff --git a/FreeSql.Tests/SqlServer/SqlServerDbFirstTest.cs b/FreeSql.Tests/SqlServer/SqlServerDbFirstTest.cs index 3cb2de5c..1cac9136 100644 --- a/FreeSql.Tests/SqlServer/SqlServerDbFirstTest.cs +++ b/FreeSql.Tests/SqlServer/SqlServerDbFirstTest.cs @@ -24,7 +24,7 @@ namespace FreeSql.Tests.SqlServer { [Fact] public void GetTablesByDatabase() { - var t2 = _sqlserverFixture.SqlServer.DbFirst.GetTablesByDatabase(_sqlserverFixture.SqlServer.DbFirst.GetDatabases()[0]); + var t2 = _sqlserverFixture.SqlServer.DbFirst.GetTablesByDatabase(); } } diff --git a/FreeSql.Tests/Sqlite/SqliteCodeFirstTest.cs b/FreeSql.Tests/Sqlite/SqliteCodeFirstTest.cs index 010287ae..519ab891 100644 --- a/FreeSql.Tests/Sqlite/SqliteCodeFirstTest.cs +++ b/FreeSql.Tests/Sqlite/SqliteCodeFirstTest.cs @@ -10,6 +10,25 @@ namespace FreeSql.Tests.Sqlite { public class SqliteCodeFirstTest { + [Fact] + public void AddUniques() { + var sql = g.sqlite.CodeFirst.GetComparisonDDLStatements(); + g.sqlite.CodeFirst.SyncStructure(); + } + [Table(Name = "AddUniquesInfo2", OldName = "AddUniquesInfo")] + class AddUniquesInfo { + public Guid id { get; set; } + [Column(Unique = "uk_phone")] + public string phone { get; set; } + + [Column(Unique = "uk_group_index")] + public string group { get; set; } + [Column(Unique = "uk_group_index111")] + public int index { get; set; } + [Column(Unique = "uk_group_index222")] + public string index22 { get; set; } + } + public class Topic { public Guid Id { get; set; } public string Title { get; set; } diff --git a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs index af47d06b..c9b7311e 100644 --- a/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs +++ b/FreeSql/Internal/CommonProvider/AdoProvider/AdoProviderUtils.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Text.RegularExpressions; namespace FreeSql.Internal.CommonProvider { @@ -10,10 +11,12 @@ namespace FreeSql.Internal.CommonProvider { var nparms = new object[parms.Length]; for (int a = 0; a < parms.Length; a++) { if (parms[a] == null) - filter = Regex.Replace(filter, @"\s*(=|IN)\s*\{" + a + @"\}", " IS {" + a + "}", RegexOptions.IgnoreCase); + filter = _dicAddslashesReplaceIsNull.GetOrAdd(a, b => new Regex(@"\s*(=|IN)\s*\{" + b + @"\}", RegexOptions.IgnoreCase | RegexOptions.Compiled)) + .Replace(filter, $" IS {{{a}}}"); nparms[a] = AddslashesProcessParam(parms[a], null); } try { string ret = string.Format(filter, nparms); return ret; } catch { return filter; } } + static ConcurrentDictionary _dicAddslashesReplaceIsNull = new ConcurrentDictionary(); } } diff --git a/FreeSql/Internal/CommonUtils.cs b/FreeSql/Internal/CommonUtils.cs index 6d4e8cdd..fe9ef4f9 100644 --- a/FreeSql/Internal/CommonUtils.cs +++ b/FreeSql/Internal/CommonUtils.cs @@ -101,6 +101,7 @@ namespace FreeSql.Internal { if (trycol._IsNullable != null) attr._IsNullable = trycol.IsNullable; if (trycol._IsIgnore != null) attr._IsIgnore = trycol.IsIgnore; if (trycol._IsVersion != null) attr._IsVersion = trycol.IsVersion; + if (!string.IsNullOrEmpty(trycol.Unique)) attr.Unique = trycol.Unique; if (trycol.MapType != null) attr.MapType = trycol.MapType; if (trycol.DbDefautValue != null) attr.DbDefautValue = trycol.DbDefautValue; } @@ -116,6 +117,7 @@ namespace FreeSql.Internal { if (tryattr._IsNullable != null) attr._IsNullable = tryattr.IsNullable; if (tryattr._IsIgnore != null) attr._IsIgnore = tryattr.IsIgnore; if (tryattr._IsVersion != null) attr._IsVersion = tryattr.IsVersion; + if (!string.IsNullOrEmpty(tryattr.Unique)) attr.Unique = tryattr.Unique; if (tryattr.MapType != null) attr.MapType = tryattr.MapType; if (tryattr.DbDefautValue != null) attr.DbDefautValue = tryattr.DbDefautValue; } @@ -128,6 +130,7 @@ namespace FreeSql.Internal { if (attr._IsNullable != null) ret = attr; if (attr._IsIgnore != null) ret = attr; if (attr._IsVersion != null) ret = attr; + if (!string.IsNullOrEmpty(attr.Unique)) ret = attr; if (attr.MapType != null) ret = attr; if (attr.DbDefautValue != null) ret = attr; if (ret != null && ret.MapType == null) ret.MapType = proto.PropertyType; diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs index 227bc3fd..31660038 100644 --- a/FreeSql/Internal/UtilsExpressionTree.cs +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -18,7 +18,11 @@ namespace FreeSql.Internal { static ConcurrentDictionary> _cacheGetTableByEntity = new ConcurrentDictionary>(); internal static void RemoveTableByEntity(Type entity, CommonUtils common) { - if (entity.FullName.StartsWith("<>f__AnonymousType")) return; + if (entity.FullName.StartsWith("<>f__AnonymousType") || + entity.IsValueType || + entity.IsNullableType() || + entity.NullableTypeOrThis() == typeof(BigInteger) + ) return; var tbc = _cacheGetTableByEntity.GetOrAdd(common._orm.Ado.DataType, k1 => new ConcurrentDictionary()); //区分数据库类型缓存 if (tbc.TryRemove(entity, out var trytb) && trytb?.TypeLazy != null) tbc.TryRemove(trytb.TypeLazy, out var trylz); } @@ -75,6 +79,7 @@ namespace FreeSql.Internal { IsNullable = tp.Value.isnullable ?? true, IsPrimary = false, IsIgnore = false, + Unique = null, MapType = p.PropertyType }; if (colattr._IsNullable == null) colattr._IsNullable = tp?.isnullable; @@ -84,8 +89,14 @@ namespace FreeSql.Internal { if (tp != null && tp.Value.isnullable == null) colattr.IsNullable = tp.Value.dbtypeFull.Contains("NOT NULL") == false; if (colattr.DbType?.Contains("NOT NULL") == true) colattr.IsNullable = false; if (string.IsNullOrEmpty(colattr.Name)) colattr.Name = p.Name; - if (common.CodeFirst.IsSyncStructureToLower) colattr.Name = colattr.Name.ToLower(); - if (common.CodeFirst.IsSyncStructureToUpper) colattr.Name = colattr.Name.ToUpper(); + if (common.CodeFirst.IsSyncStructureToLower) { + colattr.Name = colattr.Name.ToLower(); + if (!string.IsNullOrEmpty(colattr.Unique)) colattr.Unique = colattr.Unique.ToLower(); + } + if (common.CodeFirst.IsSyncStructureToUpper) { + colattr.Name = colattr.Name.ToUpper(); + if (!string.IsNullOrEmpty(colattr.Unique)) colattr.Unique = colattr.Unique.ToUpper(); + } if ((colattr.IsNullable != true || colattr.IsIdentity == true || colattr.IsPrimary == true) && colattr.DbType.Contains("NOT NULL") == false) { colattr.IsNullable = false; @@ -177,8 +188,8 @@ namespace FreeSql.Internal { } catch { } trytb.Primarys = trytb.Columns.Values.Where(a => a.Attribute.IsPrimary == true).ToArray(); } - trytb.Uniques = trytb.Columns.Values.Where(a => !string.IsNullOrEmpty(a.Attribute.Unique)) - .ToDictionary(a => a.Attribute.Unique, a => trytb.Columns.Values.Where(b => b.Attribute.Unique == a.Attribute.Unique).ToList()); + trytb.Uniques = trytb.Columns.Values.Where(a => !string.IsNullOrEmpty(a.Attribute.Unique)).Select(a => a.Attribute.Unique).Distinct() + .ToDictionary(a => a, a => trytb.Columns.Values.Where(b => b.Attribute.Unique == a).ToList()); tbc.AddOrUpdate(entity, trytb, (oldkey, oldval) => trytb); #region 查找导航属性的关系、virtual 属性延时加载,动态产生新的重写类 diff --git a/FreeSql/MySql/MySqlCodeFirst.cs b/FreeSql/MySql/MySqlCodeFirst.cs index 3f908777..25b10793 100644 --- a/FreeSql/MySql/MySqlCodeFirst.cs +++ b/FreeSql/MySql/MySqlCodeFirst.cs @@ -133,13 +133,17 @@ namespace FreeSql.MySql { if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); sb.Append(","); } - if (tb.Primarys.Any() == false) - sb.Remove(sb.Length - 1, 1); - else { + if (tb.Primarys.Any()) { sb.Append(" \r\n PRIMARY KEY ("); foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append(")"); + sb.Remove(sb.Length - 2, 2).Append("),"); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n UNIQUE KEY ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append("("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) Engine=InnoDB CHARACTER SET utf8;\r\n"); continue; } @@ -176,7 +180,7 @@ where a.table_schema in ({0}) and a.table_name in ({1})".FormatMySql(tboldname ? }, StringComparer.CurrentCultureIgnoreCase); if (istmpatler == false) { - var existsPrimary = ExecuteScalar(tbname[0], "select 1 from INFORMATION_SCHEMA.KEY_COLUMN_USAGE where table_schema={0} and table_name={1} limit 1".FormatMySql(tbname)); + var existsPrimary = ExecuteScalar(tbname[0], "select 1 from information_schema.key_column_usage where table_schema={0} and table_name={1} and constraint_name = 'PRIMARY' limit 1".FormatMySql(tbname)); foreach (var tbcol in tb.Columns.Values) { var isIdentityChanged = tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1; if (tbstruct.TryGetValue(tbcol.Attribute.Name, out var tbstructcol) || @@ -202,6 +206,23 @@ where a.table_schema in ({0}) and a.table_name in ({1})".FormatMySql(tboldname ? if (isIdentityChanged) sbalter.Append(" AUTO_INCREMENT").Append(existsPrimary == null ? "" : ", DROP PRIMARY KEY").Append(", ADD PRIMARY KEY(").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(")"); sbalter.Append(";\r\n"); } + var dsuksql = @" +select +a.column_name, +a.constraint_name 'index_id' +from information_schema.key_column_usage a +where a.constraint_schema IN ({0}) and a.table_name IN ({1})".FormatMySql(tboldname ?? tbname); + var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]) }); + foreach (var uk in tb.Uniques) { + if (uk.Key == "PRIMARY" || string.IsNullOrEmpty(uk.Key) || uk.Value.Any() == false) continue; + var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Key, true) == 0).ToArray(); + if (dsukfind1.Any() == false || dsukfind1.Length != uk.Value.Count || dsukfind1.Where(a => uk.Value.Where(b => string.Compare(b.Attribute.Name, a[0], true) == 0).Any()).Count() != uk.Value.Count) { + if (dsukfind1.Any()) sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" DROP INDEX ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(";\r\n"); + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sbalter.Remove(sbalter.Length - 2, 2).Append(");\r\n"); + } + } } if (istmpatler == false) { sb.Append(sbalter); @@ -219,13 +240,17 @@ where a.table_schema in ({0}) and a.table_name in ({1})".FormatMySql(tboldname ? if (tbcol.Attribute.IsIdentity == true && tbcol.Attribute.DbType.IndexOf("AUTO_INCREMENT", StringComparison.CurrentCultureIgnoreCase) == -1) sb.Append(" AUTO_INCREMENT"); sb.Append(","); } - if (tb.Primarys.Any() == false) - sb.Remove(sb.Length - 1, 1); - else { + if (tb.Primarys.Any()) { sb.Append(" \r\n PRIMARY KEY ("); foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append(")"); + sb.Remove(sb.Length - 2, 2).Append("),"); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n UNIQUE KEY ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append("("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) Engine=InnoDB CHARACTER SET utf8;\r\n"); sb.Append("INSERT INTO ").Append(tmptablename).Append(" ("); foreach (var tbcol in tb.Columns.Values) diff --git a/FreeSql/MySql/MySqlDbFirst.cs b/FreeSql/MySql/MySqlDbFirst.cs index 78be1d8a..eda5d69e 100644 --- a/FreeSql/MySql/MySqlDbFirst.cs +++ b/FreeSql/MySql/MySqlDbFirst.cs @@ -274,7 +274,7 @@ where a.constraint_schema in ({1}) and a.table_name in ({0}) and isnull(position if (!loc10.TryGetValue(index_id, out loc11)) loc10.Add(index_id, loc11 = new List()); loc11.Add(loc9); - if (is_unique) { + if (is_unique && !is_primary_key) { if (!uniqueColumns.TryGetValue(table_id, out loc10)) uniqueColumns.Add(table_id, loc10 = new Dictionary>()); if (!loc10.TryGetValue(index_id, out loc11)) diff --git a/FreeSql/Oracle/OracleCodeFirst.cs b/FreeSql/Oracle/OracleCodeFirst.cs index 7c310397..78087953 100644 --- a/FreeSql/Oracle/OracleCodeFirst.cs +++ b/FreeSql/Oracle/OracleCodeFirst.cs @@ -112,13 +112,17 @@ namespace FreeSql.Oracle { sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType).Append(","); if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, true)); } - if (tb.Primarys.Any() == false) - sb.Remove(sb.Length - 1, 1); - else { + if (tb.Primarys.Any()) { sb.Append(" \r\n CONSTRAINT ").Append(tbname[0]).Append("_").Append(tbname[1]).Append("_pk1 PRIMARY KEY ("); foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append(")"); + sb.Remove(sb.Length - 2, 2).Append("),"); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE ("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) \r\nLOGGING \r\nNOCOMPRESS \r\nNOCACHE\r\n';\r\n"); continue; } @@ -185,6 +189,30 @@ where owner={{0}} and table_name={{1}}".FormatOracleSQL(tboldname ?? tbname); } if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, tbcol.Attribute.IsIdentity == true)); } + var dsuksql = @" +select +c.column_name, +c.constraint_name +from +all_constraints a, +all_cons_columns c +where +a.constraint_name = c.constraint_name +and a.owner = c.owner +and a.table_name = c.table_name +and a.constraint_type in ('U') +and a.owner in ({0}) and a.table_name in ({1})".FormatMySql(tboldname ?? tbname); + var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]) }); + foreach (var uk in tb.Uniques) { + if (string.IsNullOrEmpty(uk.Key) || uk.Value.Any() == false) continue; + var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Key, true) == 0).ToArray(); + if (dsukfind1.Any() == false || dsukfind1.Length != uk.Value.Count || dsukfind1.Where(a => uk.Value.Where(b => string.Compare(b.Attribute.Name, a[0], true) == 0).Any()).Count() != uk.Value.Count) { + if (dsukfind1.Any()) sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" DROP CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append("';\r\n"); + sbalter.Append("execute immediate 'ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sbalter.Remove(sbalter.Length - 2, 2).Append(")';\r\n"); + } + } } if (istmpatler == false) { sb.Append(sbalter); @@ -203,13 +231,17 @@ where owner={{0}} and table_name={{1}}".FormatOracleSQL(tboldname ?? tbname); sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType).Append(","); if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, true)); } - if (tb.Primarys.Any() == false) - sb.Remove(sb.Length - 1, 1); - else { + if (tb.Primarys.Any()) { sb.Append(" \r\n CONSTRAINT ").Append(tbname[0]).Append("_").Append(tbname[1]).Append("_pk2 PRIMARY KEY ("); foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append(")"); + sb.Remove(sb.Length - 2, 2).Append("),"); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE ("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) LOGGING \r\nNOCOMPRESS \r\nNOCACHE\r\n';\r\n"); sb.Append("execute immediate 'INSERT INTO ").Append(tmptablename).Append(" ("); foreach (var tbcol in tb.Columns.Values) diff --git a/FreeSql/Oracle/OracleDbFirst.cs b/FreeSql/Oracle/OracleDbFirst.cs index 41b4ed02..de680be1 100644 --- a/FreeSql/Oracle/OracleDbFirst.cs +++ b/FreeSql/Oracle/OracleDbFirst.cs @@ -293,7 +293,7 @@ and a.owner in ({1}) and a.table_name in ({0}) if (!loc10.TryGetValue(index_id, out loc11)) loc10.Add(index_id, loc11 = new List()); loc11.Add(loc9); - if (is_unique) { + if (is_unique && !is_primary_key) { if (!uniqueColumns.TryGetValue(table_id, out loc10)) uniqueColumns.Add(table_id, loc10 = new Dictionary>()); if (!loc10.TryGetValue(index_id, out loc11)) diff --git a/FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs b/FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs index a55da81c..5ea16289 100644 --- a/FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs +++ b/FreeSql/PostgreSQL/PostgreSQLCodeFirst.cs @@ -155,13 +155,17 @@ namespace FreeSql.PostgreSQL { sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType).Append(","); if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, true)); } - if (tb.Primarys.Any() == false) - sb.Remove(sb.Length - 1, 1); - else { + if (tb.Primarys.Any()) { sb.Append(" \r\n CONSTRAINT ").Append(tbname[0]).Append("_").Append(tbname[1]).Append("_pkey PRIMARY KEY ("); foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append(")"); + sb.Remove(sb.Length - 2, 2).Append("),"); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) WITH (OIDS=FALSE);\r\n"); continue; } @@ -241,6 +245,27 @@ where ns.nspname = {0} and c.relname = {1}".FormatPostgreSQL(tboldname ?? tbname if (tbcol.Attribute.IsNullable == false) sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ALTER COLUMN ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" SET NOT NULL;\r\n"); if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, tbcol.Attribute.IsIdentity == true)); } + var dsuksql = @" +select +c.attname, +b.relname +from pg_index a +inner join pg_class b on b.oid = a.indexrelid +inner join pg_attribute c on c.attnum > 0 and c.attrelid = b.oid +inner join pg_namespace ns on ns.oid = b.relnamespace +inner join pg_class d on d.oid = a.indrelid +where ns.nspname in ({0}) and d.relname in ({1}) and a.indisunique = 't'".FormatMySql(tboldname ?? tbname); + var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]) }); + foreach (var uk in tb.Uniques) { + if (string.IsNullOrEmpty(uk.Key) || uk.Value.Any() == false) continue; + var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Key, true) == 0).ToArray(); + if (dsukfind1.Any() == false || dsukfind1.Length != uk.Value.Count || dsukfind1.Where(a => uk.Value.Where(b => string.Compare(b.Attribute.Name, a[0], true) == 0).Any()).Count() != uk.Value.Count) { + if (dsukfind1.Any()) sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" DROP CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(";\r\n"); + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}")).Append(" ADD CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sbalter.Remove(sbalter.Length - 2, 2).Append(");\r\n"); + } + } } if (istmpatler == false) { sb.Append(sbalter); @@ -263,13 +288,17 @@ where pg_namespace.nspname={0} and pg_class.relname={1} and pg_constraint.contyp sb.Append(" \r\n ").Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(" ").Append(tbcol.Attribute.DbType).Append(","); if (tbcol.Attribute.IsIdentity == true) seqcols.Add((tbcol, tbname, true)); } - if (tb.Primarys.Any() == false) - sb.Remove(sb.Length - 1, 1); - else { + if (tb.Primarys.Any()) { sb.Append(" \r\n CONSTRAINT ").Append(tbname[0]).Append("_").Append(tbname[1]).Append("_pkey PRIMARY KEY ("); foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append(")"); + sb.Remove(sb.Length - 2, 2).Append("),"); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) WITH (OIDS=FALSE);\r\n"); sb.Append("INSERT INTO ").Append(tmptablename).Append(" ("); foreach (var tbcol in tb.Columns.Values) diff --git a/FreeSql/PostgreSQL/PostgreSQLDbFirst.cs b/FreeSql/PostgreSQL/PostgreSQLDbFirst.cs index 4b4a2930..a1608f16 100644 --- a/FreeSql/PostgreSQL/PostgreSQLDbFirst.cs +++ b/FreeSql/PostgreSQL/PostgreSQLDbFirst.cs @@ -390,7 +390,7 @@ where ns.nspname || '.' || d.relname in ({loc8}) if (!loc10.TryGetValue(index_id, out loc11)) loc10.Add(index_id, loc11 = new List()); loc11.Add(loc9); - if (is_unique) { + if (is_unique && !is_primary_key) { if (!uniqueColumns.TryGetValue(object_id, out loc10)) uniqueColumns.Add(object_id, loc10 = new Dictionary>()); if (!loc10.TryGetValue(index_id, out loc11)) diff --git a/FreeSql/SqlServer/SqlServerCodeFirst.cs b/FreeSql/SqlServer/SqlServerCodeFirst.cs index e6f62572..3dad8044 100644 --- a/FreeSql/SqlServer/SqlServerCodeFirst.cs +++ b/FreeSql/SqlServer/SqlServerCodeFirst.cs @@ -139,13 +139,18 @@ namespace FreeSql.SqlServer { } sb.Append(","); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } sb.Remove(sb.Length - 1, 1).Append("\r\n);\r\n"); continue; } //如果新表,旧表在一个数据库和模式下,直接修改表名 if (string.Compare(tbname[0], tboldname[0], true) == 0 && string.Compare(tbname[1], tboldname[1], true) == 0) - sbalter.Append("use ").Append(_commonUtils.QuoteSqlName(tbname[0])).Append(_commonUtils.FormatSql(";\r\nEXEC sp_rename {0}, {1};\r\n", _commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}"), _commonUtils.QuoteSqlName(tbname[2]))); + sbalter.Append("use ").Append(_commonUtils.QuoteSqlName(tbname[0])).Append(_commonUtils.FormatSql(";\r\nEXEC sp_rename {0}, {1};\r\n", _commonUtils.QuoteSqlName($"{tboldname[0]}.{tboldname[1]}.{tboldname[2]}"), tbname[2])); else { //如果新表,旧表不在一起,创建新表,导入数据,删除旧表 istmpatler = true; @@ -207,9 +212,31 @@ use " + database, tboldname ?? tbname); } sbalter.Append(";\r\n"); } + var dsuksql = string.Format(@" +use [{0}]; +select +c.name +,d.name +from sys.index_columns a +inner join sys.indexes b on b.object_id = a.object_id and b.index_id = a.index_id +left join sys.columns c on c.object_id = a.object_id and c.column_id = a.column_id +left join sys.key_constraints d on d.parent_object_id = b.object_id and d.unique_index_id = b.index_id +where a.object_id in (object_id(N'[{1}].[{2}]')) and b.is_unique = 1; +use " + database, tboldname ?? tbname); + var dsuk = _orm.Ado.ExecuteArray(CommandType.Text, dsuksql).Select(a => new[] { string.Concat(a[0]), string.Concat(a[1]) }); + foreach (var uk in tb.Uniques) { + if (string.IsNullOrEmpty(uk.Key) || uk.Value.Any() == false) continue; + var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Key, true) == 0).ToArray(); + if (dsukfind1.Any() == false || dsukfind1.Length != uk.Value.Count || dsukfind1.Where(a => uk.Value.Where(b => string.Compare(b.Attribute.Name, a[0], true) == 0).Any()).Count() != uk.Value.Count) { + if (dsukfind1.Any()) sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbname[2]}")).Append(" DROP CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(";\r\n"); + sbalter.Append("ALTER TABLE ").Append(_commonUtils.QuoteSqlName($"{tbname[0]}.{tbname[1]}.{tbname[2]}")).Append(" ADD CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sbalter.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sbalter.Remove(sbalter.Length - 2, 2).Append(");\r\n"); + } + } } if (istmpatler == false) { - sb.Append(sbalter); + sb.Append(sbalter).Append("\r\nuse " + database); continue; } //创建临时表,数据导进临时表,然后删除原表,将临时表改名为原表名 @@ -244,6 +271,11 @@ use " + database, tboldname ?? tbname); sb.Append(","); idents = idents || tbcol.Attribute.IsIdentity == true; } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } sb.Remove(sb.Length - 1, 1).Append("\r\n);\r\n"); sb.Append("ALTER TABLE ").Append(tmptablename).Append(" SET (LOCK_ESCALATION = TABLE);\r\n"); if (idents) sb.Append("SET IDENTITY_INSERT ").Append(tmptablename).Append(" ON;\r\n"); @@ -268,7 +300,7 @@ use " + database, tboldname ?? tbname); sb.Remove(sb.Length - 2, 2).Append(" FROM ").Append(tablename).Append(" WITH (HOLDLOCK TABLOCKX)');\r\n"); if (idents) sb.Append("SET IDENTITY_INSERT ").Append(tmptablename).Append(" OFF;\r\n"); sb.Append("DROP TABLE ").Append(tablename).Append(";\r\n"); - sb.Append("EXECUTE sp_rename N'").Append(tmptablename).Append("', N'").Append(tbname[2]).Append("', 'OBJECT' ;\r\n"); + sb.Append("EXECUTE sp_rename N'").Append(tmptablename).Append("', N'").Append(tbname[2]).Append("', 'OBJECT';\r\n"); sb.Append("COMMIT;\r\n"); } return sb.Length == 0 ? null : sb.ToString(); diff --git a/FreeSql/SqlServer/SqlServerDbFirst.cs b/FreeSql/SqlServer/SqlServerDbFirst.cs index 24a721c2..56405a7e 100644 --- a/FreeSql/SqlServer/SqlServerDbFirst.cs +++ b/FreeSql/SqlServer/SqlServerDbFirst.cs @@ -294,7 +294,7 @@ use [{olddatabase}]; if (!loc10.TryGetValue(index_id, out loc11)) loc10.Add(index_id, loc11 = new List()); loc11.Add(loc9); - if (is_unique) { + if (is_unique && !is_primary_key) { if (!uniqueColumns.TryGetValue(object_id, out loc10)) uniqueColumns.Add(object_id, loc10 = new Dictionary>()); if (!loc10.TryGetValue(index_id, out loc11)) diff --git a/FreeSql/Sqlite/SqliteCodeFirst.cs b/FreeSql/Sqlite/SqliteCodeFirst.cs index 69aedae9..4a28f132 100644 --- a/FreeSql/Sqlite/SqliteCodeFirst.cs +++ b/FreeSql/Sqlite/SqliteCodeFirst.cs @@ -110,13 +110,17 @@ namespace FreeSql.Sqlite { } sb.Append(","); } - if (isIndent || tb.Primarys.Any() == false) - sb.Remove(sb.Length - 1, 1); - else { + if (isIndent == false && tb.Primarys.Any()) { sb.Append(" \r\n PRIMARY KEY ("); foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); - sb.Remove(sb.Length - 2, 2).Append(")"); + sb.Remove(sb.Length - 2, 2).Append("),"); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) \r\n;\r\n"); continue; } @@ -168,6 +172,22 @@ namespace FreeSql.Sqlite { //添加列 istmpatler = true; } + var dsukMatches = _regexUK.Matches(dsql); + var dsuk = new List(); + foreach (Match dsukm in dsukMatches) { + var dbsukmg2 = dsukm.Groups[2].Value.Split(','); + if (dbsukmg2.Any() == false) continue; + foreach (var dbfield in dbsukmg2) { + dsuk.Add(new[] { Regex.Match(dbfield, @"""([^""]+)""").Groups[1].Value, dsukm.Groups[1].Value }); + } + } + foreach (var uk in tb.Uniques) { + if (string.IsNullOrEmpty(uk.Key) || uk.Value.Any() == false) continue; + var dsukfind1 = dsuk.Where(a => string.Compare(a[1], uk.Key, true) == 0).ToArray(); + if (dsukfind1.Any() == false || dsukfind1.Length != uk.Value.Count || dsukfind1.Where(a => uk.Value.Where(b => string.Compare(b.Attribute.Name, a[0], true) == 0).Any()).Count() != uk.Value.Count) { + istmpatler = true; + } + } } if (istmpatler == false) { sb.Append(sbalter); @@ -190,13 +210,17 @@ namespace FreeSql.Sqlite { } sb.Append(","); } - if (isIndent || tb.Primarys.Any() == false) - sb.Remove(sb.Length - 1, 1); - else { + if (isIndent == false && tb.Primarys.Any()) { sb.Append(" \r\n PRIMARY KEY ("); foreach (var tbcol in tb.Primarys) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); sb.Remove(sb.Length - 2, 2).Append(")"); } + foreach (var uk in tb.Uniques) { + sb.Append(" \r\n CONSTRAINT ").Append(_commonUtils.QuoteSqlName(uk.Key)).Append(" UNIQUE("); + foreach (var tbcol in uk.Value) sb.Append(_commonUtils.QuoteSqlName(tbcol.Attribute.Name)).Append(", "); + sb.Remove(sb.Length - 2, 2).Append("),"); + } + sb.Remove(sb.Length - 1, 1); sb.Append("\r\n) \r\n;\r\n"); sb.Append("INSERT INTO ").Append(tmptablename).Append(" ("); foreach (var tbcol in tb.Columns.Values) @@ -223,6 +247,7 @@ namespace FreeSql.Sqlite { } return sb.Length == 0 ? null : sb.ToString(); } + static Regex _regexUK = new Regex(@"CONSTRAINT\s*""([^""]+)""\s*UNIQUE\s*\(([^\)]+)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled); static object syncStructureLock = new object(); ConcurrentDictionary dicSyced = new ConcurrentDictionary();