From 090abfd36a17f0fe79163c26981876b1bf3c0261 Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Tue, 6 Sep 2022 17:00:31 +0800 Subject: [PATCH] v3.2.667 #1237 --- Directory.Build.props | 2 +- .../TransactionalAttribute.cs | 2 +- .../AggregateRootBoundaryAttribute.cs | 0 .../AggregateRootModel.cs | 0 .../AggregateRootRepository.cs | 0 .../AggregateRootRepositoryAsync.cs | 0 .../AggregateRootRepositorySync.cs | 0 .../AggregateRootUtils.cs | 0 .../FreeSqlRepositoryExtensions.cs | 2 +- .../FreeSql.Extensions.AggregateRoot.csproj | 37 + .../FreeSql.Extensions.AggregateRoot.xml | 21 + .../FreeSql.Extensions.AggregateRoot/key.snk | Bin 0 -> 596 bytes .../FreeSql.Extensions.BaseEntity.csproj | 2 +- .../FreeSql.Extensions.JsonMap.csproj | 2 +- .../FreeSql.Extensions.LazyLoading.csproj | 2 +- .../FreeSql.Extensions.Linq.csproj | 2 +- .../FreeSql.Generator.csproj | 2 +- FreeSql.All/FreeSql.All.csproj | 2 +- .../AggregateRootBoundaryAttribute.cs | 34 + .../AggregateRootModel.cs | 17 + .../AggregateRootRepository.cs | 261 +++++++ .../AggregateRootRepositoryAsync.cs | 297 ++++++++ .../AggregateRootRepositorySync.cs | 347 ++++++++++ .../AggregateRootUtils.cs | 652 ++++++++++++++++++ .../FreeSqlRepositoryExtensions.cs | 12 + .../FreeSql - Backup.DbContext.csproj | 84 +++ FreeSql.DbContext/FreeSql.DbContext.xml | 62 ++ FreeSql.Repository/FreeSql.Repository.csproj | 2 +- FreeSql.Tests/FreeSql.Tests/Issues/1237.cs | 66 ++ FreeSql/FreeSql.csproj | 2 +- .../FreeSql.Provider.ClickHouse.csproj | 2 +- .../FreeSql.Provider.Custom.csproj | 2 +- .../FreeSql.Provider.Dameng.csproj | 2 +- .../FreeSql.Provider.Firebird.csproj | 2 +- .../FreeSql.Provider.GBase.csproj | 2 +- .../FreeSql.Provider.KingbaseES.csproj | 2 +- .../FreeSql.Provider.MsAccess.csproj | 2 +- .../FreeSql.Provider.MySql.csproj | 2 +- .../FreeSql.Provider.MySqlConnector.csproj | 2 +- .../FreeSql.Provider.Odbc.csproj | 2 +- .../FreeSql.Provider.Oracle.csproj | 2 +- .../FreeSql.Provider.OracleOledb.csproj | 2 +- .../FreeSql.Provider.PostgreSQL.csproj | 2 +- .../FreeSql.Provider.ShenTong.csproj | 2 +- .../FreeSql.Provider.SqlServer.csproj | 2 +- ...FreeSql.Provider.SqlServerForSystem.csproj | 2 +- .../FreeSql.Provider.Sqlite.csproj | 2 +- .../FreeSql.Provider.SqliteCore.csproj | 2 +- 48 files changed, 1919 insertions(+), 29 deletions(-) rename {FreeSql.Repository => Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository}/AggregateRootBoundaryAttribute.cs (100%) rename {FreeSql.Repository => Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository}/AggregateRootModel.cs (100%) rename {FreeSql.Repository => Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository}/AggregateRootRepository.cs (100%) rename {FreeSql.Repository => Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository}/AggregateRootRepositoryAsync.cs (100%) rename {FreeSql.Repository => Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository}/AggregateRootRepositorySync.cs (100%) rename {FreeSql.Repository => Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository}/AggregateRootUtils.cs (100%) rename {FreeSql.Repository => Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository}/FreeSqlRepositoryExtensions.cs (80%) create mode 100644 Extensions/FreeSql.Extensions.AggregateRoot/FreeSql.Extensions.AggregateRoot.csproj create mode 100644 Extensions/FreeSql.Extensions.AggregateRoot/FreeSql.Extensions.AggregateRoot.xml create mode 100644 Extensions/FreeSql.Extensions.AggregateRoot/key.snk create mode 100644 FreeSql.DbContext/AggregateRootRepository/AggregateRootBoundaryAttribute.cs create mode 100644 FreeSql.DbContext/AggregateRootRepository/AggregateRootModel.cs create mode 100644 FreeSql.DbContext/AggregateRootRepository/AggregateRootRepository.cs create mode 100644 FreeSql.DbContext/AggregateRootRepository/AggregateRootRepositoryAsync.cs create mode 100644 FreeSql.DbContext/AggregateRootRepository/AggregateRootRepositorySync.cs create mode 100644 FreeSql.DbContext/AggregateRootRepository/AggregateRootUtils.cs create mode 100644 FreeSql.DbContext/AggregateRootRepository/FreeSqlRepositoryExtensions.cs create mode 100644 FreeSql.DbContext/FreeSql - Backup.DbContext.csproj create mode 100644 FreeSql.Tests/FreeSql.Tests/Issues/1237.cs diff --git a/Directory.Build.props b/Directory.Build.props index 897cbb2b..b648c47c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ diff --git a/Examples/aspnetcore_transaction/TransactionalAttribute.cs b/Examples/aspnetcore_transaction/TransactionalAttribute.cs index d1ab3350..49812405 100644 --- a/Examples/aspnetcore_transaction/TransactionalAttribute.cs +++ b/Examples/aspnetcore_transaction/TransactionalAttribute.cs @@ -6,7 +6,7 @@ using Rougamo.Context; namespace FreeSql { - [AttributeUsage(AttributeTargets.Method)] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class TransactionalAttribute : Rougamo.MoAttribute { public Propagation Propagation { get; set; } = Propagation.Required; diff --git a/FreeSql.Repository/AggregateRootBoundaryAttribute.cs b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootBoundaryAttribute.cs similarity index 100% rename from FreeSql.Repository/AggregateRootBoundaryAttribute.cs rename to Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootBoundaryAttribute.cs diff --git a/FreeSql.Repository/AggregateRootModel.cs b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootModel.cs similarity index 100% rename from FreeSql.Repository/AggregateRootModel.cs rename to Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootModel.cs diff --git a/FreeSql.Repository/AggregateRootRepository.cs b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepository.cs similarity index 100% rename from FreeSql.Repository/AggregateRootRepository.cs rename to Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepository.cs diff --git a/FreeSql.Repository/AggregateRootRepositoryAsync.cs b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepositoryAsync.cs similarity index 100% rename from FreeSql.Repository/AggregateRootRepositoryAsync.cs rename to Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepositoryAsync.cs diff --git a/FreeSql.Repository/AggregateRootRepositorySync.cs b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepositorySync.cs similarity index 100% rename from FreeSql.Repository/AggregateRootRepositorySync.cs rename to Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepositorySync.cs diff --git a/FreeSql.Repository/AggregateRootUtils.cs b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootUtils.cs similarity index 100% rename from FreeSql.Repository/AggregateRootUtils.cs rename to Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootUtils.cs diff --git a/FreeSql.Repository/FreeSqlRepositoryExtensions.cs b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/FreeSqlRepositoryExtensions.cs similarity index 80% rename from FreeSql.Repository/FreeSqlRepositoryExtensions.cs rename to Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/FreeSqlRepositoryExtensions.cs index 6707ad11..0f5da722 100644 --- a/FreeSql.Repository/FreeSqlRepositoryExtensions.cs +++ b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/FreeSqlRepositoryExtensions.cs @@ -3,7 +3,7 @@ using System; using System.Linq; using System.Linq.Expressions; -public static class FreeSqlRepositoryExtensions +public static class FreeSqlAggregateRootRepositoryGlobalExtensions { public static IBaseRepository GetAggregateRootRepository(this IFreeSql that) where TEntity : class { diff --git a/Extensions/FreeSql.Extensions.AggregateRoot/FreeSql.Extensions.AggregateRoot.csproj b/Extensions/FreeSql.Extensions.AggregateRoot/FreeSql.Extensions.AggregateRoot.csproj new file mode 100644 index 00000000..6c145249 --- /dev/null +++ b/Extensions/FreeSql.Extensions.AggregateRoot/FreeSql.Extensions.AggregateRoot.csproj @@ -0,0 +1,37 @@ + + + + netstandard2.0;net60;net50;netcoreapp31;netcoreapp21;net45;net40 + true + FreeSql;ncc;YeXiangQin + FreeSql 扩展包,聚合根(实现室). + https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89 + https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89 + git + MIT + FreeSql;ORM + $(AssemblyName) + logo.png + $(AssemblyName) + true + true + true + key.snk + false + 1.0.0 + + + + + + + + FreeSql.Extensions.AggregateRoot.xml + 3 + + + + + + + diff --git a/Extensions/FreeSql.Extensions.AggregateRoot/FreeSql.Extensions.AggregateRoot.xml b/Extensions/FreeSql.Extensions.AggregateRoot/FreeSql.Extensions.AggregateRoot.xml new file mode 100644 index 00000000..75b8c707 --- /dev/null +++ b/Extensions/FreeSql.Extensions.AggregateRoot/FreeSql.Extensions.AggregateRoot.xml @@ -0,0 +1,21 @@ + + + + FreeSql.Extensions.JsonMap + + + + + When the entity class property is , map storage in JSON format.
+ 当实体类属性为【对象】时,以 JSON 形式映射存储 +
+
+ + + When the entity class property is and the attribute is marked as , map storage in JSON format.
+ 当实体类属性为【对象】时,并且标记特性 [JsonMap] 时,该属性将以JSON形式映射存储 +
+ +
+
+
diff --git a/Extensions/FreeSql.Extensions.AggregateRoot/key.snk b/Extensions/FreeSql.Extensions.AggregateRoot/key.snk new file mode 100644 index 0000000000000000000000000000000000000000..e580bc8d5d64e7c5a0c62b971545d38cfbe7d837 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096c(W3|+clf|4d2=6Xc+R`Gd@9@k@Meh} zR8`}1=JPk=q?Zlr?i$1O?SgX-{{&z z|LRF?-aWODhAO}h_7M!wz}uPXx}n-g{((r9{{%_ z4)%gVXcj;Ru@GYAIZI@e#GBtO#O5m-Qr4X_lbAV}=qNRkd0^`@I6i9k`wSe@ZPxVo zk;MXig(S-cYHE!0GWWlp7EH@E!WkF6jS+3z4rvW0%Sq;U1bq`B9*HNJjxo*23*7Vw zHyt>{2CR~8==`lYLgAmwsXPXYZ_AEAKy|PuUz0(G)L1xO{{*n6Bn?mV~QKg!055THihpc>GOh(U-NgO?4_DzY4uq!p9=Q;`G i9;v3GkC674Mbx4_b$)?7a0%Z%&zUjzbGs@!l^s|B literal 0 HcmV?d00001 diff --git a/Extensions/FreeSql.Extensions.BaseEntity/FreeSql.Extensions.BaseEntity.csproj b/Extensions/FreeSql.Extensions.BaseEntity/FreeSql.Extensions.BaseEntity.csproj index ac5fabdd..dd7c1c20 100644 --- a/Extensions/FreeSql.Extensions.BaseEntity/FreeSql.Extensions.BaseEntity.csproj +++ b/Extensions/FreeSql.Extensions.BaseEntity/FreeSql.Extensions.BaseEntity.csproj @@ -19,7 +19,7 @@ key.snk false latest - 3.2.666 + 3.2.667 diff --git a/Extensions/FreeSql.Extensions.JsonMap/FreeSql.Extensions.JsonMap.csproj b/Extensions/FreeSql.Extensions.JsonMap/FreeSql.Extensions.JsonMap.csproj index e93b7af0..6d1ebab7 100644 --- a/Extensions/FreeSql.Extensions.JsonMap/FreeSql.Extensions.JsonMap.csproj +++ b/Extensions/FreeSql.Extensions.JsonMap/FreeSql.Extensions.JsonMap.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Extensions/FreeSql.Extensions.LazyLoading/FreeSql.Extensions.LazyLoading.csproj b/Extensions/FreeSql.Extensions.LazyLoading/FreeSql.Extensions.LazyLoading.csproj index e93ecd34..157117c1 100644 --- a/Extensions/FreeSql.Extensions.LazyLoading/FreeSql.Extensions.LazyLoading.csproj +++ b/Extensions/FreeSql.Extensions.LazyLoading/FreeSql.Extensions.LazyLoading.csproj @@ -15,7 +15,7 @@ $(AssemblyName) true true - 3.2.666 + 3.2.667 diff --git a/Extensions/FreeSql.Extensions.Linq/FreeSql.Extensions.Linq.csproj b/Extensions/FreeSql.Extensions.Linq/FreeSql.Extensions.Linq.csproj index 6883e1ea..21979675 100644 --- a/Extensions/FreeSql.Extensions.Linq/FreeSql.Extensions.Linq.csproj +++ b/Extensions/FreeSql.Extensions.Linq/FreeSql.Extensions.Linq.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Extensions/FreeSql.Generator/FreeSql.Generator.csproj b/Extensions/FreeSql.Generator/FreeSql.Generator.csproj index 44818b58..f20e00e0 100644 --- a/Extensions/FreeSql.Generator/FreeSql.Generator.csproj +++ b/Extensions/FreeSql.Generator/FreeSql.Generator.csproj @@ -13,7 +13,7 @@ https://github.com/2881099/FreeSql https://github.com/2881099/FreeSql FreeSql DbFirst 实体生成器 - 3.2.666 + 3.2.667 diff --git a/FreeSql.All/FreeSql.All.csproj b/FreeSql.All/FreeSql.All.csproj index dae2607a..0e34a197 100644 --- a/FreeSql.All/FreeSql.All.csproj +++ b/FreeSql.All/FreeSql.All.csproj @@ -17,7 +17,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/FreeSql.DbContext/AggregateRootRepository/AggregateRootBoundaryAttribute.cs b/FreeSql.DbContext/AggregateRootRepository/AggregateRootBoundaryAttribute.cs new file mode 100644 index 00000000..a2603b44 --- /dev/null +++ b/FreeSql.DbContext/AggregateRootRepository/AggregateRootBoundaryAttribute.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; + +namespace FreeSql.DataAnnotations +{ + + /// + /// 设置 AggregateRootRepository 边界范围 + /// 在边界范围之内的规则 : + /// 1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改 + /// 2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略) + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class AggregateRootBoundaryAttribute : Attribute + { + public string Name { get; set; } + /// + /// 边界是否终止 + /// + public bool Break { get; set; } + /// + /// 边界是否终止向下探测 + /// + public bool BreakThen { get; set; } + + public AggregateRootBoundaryAttribute(string name) + { + this.Name = name; + } + public AggregateRootBoundaryAttribute() + { + } + } +} diff --git a/FreeSql.DbContext/AggregateRootRepository/AggregateRootModel.cs b/FreeSql.DbContext/AggregateRootRepository/AggregateRootModel.cs new file mode 100644 index 00000000..7bff61bd --- /dev/null +++ b/FreeSql.DbContext/AggregateRootRepository/AggregateRootModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace FreeSql.Internal.Model +{ + + public class AggregateRootTrackingChangeInfo + { + + public List> InsertLog { get; } = new List>(); + + public List>> UpdateLog { get; } = new List>>(); + + public List> DeleteLog { get; } = new List>(); + + } +} \ No newline at end of file diff --git a/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepository.cs b/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepository.cs new file mode 100644 index 00000000..ed043f80 --- /dev/null +++ b/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepository.cs @@ -0,0 +1,261 @@ +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace FreeSql +{ + public interface IAggregateRootRepository: IBaseRepository where TEntity : class + { + IBaseRepository ChangeBoundary(string name); + } + + public partial class AggregateRootRepository : IAggregateRootRepository where TEntity : class + { + readonly IBaseRepository _repository; + public AggregateRootRepository(IFreeSql fsql) + { + if (fsql == null) throw new ArgumentNullException(nameof(fsql)); + _repository = fsql.GetRepository(); + _repository.DbContextOptions.EnableCascadeSave = false; + } + public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager) : this(uowManager?.Orm ?? fsql) + { + uowManager?.Binding(_repository); + } + public void Dispose() + { + DisposeChildRepositorys(); + _repository.FlushState(); + _repository.Dispose(); + FlushState(); + } + + string _boundaryName = ""; + public IBaseRepository ChangeBoundary(string name) + { + DisposeChildRepositorys(); + _repository.FlushState(); + FlushState(); + _boundaryName = string.Concat(name).Trim(); + return this; + } + + public IFreeSql Orm => _repository.Orm; + public IUnitOfWork UnitOfWork { get => _repository.UnitOfWork; set => _repository.UnitOfWork = value; } + public DbContextOptions DbContextOptions + { + get => _repository.DbContextOptions; + set + { + if (value == null) throw new ArgumentNullException(nameof(DbContextOptions)); + _repository.DbContextOptions = value; + _repository.DbContextOptions.EnableCascadeSave = false; + } + } + public void AsType(Type entityType) => _repository.AsType(entityType); + Func _asTableRule; + public void AsTable(Func rule) + { + _repository.AsTable(rule); + _asTableRule = rule; + } + public Type EntityType => _repository.EntityType; + public IDataFilter DataFilter => _repository.DataFilter; + + public void Attach(TEntity entity) + { + var state = CreateEntityState(entity); + if (_states.ContainsKey(state.Key)) _states[state.Key] = state; + else _states.Add(state.Key, state); + } + public void Attach(IEnumerable entity) + { + foreach (var item in entity) + Attach(item); + } + public IBaseRepository AttachOnlyPrimary(TEntity data) => _repository.AttachOnlyPrimary(data); + public Dictionary CompareState(TEntity newdata) + { + if (newdata == null) return null; + var _table = Orm.CodeFirst.GetTableByEntity(EntityType); + if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.Incomparable_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, newdata))); + var key = Orm.GetEntityKeyString(EntityType, newdata, false); + if (string.IsNullOrEmpty(key)) throw new Exception(DbContextStrings.Incomparable_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, newdata))); + if (_states.TryGetValue(key, out var oldState) == false || oldState == null) throw new Exception($"不可对比,数据未被跟踪:{Orm.GetEntityString(EntityType, newdata)}"); + AggregateRootTrackingChangeInfo tracking = new AggregateRootTrackingChangeInfo(); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, oldState, newdata, null, tracking); + return new Dictionary + { + ["Insert"] = tracking.InsertLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray(), + ["Delete"] = tracking.DeleteLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray(), + ["Update"] = tracking.UpdateLog.Select(a => new object[] { a.Item1, a.Item2, a.Item3, a.Item4 }).ToArray(), + }; + } + public void FlushState() + { + DisposeChildRepositorys(); + _repository.FlushState(); + _states.Clear(); + } + + public IUpdate UpdateDiy => _repository.UpdateDiy; + public ISelect Where(Expression> exp) => Select.Where(exp); + public ISelect WhereIf(bool condition, Expression> exp) => Select.WhereIf(condition, exp); + + readonly Dictionary> _childRepositorys = new Dictionary>(); + IBaseRepository GetChildRepository(Type type) + { + if (_childRepositorys.TryGetValue(type, out var repo) == false) + { + repo = Orm.GetRepository(); + repo.AsType(type); + _childRepositorys.Add(type, repo); + } + repo.UnitOfWork = UnitOfWork; + repo.DbContextOptions = DbContextOptions; + repo.DbContextOptions.EnableCascadeSave = false; + repo.AsTable(_asTableRule); + return repo; + } + void DisposeChildRepositorys() + { + foreach (var repo in _childRepositorys.Values) + { + repo.FlushState(); + repo.Dispose(); + } + _childRepositorys.Clear(); + } + + #region 状态管理 + protected Dictionary _states = new Dictionary(); + protected class EntityState + { + public EntityState(TEntity value, string key) + { + this.Value = value; + this.Key = key; + this.Time = DateTime.Now; + } + public TEntity OldValue { get; set; } + public TEntity Value { get; set; } + public string Key { get; set; } + public DateTime Time { get; set; } + } + EntityState CreateEntityState(TEntity data) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + var key = Orm.GetEntityKeyString(EntityType, data, false); + var state = new EntityState((TEntity)EntityType.CreateInstanceGetDefaultValue(), key); + AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, data, state.Value); + return state; + } + bool? ExistsInStates(object data) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + var key = Orm.GetEntityKeyString(EntityType, data, false); + if (string.IsNullOrEmpty(key)) return null; + return _states.ContainsKey(key); + } + #endregion + + #region 查询数据 + /// + /// 默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性) + /// 重写:使用 + /// + public virtual ISelect Select => SelectAggregateRoot; + /// + /// 创建查询对象(纯净) + /// _ + /// 聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发 + /// string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order)) + /// + protected ISelect SelectDiy => _repository.Select.TrackToList(SelectAggregateRootTracking); + /// + /// 创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性) + /// + /// + protected ISelect SelectAggregateRoot + { + get + { + var query = _repository.Select.TrackToList(SelectAggregateRootTracking); + query = AggregateRootUtils.GetAutoIncludeQuery(_boundaryName, query); + return query; + } + } + /// + /// ISelect.TrackToList 委托,数据返回后自动 Attach + /// + /// + protected void SelectAggregateRootTracking(object list) + { + if (list == null) return; + var ls = list as IEnumerable; + if (ls == null) + { + var ie = list as IEnumerable; + if (ie == null) return; + var isfirst = true; + foreach (var item in ie) + { + if (item == null) continue; + if (isfirst) + { + isfirst = false; + var itemType = item.GetType(); + if (itemType == typeof(object)) return; + if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType; + if (Orm.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return; + if (itemType.GetConstructor(Type.EmptyTypes) == null) return; + } + if (item is TEntity item2) Attach(item2); + else return; + } + return; + } + } + //void SelectAggregateRootNavigateReader(ISelect currentQuery, Type entityType, string navigatePath, Stack ignores) + //{ + // if (ignores.Any(a => a == entityType)) return; + // ignores.Push(entityType); + // var table = Orm.CodeFirst.GetTableByEntity(entityType); + // if (table == null) return; + // if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}."; + // foreach (var tr in table.GetAllTableRef()) + // { + // var tbref = tr.Value; + // if (tbref.Exception != null) continue; + // var navigateExpression = $"{navigatePath}{tr.Key}"; + // switch (tbref.RefType) + // { + // case TableRefType.OneToOne: + // if (ignores.Any(a => a == tbref.RefEntityType)) break; + // currentQuery.IncludeByPropertyName(navigateExpression); + // SelectAggregateRootNavigateReader(currentQuery, tbref.RefEntityType, navigateExpression, ignores); + // break; + // case TableRefType.OneToMany: + // var ignoresCopy = new Stack(ignores.ToArray()); + // currentQuery.IncludeByPropertyName(navigateExpression, then => + // SelectAggregateRootNavigateReader(then, tbref.RefEntityType, "", ignoresCopy)); //variable 'then' of type 'FreeSql.ISelect`1[System.Object]' referenced from scope '', but it is not defined + // break; + // case TableRefType.ManyToMany: + // currentQuery.IncludeByPropertyName(navigateExpression); + // break; + // case TableRefType.PgArrayToMany: + // break; + // case TableRefType.ManyToOne: + // break; + // } + // } + // ignores.Pop(); + //} + #endregion + + } +} diff --git a/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepositoryAsync.cs b/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepositoryAsync.cs new file mode 100644 index 00000000..1da92ee8 --- /dev/null +++ b/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepositoryAsync.cs @@ -0,0 +1,297 @@ +#if net40 +#else +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql +{ + partial class AggregateRootRepository + { + + #region InsertAsync + async public virtual Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => (await InsertAsync(new[] { entity }, cancellationToken)).FirstOrDefault(); + async public virtual Task> InsertAsync(IEnumerable entitys, CancellationToken cancellationToken = default) + { + var repos = new Dictionary(); + try + { + var ret = await InsertWithinBoundaryStaticAsync(_boundaryName, _repository, GetChildRepository, entitys, null, cancellationToken); + Attach(ret); + return ret; + } + finally + { + DisposeChildRepositorys(); + _repository.FlushState(); + } + } + async Task> InsertWithinBoundaryStaticAsync(string boundaryName, IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys, int[] affrows, CancellationToken cancellationToken) where T1 : class + { + Dictionary> ignores = new Dictionary>(); + Dictionary> repos = new Dictionary>(); + var localAffrows = 0; + try + { + return await LocalInsertAsync(rootRepository, rootEntitys, true); + } + finally + { + if (affrows != null) affrows[0] = localAffrows; + } + + bool LocalCanInsert(Type entityType, object entity, bool isadd) + { + var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false); + if (stateKey == null) return true; + if (ignores.TryGetValue(entityType, out var stateKeys) == false) + { + if (isadd) + { + ignores.Add(entityType, stateKeys = new Dictionary()); + stateKeys.Add(stateKey, true); + } + return true; + } + if (stateKeys.ContainsKey(stateKey) == false) + { + if (isadd) stateKeys.Add(stateKey, true); + return true; + } + return false; + } + async Task> LocalInsertAsync(IBaseRepository repository, IEnumerable entitys, bool cascade) where T2 : class + { + var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType); + if (table.Primarys.Any(col => col.Attribute.IsIdentity)) + { + foreach (var entity in entitys) + repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity); + } + var ret = await repository.InsertAsync(entitys, cancellationToken); + localAffrows += ret.Count; + foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true); + if (cascade == false) return ret; + + foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) + { + var tbref = tr.Value; + if (tbref.Exception != null) continue; + if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; + var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName); + if (boundaryAttr?.Break == true) continue; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + var otoList = ret.Select(entity => + { + var otoItem = table.GetPropertyValue(entity, prop.Name); + if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null; + AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem); + return otoItem; + }).Where(entity => entity != null).ToArray(); + if (otoList.Any()) + { + var repo = getChildRepository(tbref.RefEntityType); + await LocalInsertAsync(repo, otoList, boundaryAttr?.BreakThen != true); + } + break; + case TableRefType.OneToMany: + var otmList = ret.Select(entity => + { + var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable; + if (otmEach == null) return null; + var otmItems = new List(); + foreach (var otmItem in otmEach) + { + if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue; + otmItems.Add(otmItem); + } + AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems); + return otmItems; + }).Where(entity => entity != null).SelectMany(entity => entity).ToArray(); + if (otmList.Any()) + { + var repo = getChildRepository(tbref.RefEntityType); + await LocalInsertAsync(repo, otmList, boundaryAttr?.BreakThen != true); + } + break; + case TableRefType.ManyToMany: + var mtmMidList = new List(); + ret.ForEach(entity => + { + var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop); + if (mids != null) mtmMidList.AddRange(mids); + }); + if (mtmMidList.Any()) + { + var repo = getChildRepository(tbref.RefMiddleEntityType); + await LocalInsertAsync(repo, mtmMidList, false); + } + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 + break; + } + } + return ret; + } + } + #endregion + + async public virtual Task InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default) + { + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (entity == null) throw new ArgumentNullException(nameof(entity)); + var table = Orm.CodeFirst.GetTableByEntity(EntityType); + if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity))); + + var flagExists = ExistsInStates(entity); + if (flagExists == false) + { + var olddata = await Select.WhereDynamic(entity).FirstAsync(cancellationToken); + flagExists = olddata != null; + } + if (flagExists == true) + { + var affrows = await UpdateAsync(entity, cancellationToken); + if (affrows > 0) return entity; + } + if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length) + { + Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity); + return await InsertAsync(entity, cancellationToken); + } + throw new Exception(DbContextStrings.CannotAdd_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, entity))); + } + + public virtual Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => UpdateAsync(new[] { entity }, cancellationToken); + public virtual Task UpdateAsync(IEnumerable entitys, CancellationToken cancellationToken = default) + { + var tracking = new AggregateRootTrackingChangeInfo(); + foreach (var entity in entitys) + { + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}"); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking); + } + foreach (var entity in entitys) + Attach(entity); + + return SaveTrackingChangeAsync(tracking, cancellationToken); + } + + + public virtual Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(new[] { entity }, null, cancellationToken); + public virtual Task DeleteAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(entitys, null, cancellationToken); + async public virtual Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) => await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), null, cancellationToken); + async public virtual Task> DeleteCascadeByDatabaseAsync(Expression> predicate, CancellationToken cancellationToken = default) + { + var deletedOutput = new List(); + await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), deletedOutput, cancellationToken); + return deletedOutput; + } + async Task DeleteWithinBoundaryAsync(IEnumerable entitys, List deletedOutput, CancellationToken cancellationToken) + { + var tracking = new AggregateRootTrackingChangeInfo(); + foreach (var entity in entitys) + { + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking); + _states.Remove(stateKey); + } + var affrows = 0; + for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--) + { + affrows += await Orm.Delete().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule) + .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken); + if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2); + UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x => + new DbContext.EntityChangeReport.ChangeInfo + { + Type = DbContext.EntityChangeType.Delete, + EntityType = tracking.DeleteLog[a].Item1, + Object = x + })); + } + return affrows; + } + + async public virtual Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default) + { + var tracking = new AggregateRootTrackingChangeInfo(); + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}"); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking); + Attach(entity); //应该只存储 propertyName 内容 + await SaveTrackingChangeAsync(tracking, cancellationToken); + } + + + async Task SaveTrackingChangeAsync(AggregateRootTrackingChangeInfo tracking, CancellationToken cancellationToken) + { + var affrows = 0; + DisposeChildRepositorys(); + var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray()); + foreach (var il in insertLogDict) + { + var repo = GetChildRepository(il.Key); + var affrowsOut = new int[1]; + await InsertWithinBoundaryStaticAsync(_boundaryName, repo, GetChildRepository, il.Value, affrowsOut, cancellationToken); + affrows += affrowsOut[0]; + } + + for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--) + { + affrows += await Orm.Delete().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule) + .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken); + UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x => + new DbContext.EntityChangeReport.ChangeInfo + { + Type = DbContext.EntityChangeType.Delete, + EntityType = tracking.DeleteLog[a].Item1, + Object = x + })); + } + + var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => new + { + BeforeObject = b.Item2, + AfterObject = b.Item3, + UpdateColumns = b.Item4, + UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c)) + }).ToArray()); + var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.UpdateColumnsString, b => a.Value.Where(c => c.UpdateColumnsString == b.UpdateColumnsString).ToArray())); + foreach (var dl in updateLogDict2) + { + foreach (var dl2 in dl.Value) + { + affrows += await Orm.Update().AsType(dl.Key).AsTable(_asTableRule) + .SetSource(dl2.Value.Select(a => a.AfterObject).ToArray()) + .UpdateColumns(dl2.Value.First().UpdateColumns.ToArray()) + .ExecuteAffrowsAsync(cancellationToken); + UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x => + new DbContext.EntityChangeReport.ChangeInfo + { + Type = DbContext.EntityChangeType.Update, + EntityType = dl.Key, + Object = x.AfterObject, + BeforeObject = x.BeforeObject + })); + } + } + DisposeChildRepositorys(); + return affrows; + } + } +} +#endif \ No newline at end of file diff --git a/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepositorySync.cs b/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepositorySync.cs new file mode 100644 index 00000000..cdd18e00 --- /dev/null +++ b/FreeSql.DbContext/AggregateRootRepository/AggregateRootRepositorySync.cs @@ -0,0 +1,347 @@ +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql +{ + partial class AggregateRootRepository + { + + #region BeginEdit/EndEdit + List _dataEditing; + ConcurrentDictionary _statesEditing = new ConcurrentDictionary(); + public virtual void BeginEdit(List data) + { + if (data == null) return; + var table = Orm.CodeFirst.GetTableByEntity(EntityType); + if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotEdit_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, data.First()))); + _statesEditing.Clear(); + _dataEditing = data; + foreach (var item in data) + { + var key = Orm.GetEntityKeyString(EntityType, item, false); + if (string.IsNullOrEmpty(key)) continue; + + _statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) => + { + AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, item, ov.Value); + ov.Time = DateTime.Now; + return ov; + }); + } + } + public virtual int EndEdit(List data = null) + { + if (data == null) data = _dataEditing; + if (data == null) return 0; + var tracking = new AggregateRootTrackingChangeInfo(); + try + { + var addList = new List(); + var ediList = new List(); + foreach (var item in data) + { + var key = Orm.GetEntityKeyString(EntityType, item, false); + if (_statesEditing.TryRemove(key, out var state) == false) + { + tracking.InsertLog.Add(NativeTuple.Create(EntityType, (object)item)); + continue; + } + _states[key] = state; + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, item, null, tracking); + } + foreach (var item in _statesEditing.Values.OrderBy(a => a.Time)) + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, item, null, null, tracking); + + return SaveTrackingChange(tracking); + } + finally + { + _dataEditing = null; + _statesEditing.Clear(); + } + } + #endregion + + #region Insert + public virtual TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault(); + public virtual List Insert(IEnumerable entitys) + { + var repos = new Dictionary(); + try + { + var ret = InsertWithinBoundaryStatic(_boundaryName, _repository, GetChildRepository, entitys, out var affrows); + Attach(ret); + return ret; + } + finally + { + DisposeChildRepositorys(); + _repository.FlushState(); + } + } + static List InsertWithinBoundaryStatic(string boundaryName, IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys, out int affrows) where T1 : class { + Dictionary> ignores = new Dictionary>(); + Dictionary> repos = new Dictionary>(); + var localAffrows = 0; + try + { + return LocalInsert(rootRepository, rootEntitys, true); + } + finally + { + affrows = localAffrows; + } + + bool LocalCanInsert(Type entityType, object entity, bool isadd) + { + var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false); + if (stateKey == null) return true; + if (ignores.TryGetValue(entityType, out var stateKeys) == false) + { + if (isadd) + { + ignores.Add(entityType, stateKeys = new Dictionary()); + stateKeys.Add(stateKey, true); + } + return true; + } + if (stateKeys.ContainsKey(stateKey) == false) + { + if (isadd) stateKeys.Add(stateKey, true); + return true; + } + return false; + } + List LocalInsert(IBaseRepository repository, IEnumerable entitys, bool cascade) where T2 : class + { + var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType); + if (table.Primarys.Any(col => col.Attribute.IsIdentity)) + { + foreach (var entity in entitys) + repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity); + } + var ret = repository.Insert(entitys); + localAffrows += ret.Count; + foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true); + if (cascade == false) return ret; + + foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) + { + var tbref = tr.Value; + if (tbref.Exception != null) continue; + if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; + var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName); + if (boundaryAttr?.Break == true) continue; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + var otoList = ret.Select(entity => + { + var otoItem = table.GetPropertyValue(entity, prop.Name); + if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null; + AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem); + return otoItem; + }).Where(entity => entity != null).ToArray(); + if (otoList.Any()) + { + var repo = getChildRepository(tbref.RefEntityType); + LocalInsert(repo, otoList, boundaryAttr?.BreakThen != true); + } + break; + case TableRefType.OneToMany: + var otmList = ret.Select(entity => + { + var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable; + if (otmEach == null) return null; + var otmItems = new List(); + foreach (var otmItem in otmEach) + { + if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue; + otmItems.Add(otmItem); + } + AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems); + return otmItems; + }).Where(entity => entity != null).SelectMany(entity => entity).ToArray(); + if (otmList.Any()) + { + var repo = getChildRepository(tbref.RefEntityType); + LocalInsert(repo, otmList, boundaryAttr?.BreakThen != true); + } + break; + case TableRefType.ManyToMany: + var mtmMidList = new List(); + ret.ForEach(entity => + { + var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop); + if (mids != null) mtmMidList.AddRange(mids); + }); + if (mtmMidList.Any()) + { + var repo = getChildRepository(tbref.RefMiddleEntityType); + LocalInsert(repo, mtmMidList, false); + } + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 + break; + } + } + return ret; + } + } + #endregion + + public virtual TEntity InsertOrUpdate(TEntity entity) + { + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (entity == null) throw new ArgumentNullException(nameof(entity)); + var table = Orm.CodeFirst.GetTableByEntity(EntityType); + if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity))); + + var flagExists = ExistsInStates(entity); + if (flagExists == false) + { + var olddata = Select.WhereDynamic(entity).First(); + flagExists = olddata != null; + } + if (flagExists == true) + { + var affrows = Update(entity); + if (affrows > 0) return entity; + } + if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length) + { + Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity); + return Insert(entity); + } + throw new Exception(DbContextStrings.CannotAdd_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, entity))); + } + + public virtual int Update(TEntity entity) => Update(new[] { entity }); + public virtual int Update(IEnumerable entitys) + { + var tracking = new AggregateRootTrackingChangeInfo(); + foreach(var entity in entitys) + { + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}"); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking); + } + foreach (var entity in entitys) + Attach(entity); + + return SaveTrackingChange(tracking); + } + + public virtual int Delete(TEntity entity) => DeleteWithinBoundary(new[] { entity }, null); + public virtual int Delete(IEnumerable entitys) => DeleteWithinBoundary(entitys, null); + public virtual int Delete(Expression> predicate) => DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), null); + public virtual List DeleteCascadeByDatabase(Expression> predicate) + { + var deletedOutput = new List(); + DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), deletedOutput); + return deletedOutput; + } + int DeleteWithinBoundary(IEnumerable entitys, List deletedOutput) + { + var tracking = new AggregateRootTrackingChangeInfo(); + foreach (var entity in entitys) + { + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking); + _states.Remove(stateKey); + } + var affrows = 0; + for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--) + { + affrows += Orm.Delete().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule) + .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows(); + if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2); + UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x => + new DbContext.EntityChangeReport.ChangeInfo + { + Type = DbContext.EntityChangeType.Delete, + EntityType = tracking.DeleteLog[a].Item1, + Object = x + })); + } + return affrows; + } + + public virtual void SaveMany(TEntity entity, string propertyName) + { + var tracking = new AggregateRootTrackingChangeInfo(); + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}"); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking); + Attach(entity); //应该只存储 propertyName 内容 + SaveTrackingChange(tracking); + } + + + int SaveTrackingChange(AggregateRootTrackingChangeInfo tracking) + { + var affrows = 0; + DisposeChildRepositorys(); + var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray()); + foreach (var il in insertLogDict) + { + var repo = GetChildRepository(il.Key); + InsertWithinBoundaryStatic(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut); + affrows += affrowsOut; + } + + for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--) + { + affrows += Orm.Delete().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule) + .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows(); + UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x => + new DbContext.EntityChangeReport.ChangeInfo + { + Type = DbContext.EntityChangeType.Delete, + EntityType = tracking.DeleteLog[a].Item1, + Object = x + })); + } + + var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => new + { + BeforeObject = b.Item2, + AfterObject = b.Item3, + UpdateColumns = b.Item4, + UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c)) + }).ToArray()); + var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.UpdateColumnsString, b => a.Value.Where(c => c.UpdateColumnsString == b.UpdateColumnsString).ToArray())); + foreach (var dl in updateLogDict2) + { + foreach (var dl2 in dl.Value) + { + affrows += Orm.Update().AsType(dl.Key).AsTable(_asTableRule) + .SetSource(dl2.Value.Select(a => a.AfterObject).ToArray()) + .UpdateColumns(dl2.Value.First().UpdateColumns.ToArray()) + .ExecuteAffrows(); + UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x => + new DbContext.EntityChangeReport.ChangeInfo + { + Type = DbContext.EntityChangeType.Update, + EntityType = dl.Key, + Object = x.AfterObject, + BeforeObject = x.BeforeObject + })); + } + } + DisposeChildRepositorys(); + return affrows; + } + } +} diff --git a/FreeSql.DbContext/AggregateRootRepository/AggregateRootUtils.cs b/FreeSql.DbContext/AggregateRootRepository/AggregateRootUtils.cs new file mode 100644 index 00000000..3f09558c --- /dev/null +++ b/FreeSql.DbContext/AggregateRootRepository/AggregateRootUtils.cs @@ -0,0 +1,652 @@ +using FreeSql; +using FreeSql.DataAnnotations; +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal; +using FreeSql.Internal.CommonProvider; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace FreeSql +{ + public class AggregateRootUtils + { + static ConcurrentDictionary> _dicGetPropertyBoundaryAttribute = new ConcurrentDictionary>(); + public static AggregateRootBoundaryAttribute GetPropertyBoundaryAttribute(PropertyInfo prop, string boundaryName) + { + if (boundaryName == null) return null; + return _dicGetPropertyBoundaryAttribute.GetOrAdd(prop, tp => new ConcurrentDictionary()) + .GetOrAdd(boundaryName, bn => + { + var attrs = prop.GetCustomAttributes(typeof(AggregateRootBoundaryAttribute), false); + if (attrs == null || attrs.Any() == false) return null; + return attrs.Select(a => a as AggregateRootBoundaryAttribute).Where(a => a.Name == bn).FirstOrDefault(); + }); + } + + public static void CompareEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityBefore, object rootEntityAfter, string rootNavigatePropertyName, AggregateRootTrackingChangeInfo tracking) + { + Dictionary> ignores = new Dictionary>(); + LocalCompareEntityValue(rootEntityType, rootEntityBefore, rootEntityAfter, rootNavigatePropertyName, true); + ignores.Clear(); + + void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName, bool cascade) + { + if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType(); + + if (entityBefore != null) + { + var stateKey = $":before://{fsql.GetEntityKeyString(entityType, entityBefore, false)}"; + if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary()); + if (stateKeys.ContainsKey(stateKey)) return; + stateKeys.Add(stateKey, true); + } + if (entityAfter != null) + { + var stateKey = $":after://{fsql.GetEntityKeyString(entityType, entityAfter, false)}"; + if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary()); + if (stateKeys.ContainsKey(stateKey)) return; + stateKeys.Add(stateKey, true); + } + + var table = fsql.CodeFirst.GetTableByEntity(entityType); + if (table == null) return; + if (entityBefore == null && entityAfter == null) return; + if (entityBefore == null && entityAfter != null) + { + tracking.InsertLog.Add(NativeTuple.Create(entityType, entityAfter)); + return; + } + if (entityBefore != null && entityAfter == null) + { + tracking.DeleteLog.Add(NativeTuple.Create(entityType, new[] { entityBefore })); + NavigateReader(boundaryName, fsql, entityType, entityBefore, (path, tr, ct, stackvs) => + { + var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() }; + tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist)); + }); + return; + } + var changes = new List(); + foreach (var col in table.ColumnsByCs.Values) + { + if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue; + if (table.ColumnsByCs.ContainsKey(col.CsName)) + { + if (col.Attribute.IsVersion) continue; + var propvalBefore = table.GetPropertyValue(entityBefore, col.CsName); + var propvalAfter = table.GetPropertyValue(entityAfter, col.CsName); + //if (object.Equals(propvalBefore, propvalAfter) == false) changes.Add(col.CsName); + if (CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) changes.Add(col.CsName); + continue; + } + } + if (changes.Any()) tracking.UpdateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes)); + if (cascade == false) return; + + foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) + { + var tbref = tr.Value; + if (tbref.Exception != null) continue; + if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; + var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName); + if (boundaryAttr?.Break == true) continue; + if (navigatePropertyName != null && prop.Name != navigatePropertyName) continue; + var propvalBefore = table.GetPropertyValue(entityBefore, prop.Name); + var propvalAfter = table.GetPropertyValue(entityAfter, prop.Name); + switch (tbref.RefType) + { + case TableRefType.OneToOne: + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore); + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter); + LocalCompareEntityValue(tbref.RefEntityType, propvalBefore, propvalAfter, null, boundaryAttr?.BreakThen != true); + break; + case TableRefType.OneToMany: + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore); + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter); + LocalCompareEntityValueCollection(tbref.RefEntityType, propvalBefore as IEnumerable, propvalAfter as IEnumerable, boundaryAttr?.BreakThen != true); + break; + case TableRefType.ManyToMany: + var middleValuesBefore = GetManyToManyObjects(fsql, table, tbref, entityBefore, prop); + var middleValuesAfter = GetManyToManyObjects(fsql, table, tbref, entityAfter, prop); + LocalCompareEntityValueCollection(tbref.RefMiddleEntityType, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable, false); + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 + break; + } + } + } + void LocalCompareEntityValueCollection(Type elementType, IEnumerable collectionBefore, IEnumerable collectionAfter, bool cascade) + { + if (collectionBefore == null && collectionAfter == null) return; + if (collectionBefore == null && collectionAfter != null) + { + foreach (var item in collectionAfter) + tracking.InsertLog.Add(NativeTuple.Create(elementType, item)); + return; + } + if (collectionBefore != null && collectionAfter == null) + { + //foreach (var item in collectionBefore as IEnumerable) + //{ + // changelog.DeleteLog.Add(NativeTuple.Create(elementType, new[] { item })); + // NavigateReader(boundaryName, fsql, elementType, item, (path, tr, ct, stackvs) => + // { + // var dellist = stackvs.Last() as object[] ?? new [] { stackvs.Last() }; + // changelog.DeleteLog.Add(NativeTuple.Create(ct, dellist)); + // }); + //} + return; + } + Dictionary dictBefore = new Dictionary(); + Dictionary dictAfter = new Dictionary(); + foreach (var item in collectionBefore as IEnumerable) + { + var key = fsql.GetEntityKeyString(elementType, item, false); + if (key != null) dictBefore.Add(key, item); + } + foreach (var item in collectionAfter as IEnumerable) + { + var key = fsql.GetEntityKeyString(elementType, item, false); + if (key != null) + { + if (dictAfter.ContainsKey(key) == false) + dictAfter.Add(key, item); + } + else tracking.InsertLog.Add(NativeTuple.Create(elementType, item)); + } + foreach (var key in dictBefore.Keys.ToArray()) + { + if (dictAfter.ContainsKey(key) == false) + { + var value = dictBefore[key]; + tracking.DeleteLog.Add(NativeTuple.Create(elementType, new[] { value })); + NavigateReader(boundaryName, fsql, elementType, value, (path, tr, ct, stackvs) => + { + var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() }; + tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist)); + }); + dictBefore.Remove(key); + } + } + foreach (var key in dictAfter.Keys.ToArray()) + { + if (dictBefore.ContainsKey(key) == false) + { + tracking.InsertLog.Add(NativeTuple.Create(elementType, dictAfter[key])); + dictAfter.Remove(key); + } + } + foreach (var key in dictBefore.Keys) + LocalCompareEntityValue(elementType, dictBefore[key], dictAfter[key], null, cascade); + } + } + + static ConcurrentDictionary _dicCompareEntityPropertyValue = new ConcurrentDictionary + { + [typeof(string)] = true, + [typeof(DateTime)] = true, + [typeof(DateTime?)] = true, + [typeof(DateTimeOffset)] = true, + [typeof(DateTimeOffset?)] = true, + [typeof(TimeSpan)] = true, + [typeof(TimeSpan?)] = true, + }; + public static bool CompareEntityPropertyValue(Type type, object propvalBefore, object propvalAfter) + { + if (propvalBefore == null && propvalAfter == null) return true; + if (type.IsNumberType() || + _dicCompareEntityPropertyValue.ContainsKey(type) || + type.IsEnum || + type.IsValueType || + type.NullableTypeOrThis().IsEnum) return object.Equals(propvalBefore, propvalAfter); + if (propvalBefore == null && propvalAfter != null) return false; + if (propvalBefore != null && propvalAfter == null) return false; + + if (FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(type)) { + if (type.FullName.StartsWith("Newtonsoft.")) + return object.Equals(propvalBefore.ToString(), propvalAfter.ToString()); + + if (typeof(IDictionary).IsAssignableFrom(type)) + { + var dictBefore = (propvalBefore as IDictionary); + var dictAfter = (propvalAfter as IDictionary); + if (dictBefore.Count != dictAfter.Count) return false; + foreach (var key in dictBefore.Keys) + { + if (dictAfter.Contains(key) == false) return false; + var valBefore = dictBefore[key]; + var valAfter = dictAfter[key]; + if (valBefore == null && valAfter == null) continue; + if (valBefore == null && valAfter != null) return false; + if (valBefore != null && valAfter == null) return false; + if (CompareEntityPropertyValue(valBefore.GetType(), valBefore, valAfter) == false) return false; + } + return true; + } + + if (type.IsArrayOrList()) + { + var enumableBefore = propvalBefore as IEnumerable; + var enumableAfter = propvalAfter as IEnumerable; + var itorBefore = enumableBefore.GetEnumerator(); + var itorAfter = enumableAfter.GetEnumerator(); + while (true) + { + var moveNextBefore = itorBefore.MoveNext(); + var moveNextAfter = itorAfter.MoveNext(); + if (moveNextBefore != moveNextAfter) return false; + if (moveNextBefore == false) break; + var currentBefore = itorBefore.Current; + var currentAfter = itorAfter.Current; + if (currentBefore == null && enumableAfter == null) continue; + if (currentBefore == null && currentAfter != null) return false; + if (currentBefore != null && currentAfter == null) return false; + if (CompareEntityPropertyValue(currentBefore.GetType(), currentBefore, currentAfter) == false) return false; + } + return true; + } + + if (type.FullName.StartsWith("System.") || + type.FullName.StartsWith("Npgsql.") || + type.FullName.StartsWith("NetTopologySuite.")) + return object.Equals(propvalBefore, propvalAfter); + + if (type.IsClass) + { + foreach (var prop in type.GetProperties()) + { + var valBefore = prop.GetValue(propvalBefore, new object[0]); + var valAfter = prop.GetValue(propvalAfter, new object[0]); + if (CompareEntityPropertyValue(prop.PropertyType, valBefore, valAfter) == false) return false; + } + return true; + } + } + return object.Equals(propvalBefore, propvalAfter); + } + + public static void NavigateReader(string boundaryName, IFreeSql fsql, Type rootType, object rootEntity, Action> callback) + { + Dictionary> ignores = new Dictionary>(); + var statckPath = new Stack(); + var stackValues = new List(); + statckPath.Push("_"); + stackValues.Add(rootEntity); + LocalNavigateReader(rootType, rootEntity); + ignores.Clear(); + + void LocalNavigateReader(Type entityType, object entity) + { + if (entity == null) return; + if (entityType == null) entityType = entity.GetType(); + var table = fsql.CodeFirst.GetTableByEntity(entityType); + if (table == null) return; + + var stateKey = fsql.GetEntityKeyString(entityType, entity, false); + if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary()); + if (stateKeys.ContainsKey(stateKey)) return; + stateKeys.Add(stateKey, true); + + foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) + { + var tbref = tr.Value; + if (tbref.Exception != null) continue; + if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; + var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName); + if (boundaryAttr?.Break == true) continue; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + var propval = table.GetPropertyValue(entity, prop.Name); + if (propval == null) continue; + statckPath.Push(prop.Name); + stackValues.Add(propval); + SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propval); + callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); + if (boundaryAttr?.BreakThen != true) + LocalNavigateReader(tbref.RefEntityType, propval); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + break; + case TableRefType.OneToMany: + var propvalOtm = table.GetPropertyValue(entity, prop.Name) as IEnumerable; + if (propvalOtm == null) continue; + SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propvalOtm); + var propvalOtmList = new List(); + foreach (var val in propvalOtm) + propvalOtmList.Add(val); + statckPath.Push($"{prop.Name}[]"); + stackValues.Add(propvalOtmList.ToArray()); + callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); + if (boundaryAttr?.BreakThen != true) + foreach (var val in propvalOtm) + LocalNavigateReader(tbref.RefEntityType, val); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + break; + case TableRefType.ManyToMany: + var middleValues = GetManyToManyObjects(fsql, table, tbref, entity, prop).ToArray(); + if (middleValues == null) continue; + statckPath.Push($"{prop.Name}[]"); + stackValues.Add(middleValues); + callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefMiddleEntityType, stackValues); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 + break; + } + } + } + } + + public static void MapEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo) + { + Dictionary> ignores = new Dictionary>(); + LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo, true); + ignores.Clear(); + + void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo, bool cascade) + { + if (entityFrom == null || entityTo == null) return; + if (entityType == null) entityType = entityFrom?.GetType() ?? entityTo?.GetType(); + var table = fsql.CodeFirst.GetTableByEntity(entityType); + if (table == null) return; + + var stateKey = fsql.GetEntityKeyString(entityType, entityFrom, false); + if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary()); + if (stateKeys.ContainsKey(stateKey)) return; + stateKeys.Add(stateKey, true); + + foreach (var prop in table.Properties.Values) + { + if (table.ColumnsByCsIgnore.ContainsKey(prop.Name)) continue; + if (table.ColumnsByCs.ContainsKey(prop.Name)) + { + table.SetPropertyValue(entityTo, prop.Name, table.GetPropertyValue(entityFrom, prop.Name)); + continue; + } + if (cascade == false) continue; + var tbref = table.GetTableRef(prop.Name, false); + if (tbref == null) continue; + var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName); + if (boundaryAttr?.Break == true) continue; + var propvalFrom = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, entityType, entityFrom, prop.Name); + if (propvalFrom == null) + { + EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, null); + return; + } + switch (tbref.RefType) + { + case TableRefType.OneToOne: + var propvalTo = tbref.RefEntityType.CreateInstanceGetDefaultValue(); + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom); + LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo, boundaryAttr?.BreakThen != true); + EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo); + break; + case TableRefType.OneToMany: + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom); + LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, boundaryAttr?.BreakThen != true); + break; + case TableRefType.ManyToMany: + LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, false); + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 + break; + } + } + } + void LocalMapEntityValueCollection(Type entityType, object entityFrom, object entityTo, TableRef tbref, IEnumerable propvalFrom, PropertyInfo prop, bool cascade) + { + var propvalTo = typeof(List<>).MakeGenericType(tbref.RefEntityType).CreateInstanceGetDefaultValue(); + var propvalToIList = propvalTo as IList; + foreach (var fromItem in propvalFrom) + { + var toItem = tbref.RefEntityType.CreateInstanceGetDefaultValue(); + LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem, cascade); + propvalToIList.Add(toItem); + } + var propvalType = prop.PropertyType.GetGenericTypeDefinition(); + if (propvalType == typeof(List<>) || propvalType == typeof(ICollection<>)) + EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo); + else if (propvalType == typeof(ObservableCollection<>)) + { + //var propvalTypeOcCtor = typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType).GetConstructor(new[] { typeof(List<>).MakeGenericType(tbref.RefEntityType) }); + var propvalTypeOc = Activator.CreateInstance(typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType), new object[] { propvalTo }); + EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTypeOc); + } + } + } + + static ConcurrentDictionary>>> _dicGetAutoIncludeQuery = new ConcurrentDictionary>>>(); + public static ISelect GetAutoIncludeQuery(string boundaryName, ISelect select) + { + var select0p = select as Select0Provider; + var table0Type = select0p._tables[0].Table.Type; + var func = _dicGetAutoIncludeQuery.GetOrAdd(boundaryName ?? "", bn => new ConcurrentDictionary>>()) + .GetOrAdd(typeof(TEntity), t => new ConcurrentDictionary>()) + .GetOrAdd(table0Type, t => + { + var parmExp1 = Expression.Parameter(typeof(ISelect0)); + var parmNavigateParameterExp = Expression.Parameter(typeof(TEntity), "a"); + var parmQueryExp = Expression.Convert(parmExp1, typeof(ISelect<>).MakeGenericType(typeof(TEntity))); + var exp = LocalGetAutoIncludeQuery(parmQueryExp, 1, t, parmNavigateParameterExp, parmNavigateParameterExp, new Stack()); + return Expression.Lambda>(exp, parmExp1).Compile(); + }); + func(select); + return select; + Expression LocalGetAutoIncludeQuery(Expression queryExp, int depth, Type entityType, ParameterExpression navigateParameterExp, Expression navigatePathExp, Stack ignores) + { + if (ignores.Any(a => a == entityType)) return queryExp; + ignores.Push(entityType); + var table = select0p._orm.CodeFirst.GetTableByEntity(entityType); + if (table == null) return queryExp; + foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) + { + var tbref = tr.Value; + if (tbref.Exception != null) continue; + if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; + var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName); + if (boundaryAttr?.Break == true) continue; + Expression navigateExp = Expression.MakeMemberAccess(navigatePathExp, prop); + //var lambdaAlias = (char)((byte)'a' + (depth - 1)); + switch (tbref.RefType) + { + case TableRefType.OneToOne: + if (ignores.Any(a => a == tbref.RefEntityType)) break; + LocalInclude(tbref, navigateExp); + if (boundaryAttr?.BreakThen != true) + queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores); + break; + case TableRefType.OneToMany: + LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen != true); + break; + case TableRefType.ManyToMany: + LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false); + break; + case TableRefType.PgArrayToMany: + if (boundaryAttr?.Break == false) + LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false); + break; + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 + if (boundaryAttr?.Break == false) + { + LocalInclude(tbref, navigateExp); + if (boundaryAttr?.BreakThen == false) + queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores); + } + break; + } + } + ignores.Pop(); + return queryExp; + void LocalInclude(TableRef tbref, Expression exp) + { + var incMethod = queryExp.Type.GetMethod("Include"); + if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany.Replace("IncludeMany", "Include")); + queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType), + Expression.Lambda(typeof(Func<,>).MakeGenericType(entityType, tbref.RefEntityType), exp, navigateParameterExp)); + } + void LocalIncludeMany(TableRef tbref, Expression exp, bool isthen) + { + var funcType = typeof(Func<,>).MakeGenericType(entityType, typeof(IEnumerable<>).MakeGenericType(tbref.RefEntityType)); + var navigateSelector = Expression.Lambda(funcType, exp, navigateParameterExp); + var incMethod = queryExp.Type.GetMethod("IncludeMany"); + if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany); + LambdaExpression navigateThen = null; + var navigateThenType = typeof(Action<>).MakeGenericType(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType)); + var thenParameter = Expression.Parameter(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType), "then"); + Expression paramQueryExp = thenParameter; + var paramNavigateParameterExp = Expression.Parameter(tbref.RefEntityType, string.Concat((char)((byte)'a' + (depth - 1)))); + if (isthen) paramQueryExp = LocalGetAutoIncludeQuery(paramQueryExp, depth + 1, tbref.RefEntityType, paramNavigateParameterExp, paramNavigateParameterExp, ignores); + navigateThen = Expression.Lambda(navigateThenType, paramQueryExp, thenParameter); + queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType), navigateSelector, navigateThen); + } + } + } + + public static string GetAutoIncludeQueryStaicCode(string boundaryName, IFreeSql fsql, Type rootEntityType) + { + return $"//fsql.Select<{rootEntityType.Name}>()\r\nSelectDiy{LocalGetAutoIncludeQueryStaicCode(1, rootEntityType, "", new Stack())}"; + string LocalGetAutoIncludeQueryStaicCode(int depth, Type entityType, string navigatePath, Stack ignores) + { + var code = new StringBuilder(); + if (ignores.Any(a => a == entityType)) return null; + ignores.Push(entityType); + var table = fsql.CodeFirst.GetTableByEntity(entityType); + if (table == null) return null; + if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}."; + foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) + { + var tbref = tr.Value; + if (tbref.Exception != null) continue; + if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; + var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName); + if (boundaryAttr?.Break == true) continue; + var navigateExpression = $"{navigatePath}{tr.Key}"; + var depthTab = "".PadLeft(depth * 4); + var lambdaAlias = (char)((byte)'a' + (depth - 1)); + var lambdaStr = $"{lambdaAlias} => {lambdaAlias}."; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + if (ignores.Any(a => a == tbref.RefEntityType)) break; + code.Append("\r\n").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")"); + if (boundaryAttr?.BreakThen != true) + code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores)); + break; + case TableRefType.OneToMany: + code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression); + if (boundaryAttr?.BreakThen != true) + { + var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack(ignores.ToArray())); + if (thencode.Length > 0) code.Append(", then => then").Append(thencode); + } + code.Append(")"); + break; + case TableRefType.ManyToMany: + code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression); + if (boundaryAttr?.BreakThen == false) + { + var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack(ignores.ToArray())); + if (thencode.Length > 0) code.Append(", then => then").Append(thencode); + } + code.Append(")"); + break; + case TableRefType.PgArrayToMany: + code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression); + if (boundaryAttr?.BreakThen == false) + { + var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack(ignores.ToArray())); + if (thencode.Length > 0) code.Append(", then => then").Append(thencode); + } + code.Append(")"); + break; + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 + code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")"); + if (boundaryAttr?.BreakThen == false) + code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores)); + break; + } + } + ignores.Pop(); + return code.ToString(); + } + } + + public static List GetManyToManyObjects(IFreeSql fsql, TableInfo table, TableRef tbref, object entity, PropertyInfo prop) + { + if (tbref.RefType != TableRefType.ManyToMany) return null; + var rights = table.GetPropertyValue(entity, prop.Name) as IEnumerable; + if (rights == null) return null; + var middles = new List(); + var leftpkvals = new object[tbref.Columns.Count]; + for (var x = 0; x < tbref.Columns.Count; x++) + leftpkvals[x] = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, table.Type, entity, tbref.Columns[x].CsName)); + foreach (var right in rights) + { + var midval = tbref.RefMiddleEntityType.CreateInstanceGetDefaultValue(); + for (var x = 0; x < tbref.Columns.Count; x++) + EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, leftpkvals[x]); + + for (var x = tbref.Columns.Count; x < tbref.MiddleColumns.Count; x++) + { + var refcol = tbref.RefColumns[x - tbref.Columns.Count]; + var refval = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, tbref.RefEntityType, right, refcol.CsName); + if (refval == refcol.CsType.CreateInstanceGetDefaultValue()) throw new Exception($"ManyToMany 关联对象的主键属性({tbref.RefEntityType.DisplayCsharp()}.{refcol.CsName})不能为空"); + refval = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, refval); + EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, refval); + } + middles.Add(midval); + } + return middles; + } + + public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem) + { + switch (tbref.RefType) + { + case TableRefType.OneToOne: + if (rightItem == null) return; + for (var idx = 0; idx < tbref.Columns.Count; idx++) + { + var colval = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName)); + EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName, colval); + } + break; + case TableRefType.OneToMany: + if (rightItem == null) return; + var rightEachOtm = rightItem as IEnumerable; + if (rightEachOtm == null) break; + var leftColValsOtm = new object[tbref.Columns.Count]; + for (var idx = 0; idx < tbref.Columns.Count; idx++) + leftColValsOtm[idx] = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName)); + foreach (var rightEle in rightEachOtm) + for (var idx = 0; idx < tbref.Columns.Count; idx++) + EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightEle, tbref.RefColumns[idx].CsName, leftColValsOtm[idx]); + break; + case TableRefType.ManyToOne: + for (var idx = 0; idx < tbref.RefColumns.Count; idx++) + { + var colval = rightItem == null ? + tbref.Columns[idx].CsType.CreateInstanceGetDefaultValue() : + Utils.GetDataReaderValue(tbref.Columns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName)); + EntityUtilExtensions.SetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName, colval); + } + break; + } + } + } +} \ No newline at end of file diff --git a/FreeSql.DbContext/AggregateRootRepository/FreeSqlRepositoryExtensions.cs b/FreeSql.DbContext/AggregateRootRepository/FreeSqlRepositoryExtensions.cs new file mode 100644 index 00000000..0f5da722 --- /dev/null +++ b/FreeSql.DbContext/AggregateRootRepository/FreeSqlRepositoryExtensions.cs @@ -0,0 +1,12 @@ +using FreeSql; +using System; +using System.Linq; +using System.Linq.Expressions; + +public static class FreeSqlAggregateRootRepositoryGlobalExtensions +{ + public static IBaseRepository GetAggregateRootRepository(this IFreeSql that) where TEntity : class + { + return new AggregateRootRepository(that); + } +} \ No newline at end of file diff --git a/FreeSql.DbContext/FreeSql - Backup.DbContext.csproj b/FreeSql.DbContext/FreeSql - Backup.DbContext.csproj new file mode 100644 index 00000000..1e45ee9e --- /dev/null +++ b/FreeSql.DbContext/FreeSql - Backup.DbContext.csproj @@ -0,0 +1,84 @@ + + + + netstandard2.0;net60;net50;netcoreapp31;netcoreapp21;net45;net40 + true + FreeSql;ncc;YeXiangQin + FreeSql is the ORM in .NetCore, .NetFramework, And Xamarin. It supports Mysql, Postgresql, SqlServer, Oracle, Sqlite, Firebird, Odbc, 达梦, 人大金仓, 神舟通用, 南大通用, 翰高, And Access + https://github.com/2881099/FreeSql/wiki/DbContext + FreeSql ORM DbContext + git + MIT + $(AssemblyName) + logo.png + $(AssemblyName) + true + true + true + key.snk + false + 3.2.667 + + + + + + + + FreeSql.DbContext.xml + 3 + 1701;1702;1591 + + + + net40 + + + netcoreapp + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + DbContextStrings.Designer.tt + + + + + + TextTemplatingFileGenerator + DbContextStrings.Designer.cs + + + Designer + FreeSql + + + FreeSql + + + + diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index 594fbad3..1fe1062e 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -4,6 +4,50 @@ FreeSql.DbContext + + + 设置 AggregateRootRepository 边界范围 + 在边界范围之内的规则 : + 1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改 + 2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略) + + + + + 边界是否终止 + + + + + 边界是否终止向下探测 + + + + + 默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性) + 重写:使用 + + + + + 创建查询对象(纯净) + _ + 聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发 + string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order)) + + + + + 创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性) + + + + + + ISelect.TrackToList 委托,数据返回后自动 Attach + + + 该对象 Select/Delete/Insert/Update/InsertOrUpdate 与 DbContext 事务保持一致,可省略传递 WithTransaction @@ -733,6 +777,15 @@ + + + 根据Assembly扫描所有继承IEntityTypeConfiguration<T>的配置类 + + + + + + 创建普通数据上下文档对象 @@ -791,5 +844,14 @@ + + + 批量注入 Repository,可以参考代码自行调整 + + + + + + diff --git a/FreeSql.Repository/FreeSql.Repository.csproj b/FreeSql.Repository/FreeSql.Repository.csproj index 2692cf92..0eed492f 100644 --- a/FreeSql.Repository/FreeSql.Repository.csproj +++ b/FreeSql.Repository/FreeSql.Repository.csproj @@ -17,7 +17,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/FreeSql.Tests/FreeSql.Tests/Issues/1237.cs b/FreeSql.Tests/FreeSql.Tests/Issues/1237.cs new file mode 100644 index 00000000..bacd83be --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/Issues/1237.cs @@ -0,0 +1,66 @@ +using FreeSql.DataAnnotations; +using Xunit; + +namespace FreeSql.Tests.Issues +{ + public class _1237 + { + [Fact] + public void WithTempQuery() + { + var fsql = g.sqlite; + var people1 = fsql.Select() + .GroupBy(x => x.Name.Replace(" ", "").Replace(" ", "")) + .Having(x => x.Count() > 1) + .WithTempQuery(x => new { xm = x.Key }) + .From() + .InnerJoin((a, b) => a.xm == b.Name.Replace(" ", "").Replace(" ", "")) + .OrderBy((a, b) => b.Name) + .OrderBy((a, b) => b.ID) + .ToSql(); + Assert.Equal(@"SELECT * +FROM ( + SELECT replace(replace(a.""Name"", ' ', ''), ' ', '') ""xm"" + FROM ""people_issues_1237"" a + GROUP BY replace(replace(a.""Name"", ' ', ''), ' ', '') + HAVING (count(1) > 1) ) a +INNER JOIN ""people_issues_1237"" b ON a.""xm"" = replace(replace(b.""Name"", ' ', ''), ' ', '') +ORDER BY b.""Name"", b.""ID""", people1); + + var people2 = fsql.Select() + .GroupBy(x => new { xm_new = x.Name.Replace(" ", "").Replace(" ", ""), csny = x.CSNY }) + .Having(x => x.Count() > 1).WithTempQuery(x => new { xm = x.Key.xm_new, csny = x.Key.csny }) + .From() + .InnerJoin((a, b) => a.xm == b.Name.Replace(" ", "").Replace(" ", "") && a.csny == b.CSNY) + .OrderBy((a, b) => b.Name).OrderBy((a, b) => b.ID) + .ToSql(); + Assert.Equal(@"SELECT * +FROM ( + SELECT replace(replace(a.""Name"", ' ', ''), ' ', ''), a.""CSNY"" + FROM ""people_issues_1237"" a + GROUP BY replace(replace(a.""Name"", ' ', ''), ' ', ''), a.""CSNY"" + HAVING (count(1) > 1) ) a +INNER JOIN ""people_issues_1237"" b ON a.""xm_new"" = replace(replace(b.""Name"", ' ', ''), ' ', '') AND a.""csny"" = b.""CSNY"" +ORDER BY b.""Name"", b.""ID""", people2); + + } + + [Table(Name = "people_issues_1237")] + public partial class people + { + + [Column(IsPrimary = true, IsIdentity = true)] + public int ID { get; set; } + + [Column(DbType = "varchar(255)")] + public string Name { get; set; } + + [Column(DbType = "varchar(255)")] + public string CSNY { get; set; } + + [Column(DbType = "varchar(255)")] + public string Sex { get; set; } + } + } + +} diff --git a/FreeSql/FreeSql.csproj b/FreeSql/FreeSql.csproj index eff80556..eb8a9a2f 100644 --- a/FreeSql/FreeSql.csproj +++ b/FreeSql/FreeSql.csproj @@ -17,7 +17,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj b/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj index 2de9bcfa..e8f74b42 100644 --- a/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj +++ b/Providers/FreeSql.Provider.ClickHouse/FreeSql.Provider.ClickHouse.csproj @@ -19,7 +19,7 @@ False key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.Custom/FreeSql.Provider.Custom.csproj b/Providers/FreeSql.Provider.Custom/FreeSql.Provider.Custom.csproj index 929c371c..74751917 100644 --- a/Providers/FreeSql.Provider.Custom/FreeSql.Provider.Custom.csproj +++ b/Providers/FreeSql.Provider.Custom/FreeSql.Provider.Custom.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.Dameng/FreeSql.Provider.Dameng.csproj b/Providers/FreeSql.Provider.Dameng/FreeSql.Provider.Dameng.csproj index 463a2386..b9d7c21f 100644 --- a/Providers/FreeSql.Provider.Dameng/FreeSql.Provider.Dameng.csproj +++ b/Providers/FreeSql.Provider.Dameng/FreeSql.Provider.Dameng.csproj @@ -15,7 +15,7 @@ $(AssemblyName) true true - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.Firebird/FreeSql.Provider.Firebird.csproj b/Providers/FreeSql.Provider.Firebird/FreeSql.Provider.Firebird.csproj index 88c8950c..4a6aec2e 100644 --- a/Providers/FreeSql.Provider.Firebird/FreeSql.Provider.Firebird.csproj +++ b/Providers/FreeSql.Provider.Firebird/FreeSql.Provider.Firebird.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.GBase/FreeSql.Provider.GBase.csproj b/Providers/FreeSql.Provider.GBase/FreeSql.Provider.GBase.csproj index ae4fa155..94493707 100644 --- a/Providers/FreeSql.Provider.GBase/FreeSql.Provider.GBase.csproj +++ b/Providers/FreeSql.Provider.GBase/FreeSql.Provider.GBase.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.KingbaseES/FreeSql.Provider.KingbaseES.csproj b/Providers/FreeSql.Provider.KingbaseES/FreeSql.Provider.KingbaseES.csproj index cd2fc5c6..ac168afe 100644 --- a/Providers/FreeSql.Provider.KingbaseES/FreeSql.Provider.KingbaseES.csproj +++ b/Providers/FreeSql.Provider.KingbaseES/FreeSql.Provider.KingbaseES.csproj @@ -15,7 +15,7 @@ $(AssemblyName) true true - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.MsAccess/FreeSql.Provider.MsAccess.csproj b/Providers/FreeSql.Provider.MsAccess/FreeSql.Provider.MsAccess.csproj index 5297256f..2d7d2429 100644 --- a/Providers/FreeSql.Provider.MsAccess/FreeSql.Provider.MsAccess.csproj +++ b/Providers/FreeSql.Provider.MsAccess/FreeSql.Provider.MsAccess.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.MySql/FreeSql.Provider.MySql.csproj b/Providers/FreeSql.Provider.MySql/FreeSql.Provider.MySql.csproj index a7245414..cd60e7cc 100644 --- a/Providers/FreeSql.Provider.MySql/FreeSql.Provider.MySql.csproj +++ b/Providers/FreeSql.Provider.MySql/FreeSql.Provider.MySql.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.MySqlConnector/FreeSql.Provider.MySqlConnector.csproj b/Providers/FreeSql.Provider.MySqlConnector/FreeSql.Provider.MySqlConnector.csproj index 875aa514..0420eec3 100644 --- a/Providers/FreeSql.Provider.MySqlConnector/FreeSql.Provider.MySqlConnector.csproj +++ b/Providers/FreeSql.Provider.MySqlConnector/FreeSql.Provider.MySqlConnector.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.Odbc/FreeSql.Provider.Odbc.csproj b/Providers/FreeSql.Provider.Odbc/FreeSql.Provider.Odbc.csproj index a15bcac3..c5e093ba 100644 --- a/Providers/FreeSql.Provider.Odbc/FreeSql.Provider.Odbc.csproj +++ b/Providers/FreeSql.Provider.Odbc/FreeSql.Provider.Odbc.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.Oracle/FreeSql.Provider.Oracle.csproj b/Providers/FreeSql.Provider.Oracle/FreeSql.Provider.Oracle.csproj index 26d49a4f..d34a4373 100644 --- a/Providers/FreeSql.Provider.Oracle/FreeSql.Provider.Oracle.csproj +++ b/Providers/FreeSql.Provider.Oracle/FreeSql.Provider.Oracle.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.OracleOledb/FreeSql.Provider.OracleOledb.csproj b/Providers/FreeSql.Provider.OracleOledb/FreeSql.Provider.OracleOledb.csproj index 6890df3f..9429f16f 100644 --- a/Providers/FreeSql.Provider.OracleOledb/FreeSql.Provider.OracleOledb.csproj +++ b/Providers/FreeSql.Provider.OracleOledb/FreeSql.Provider.OracleOledb.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.PostgreSQL/FreeSql.Provider.PostgreSQL.csproj b/Providers/FreeSql.Provider.PostgreSQL/FreeSql.Provider.PostgreSQL.csproj index 3da8e2e9..875c4dd5 100644 --- a/Providers/FreeSql.Provider.PostgreSQL/FreeSql.Provider.PostgreSQL.csproj +++ b/Providers/FreeSql.Provider.PostgreSQL/FreeSql.Provider.PostgreSQL.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.ShenTong/FreeSql.Provider.ShenTong.csproj b/Providers/FreeSql.Provider.ShenTong/FreeSql.Provider.ShenTong.csproj index 0408d544..1e9cfdef 100644 --- a/Providers/FreeSql.Provider.ShenTong/FreeSql.Provider.ShenTong.csproj +++ b/Providers/FreeSql.Provider.ShenTong/FreeSql.Provider.ShenTong.csproj @@ -15,7 +15,7 @@ $(AssemblyName) true true - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.SqlServer/FreeSql.Provider.SqlServer.csproj b/Providers/FreeSql.Provider.SqlServer/FreeSql.Provider.SqlServer.csproj index 7c229e31..91182fd9 100644 --- a/Providers/FreeSql.Provider.SqlServer/FreeSql.Provider.SqlServer.csproj +++ b/Providers/FreeSql.Provider.SqlServer/FreeSql.Provider.SqlServer.csproj @@ -18,7 +18,7 @@ true false key.snk - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.SqlServerForSystem/FreeSql.Provider.SqlServerForSystem.csproj b/Providers/FreeSql.Provider.SqlServerForSystem/FreeSql.Provider.SqlServerForSystem.csproj index 4e08df8f..fbabfb5e 100644 --- a/Providers/FreeSql.Provider.SqlServerForSystem/FreeSql.Provider.SqlServerForSystem.csproj +++ b/Providers/FreeSql.Provider.SqlServerForSystem/FreeSql.Provider.SqlServerForSystem.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.Sqlite/FreeSql.Provider.Sqlite.csproj b/Providers/FreeSql.Provider.Sqlite/FreeSql.Provider.Sqlite.csproj index 5e45bf8d..6f1767cf 100644 --- a/Providers/FreeSql.Provider.Sqlite/FreeSql.Provider.Sqlite.csproj +++ b/Providers/FreeSql.Provider.Sqlite/FreeSql.Provider.Sqlite.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667 diff --git a/Providers/FreeSql.Provider.SqliteCore/FreeSql.Provider.SqliteCore.csproj b/Providers/FreeSql.Provider.SqliteCore/FreeSql.Provider.SqliteCore.csproj index a600171a..410ebe2c 100644 --- a/Providers/FreeSql.Provider.SqliteCore/FreeSql.Provider.SqliteCore.csproj +++ b/Providers/FreeSql.Provider.SqliteCore/FreeSql.Provider.SqliteCore.csproj @@ -18,7 +18,7 @@ true key.snk false - 3.2.666 + 3.2.667