From 84cfa65281d8edd9d462b2502bf1f9995dc14c4d Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Mon, 4 Jul 2022 17:15:30 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20IsVersion=20string=20?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E4=B9=90=E8=A7=82=E9=94=81=EF=BC=9B?= =?UTF-8?q?#1178?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Examples/base_entity/Program.cs | 6 +- .../SqlServer/SqlServerCodeFirstTest.cs | 125 ++++++++++++++++++ FreeSql.Tests/FreeSql.Tests/UnitTest1.cs | 3 +- FreeSql/FreeSql.xml | 2 +- .../Internal/CommonProvider/InsertProvider.cs | 16 ++- .../Internal/CommonProvider/UpdateProvider.cs | 11 +- FreeSql/Internal/UtilsExpressionTree.cs | 4 +- FreeSql/Properties/CoreStrings.Designer.cs | 2 +- FreeSql/Properties/CoreStrings.resx | 2 +- FreeSql/Properties/CoreStrings.zh-Hans.resx | 2 +- .../Curd/ClickHouseUpdate.cs | 8 ++ 11 files changed, 169 insertions(+), 12 deletions(-) diff --git a/Examples/base_entity/Program.cs b/Examples/base_entity/Program.cs index c88e7856..9de227d2 100644 --- a/Examples/base_entity/Program.cs +++ b/Examples/base_entity/Program.cs @@ -311,9 +311,9 @@ namespace base_entity //.UseConnectionString(FreeSql.DataType.Firebird, @"database=localhost:D:\fbdata\EXAMPLES.fdb;user=sysdba;password=123456;max pool size=5") - .UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=2") + //.UseConnectionString(FreeSql.DataType.MySql, "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=2") - .UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=3;TrustServerCertificate=true") + //.UseConnectionString(FreeSql.DataType.SqlServer, "Data Source=.;Integrated Security=True;Initial Catalog=freesqlTest;Pooling=true;Max Pool Size=3;TrustServerCertificate=true") //.UseConnectionString(FreeSql.DataType.PostgreSQL, "Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=tedb;Pooling=true;Maximum Pool Size=2") //.UseConnectionString(FreeSql.DataType.PostgreSQL, "Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=toc;Pooling=true;Maximum Pool Size=2") @@ -344,6 +344,8 @@ namespace base_entity BaseEntity.Initialization(fsql, () => _asyncUow.Value); #endregion + var sqlToYear = fsql.Select().ToSql(a => a.CreateTime.Year); + TestExp(fsql); fsql.CodeFirst.GetTableByEntity(typeof(TestComment01)); diff --git a/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs index 63df89b7..6e4685ad 100644 --- a/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/SqlServer/SqlServerCodeFirstTest.cs @@ -12,6 +12,131 @@ namespace FreeSql.Tests.SqlServer { public class SqlServerCodeFirstTest { + [Fact] + public void VersionInt() + { + var fsql = g.sqlserver; + fsql.Delete().Where("1=1").ExecuteAffrows(); + var item = new VersionInt01 { name = "name01" }; + fsql.Insert(item).ExecuteAffrows(); + + item = fsql.Select().Where(a => a.id == item.id).First(); + Assert.NotNull(item); + Assert.Equal(0, item.version); + + item.name = "name02"; + Assert.Equal($@"UPDATE [VersionInt01] SET [name] = @p_0, [version] = isnull([version], 0) + 1 +WHERE ([id] = '{item.id}') AND [version] = 0", fsql.Update().SetSource(item).ToSql()); + Assert.Equal($@"UPDATE [VersionInt01] SET [name] = N'name02', [version] = isnull([version], 0) + 1 +WHERE ([id] = '{item.id}') AND [version] = 0", fsql.Update().SetSource(item).NoneParameter().ToSql()); + Assert.Equal(1, fsql.Update().SetSource(item).ExecuteAffrows()); + item = fsql.Select().Where(a => a.id == item.id).First(); + Assert.NotNull(item); + Assert.Equal("name02", item.name); + Assert.Equal(1, item.version); + + item.name = "name03"; + Assert.Equal($@"UPDATE [VersionInt01] SET [name] = @p_0, [version] = isnull([version], 0) + 1 +WHERE ([id] = '{item.id}') AND [version] = 1", fsql.Update().SetSource(item).ToSql()); + Assert.Equal($@"UPDATE [VersionInt01] SET [name] = N'name03', [version] = isnull([version], 0) + 1 +WHERE ([id] = '{item.id}') AND [version] = 1", fsql.Update().SetSource(item).NoneParameter().ToSql()); + Assert.Equal(1, fsql.Update().SetSource(item).ExecuteAffrows()); + item = fsql.Select().Where(a => a.id == item.id).First(); + Assert.NotNull(item); + Assert.Equal("name03", item.name); + Assert.Equal(2, item.version); + + Assert.Equal($@"UPDATE [VersionInt01] SET [name] = @p_0, [version] = isnull([version], 0) + 1 +WHERE ([id] = '{item.id}')", fsql.Update().Set(a => a.name, "name04").Where(a => a.id == item.id).ToSql()); + Assert.Equal($@"UPDATE [VersionInt01] SET [name] = N'name04', [version] = isnull([version], 0) + 1 +WHERE ([id] = '{item.id}')", fsql.Update().NoneParameter().Set(a => a.name, "name04").Where(a => a.id == item.id).ToSql()); + Assert.Equal(1, fsql.Update().Set(a => a.name, "name04").Where(a => a.id == item.id).ExecuteAffrows()); + item = fsql.Select().Where(a => a.id == item.id).First(); + Assert.NotNull(item); + Assert.Equal("name04", item.name); + Assert.Equal(3, item.version); + } + class VersionInt01 + { + public Guid id { get; set; } + public string name { get; set; } + [Column(IsVersion = true)] + public int version { get; set; } + } + + [Fact] + public void VersionBytes() + { + bool LocalEqualsVersion(byte[] v1, byte[] v2) + { + if (v1.Length == v2.Length) + { + for (var y = 0; y < v2.Length; y++) + if (v1[y] != v2[y]) return false; + return true; + } + return false; + } + + var fsql = g.sqlserver; + fsql.Delete().Where("1=1").ExecuteAffrows(); + var item = new VersionBytes01 { name = "name01" }; + fsql.Insert(item).ExecuteAffrows(); + var itemVersion = item.version; + Assert.NotNull(itemVersion); + + item = fsql.Select().Where(a => a.id == item.id).First(); + Assert.NotNull(item); + Assert.True(LocalEqualsVersion(itemVersion, item.version)); + + item.name = "name02"; + var sql = fsql.Update().SetSource(item).ToSql(); + Assert.Equal(1, fsql.Update().SetSource(item).ExecuteAffrows()); + + item.name = "name03"; + Assert.Equal(1, fsql.Update().SetSource(item).ExecuteAffrows()); + + Assert.Equal(1, fsql.Update().Set(a => a.name, "name04").Where(a => a.id == item.id).ExecuteAffrows()); + } + class VersionBytes01 + { + public Guid id { get; set; } + public string name { get; set; } + [Column(IsVersion = true)] + public byte[] version { get; set; } + } + + [Fact] + public void VersionString() + { + var fsql = g.sqlserver; + fsql.Delete().Where("1=1").ExecuteAffrows(); + var item = new VersionString01 { name = "name01" }; + fsql.Insert(item).ExecuteAffrows(); + var itemVersion = item.version; + Assert.NotNull(itemVersion); + + item = fsql.Select().Where(a => a.id == item.id).First(); + Assert.NotNull(item); + Assert.Equal(itemVersion, item.version); + + item.name = "name02"; + var sql = fsql.Update().SetSource(item).ToSql(); + Assert.Equal(1, fsql.Update().SetSource(item).ExecuteAffrows()); + + item.name = "name03"; + Assert.Equal(1, fsql.Update().SetSource(item).ExecuteAffrows()); + + Assert.Equal(1, fsql.Update().Set(a => a.name, "name04").Where(a => a.id == item.id).ExecuteAffrows()); + } + class VersionString01 + { + public Guid id { get; set; } + public string name { get; set; } + [Column(IsVersion = true)] + public string version { get; set; } + } + [Fact] public void Test_0String() { diff --git a/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs b/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs index a70df98d..2f1ca4ce 100644 --- a/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs +++ b/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs @@ -734,7 +734,8 @@ namespace FreeSql.Tests .ToList(a => new { a.Key, - sss = a.Sum(a.Value.Item1.OptionsEntity04) + sss = a.Sum(a.Value.Item1.OptionsEntity04), + xxx = SqlExt.DistinctCount(a.Value.Item2.Title) }); diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 732dbb40..2a6a5fd6 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -5236,7 +5236,7 @@ - 属性{trytbVersionColumnCsName} 被标注为行锁(乐观锁)(IsVersion),但其必须为数字类型 或者 byte[],并且不可为 Nullable + 属性{trytbVersionColumnCsName} 被标注为行锁(乐观锁)(IsVersion),但其必须为数字类型 或者 byte[] 或者 string,并且不可为 Nullable diff --git a/FreeSql/Internal/CommonProvider/InsertProvider.cs b/FreeSql/Internal/CommonProvider/InsertProvider.cs index a2b932e3..f572b8b7 100644 --- a/FreeSql/Internal/CommonProvider/InsertProvider.cs +++ b/FreeSql/Internal/CommonProvider/InsertProvider.cs @@ -188,10 +188,22 @@ namespace FreeSql.Internal.CommonProvider col.SetValue(data, val = FreeUtil.NewMongodbId()); } } + if (col.Attribute.IsVersion) + { + if (col.Attribute.MapType == typeof(byte[])) + { + if (val == null || (val is byte[] bytes && bytes.Length == 0)) + col.SetValue(data, val = Utils.GuidToBytes(Guid.NewGuid())); + } + else if (col.Attribute.MapType == typeof(string)) + { + var verval = col.GetDbValue(data) as string; + if (string.IsNullOrWhiteSpace(verval)) + col.SetValue(data, val = Guid.NewGuid().ToString()); + } + } if (val == null && col.Attribute.MapType == typeof(string) && col.Attribute.IsNullable == false) col.SetValue(data, val = ""); - if (col.Attribute.MapType == typeof(byte[]) && (val == null || (val is byte[] bytes && bytes.Length == 0)) && col.Attribute.IsVersion) - col.SetValue(data, val = Utils.GuidToBytes(Guid.NewGuid())); } } diff --git a/FreeSql/Internal/CommonProvider/UpdateProvider.cs b/FreeSql/Internal/CommonProvider/UpdateProvider.cs index d130b9e1..0d825c45 100644 --- a/FreeSql/Internal/CommonProvider/UpdateProvider.cs +++ b/FreeSql/Internal/CommonProvider/UpdateProvider.cs @@ -37,7 +37,7 @@ namespace FreeSql.Internal.CommonProvider public DbConnection _connection; public int _commandTimeout = 0; public Action _interceptSql; - public byte[] _updateVersionValue; + public object _updateVersionValue; } public abstract partial class UpdateProvider : UpdateProvider, IUpdate @@ -139,7 +139,9 @@ namespace FreeSql.Internal.CommonProvider throw new DbUpdateVersionException(CoreStrings.DbUpdateVersionException_RowLevelOptimisticLock(_source.Count, affrows), _table, sql, dbParms, affrows, _source.Select(a => (object)a)); foreach (var d in _source) { - if (_versionColumn.Attribute.MapType == typeof(byte[])) + if (_versionColumn.Attribute.MapType == typeof(byte[])) + _orm.SetEntityValueWithPropertyName(_table.Type, d, _versionColumn.CsName, _updateVersionValue); + else if (_versionColumn.Attribute.MapType == typeof(string)) _orm.SetEntityValueWithPropertyName(_table.Type, d, _versionColumn.CsName, _updateVersionValue); else _orm.SetEntityIncrByWithPropertyName(_table.Type, d, _versionColumn.CsName, 1); @@ -1005,6 +1007,11 @@ namespace FreeSql.Internal.CommonProvider _updateVersionValue = Utils.GuidToBytes(Guid.NewGuid()); sb.Append(", ").Append(vcname).Append(" = ").Append(_commonUtils.GetNoneParamaterSqlValue(_paramsSource, "uv", _versionColumn, _versionColumn.Attribute.MapType, _updateVersionValue)); } + else if (_versionColumn.Attribute.MapType == typeof(string)) + { + _updateVersionValue = Guid.NewGuid().ToString(); + sb.Append(", ").Append(vcname).Append(" = ").Append(_commonUtils.GetNoneParamaterSqlValue(_paramsSource, "uv", _versionColumn, _versionColumn.Attribute.MapType, _updateVersionValue)); + } else sb.Append(", ").Append(vcname).Append(" = ").Append(_commonUtils.IsNull(vcname, 0)).Append(" + 1"); } diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs index bd341b7e..2bd827ff 100644 --- a/FreeSql/Internal/UtilsExpressionTree.cs +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -326,6 +326,7 @@ namespace FreeSql.Internal break; } } + if (colattr.MapType == typeof(string) && colattr.IsVersion == true) colattr.StringLength = 40; if (colattr.MapType == typeof(byte[]) && colattr.IsVersion == true) colattr.StringLength = 16; if (colattr.MapType == typeof(byte[]) && colattr.StringLength != 0) { @@ -395,7 +396,8 @@ namespace FreeSql.Internal trytb.VersionColumn = trytb.Columns.Values.Where(a => a.Attribute.IsVersion == true).LastOrDefault(); if (trytb.VersionColumn != null) { - if (trytb.VersionColumn.Attribute.MapType.IsNullableType() || trytb.VersionColumn.Attribute.MapType.IsNumberType() == false && trytb.VersionColumn.Attribute.MapType != typeof(byte[])) + if (trytb.VersionColumn.Attribute.MapType.IsNullableType() || + trytb.VersionColumn.Attribute.MapType.IsNumberType() == false && !new[] { typeof(byte[]), typeof(string) }.Contains(trytb.VersionColumn.Attribute.MapType)) throw new Exception(CoreStrings.Properties_AsRowLock_Must_Numeric_Byte(trytb.VersionColumn.CsName)); } tbattr?.ParseAsTable(trytb); diff --git a/FreeSql/Properties/CoreStrings.Designer.cs b/FreeSql/Properties/CoreStrings.Designer.cs index 0901b233..146386c8 100644 --- a/FreeSql/Properties/CoreStrings.Designer.cs +++ b/FreeSql/Properties/CoreStrings.Designer.cs @@ -827,7 +827,7 @@ namespace FreeSql policyName, UnavailableExceptionMessage); /// - /// 属性{trytbVersionColumnCsName} 被标注为行锁(乐观锁)(IsVersion),但其必须为数字类型 或者 byte[],并且不可为 Nullable + /// 属性{trytbVersionColumnCsName} 被标注为行锁(乐观锁)(IsVersion),但其必须为数字类型 或者 byte[] 或者 string,并且不可为 Nullable /// public static string Properties_AsRowLock_Must_Numeric_Byte(object trytbVersionColumnCsName) => string.Format( diff --git a/FreeSql/Properties/CoreStrings.resx b/FreeSql/Properties/CoreStrings.resx index a8f6cb48..6328057c 100644 --- a/FreeSql/Properties/CoreStrings.resx +++ b/FreeSql/Properties/CoreStrings.resx @@ -433,7 +433,7 @@ FreeSql: The {policyName} status is unavailable and cannot be used until the background checker is restored. {UnavailableExceptionMessage} - FreeSql: The property {trytbVersionColumnCsName} is labeled as a row lock (optimistic lock) (IsVersion), but it must be a numeric type or byte[], and it cannot be Nullable + FreeSql: The property {trytbVersionColumnCsName} is labeled as a row lock (optimistic lock) (IsVersion), but it must be a numeric type or byte[] or string, and it cannot be Nullable FreeSql: Properrties parameter cannot be empty diff --git a/FreeSql/Properties/CoreStrings.zh-Hans.resx b/FreeSql/Properties/CoreStrings.zh-Hans.resx index 48144c51..2b501999 100644 --- a/FreeSql/Properties/CoreStrings.zh-Hans.resx +++ b/FreeSql/Properties/CoreStrings.zh-Hans.resx @@ -433,7 +433,7 @@ 【{policyName}】状态不可用,等待后台检查程序恢复方可使用。{UnavailableExceptionMessage} - 属性{trytbVersionColumnCsName} 被标注为行锁(乐观锁)(IsVersion),但其必须为数字类型 或者 byte[],并且不可为 Nullable + 属性{trytbVersionColumnCsName} 被标注为行锁(乐观锁)(IsVersion),但其必须为数字类型 或者 byte[] 或者 string,并且不可为 Nullable properties 参数不能为空 diff --git a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs index cdca5e8b..332ac7c9 100644 --- a/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs +++ b/Providers/FreeSql.Provider.ClickHouse/Curd/ClickHouseUpdate.cs @@ -206,6 +206,14 @@ namespace FreeSql.ClickHouse.Curd _updateVersionValue = Utils.GuidToBytes(Guid.NewGuid()); sb.Append(", ").Append(vcname).Append(" = ").Append(_commonUtils.GetNoneParamaterSqlValue(_paramsSource, "uv", _table.VersionColumn, _table.VersionColumn.Attribute.MapType, _updateVersionValue)); } + else if (_versionColumn.Attribute.MapType == typeof(string)) + { + _updateVersionValue = Guid.NewGuid().ToString(); + sb.Append(", ").Append(vcname).Append(" = ").Append(_noneParameter ? _commonUtils.GetNoneParamaterSqlValue(_paramsSource, "uv", _versionColumn, _versionColumn.Attribute.MapType, _updateVersionValue) : + _commonUtils.QuoteWriteParamterAdapter(_versionColumn.Attribute.MapType, _commonUtils.QuoteParamterName($"p_{_paramsSource.Count}"))); + if (_noneParameter == false) + _commonUtils.AppendParamter(_paramsSource, null, _versionColumn, _versionColumn.Attribute.MapType, _updateVersionValue); + } else sb.Append(", ").Append(vcname).Append(" = ").Append(_commonUtils.IsNull(vcname, 0)).Append(" + 1"); }