diff --git a/FreeSql.Repository/AggregateRootBoundaryAttribute.cs b/FreeSql.Repository/AggregateRootBoundaryAttribute.cs new file mode 100644 index 00000000..b45601ca --- /dev/null +++ b/FreeSql.Repository/AggregateRootBoundaryAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; + +namespace FreeSql.DataAnnotations +{ + + [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.Repository/AggregateRootRepository.cs b/FreeSql.Repository/AggregateRootRepository.cs index 59a38c9d..7910b77e 100644 --- a/FreeSql.Repository/AggregateRootRepository.cs +++ b/FreeSql.Repository/AggregateRootRepository.cs @@ -1,4 +1,5 @@ using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal.Model; using System; using System.Collections; using System.Collections.Generic; @@ -7,7 +8,12 @@ using System.Linq.Expressions; namespace FreeSql { - public partial class AggregateRootRepository : IBaseRepository where TEntity : class + 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) @@ -23,10 +29,21 @@ namespace FreeSql public void Dispose() { DisposeChildRepositorys(); + _repository.FlushState(); _repository.Dispose(); FlushState(); } + string _boundaryName = ""; + public IBaseRepository ChangeBoundary(string name) + { + DisposeChildRepositorys(); + _repository.FlushState(); + FlushState(); + _boundaryName = name; + return this; + } + public IFreeSql Orm => _repository.Orm; public IUnitOfWork UnitOfWork { get => _repository.UnitOfWork; set => _repository.UnitOfWork = value; } public DbContextOptions DbContextOptions @@ -61,9 +78,26 @@ namespace FreeSql Attach(item); } public IBaseRepository AttachOnlyPrimary(TEntity data) => _repository.AttachOnlyPrimary(data); - public Dictionary CompareState(TEntity newdata) => _repository.CompareState(newdata); + 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(); } @@ -117,7 +151,7 @@ namespace FreeSql 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(Orm, EntityType, data, state.Value); + AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, data, state.Value); return state; } bool? ExistsInStates(object data) @@ -139,9 +173,9 @@ namespace FreeSql /// 创建查询对象(纯净) /// _ /// 聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发 - /// string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(fsql, typeof(Order)) + /// string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order)) /// - protected ISelect SelectDiy => _repository.Select; + protected ISelect SelectDiy => _repository.Select.TrackToList(SelectAggregateRootTracking); /// /// 创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性) /// @@ -151,7 +185,7 @@ namespace FreeSql get { var query = _repository.Select.TrackToList(SelectAggregateRootTracking); - query = AggregateRootUtils.GetAutoIncludeQuery(query); + query = AggregateRootUtils.GetAutoIncludeQuery(_boundaryName, query); return query; } } @@ -214,7 +248,8 @@ namespace FreeSql // currentQuery.IncludeByPropertyName(navigateExpression); // break; // case TableRefType.PgArrayToMany: - // case TableRefType.ManyToOne: //不属于聚合根 + // break; + // case TableRefType.ManyToOne: // break; // } // } diff --git a/FreeSql.Repository/AggregateRootRepositoryAsync.cs b/FreeSql.Repository/AggregateRootRepositoryAsync.cs index 1ecd4798..f6a94d82 100644 --- a/FreeSql.Repository/AggregateRootRepositoryAsync.cs +++ b/FreeSql.Repository/AggregateRootRepositoryAsync.cs @@ -25,7 +25,7 @@ namespace FreeSql var repos = new Dictionary(); try { - var ret = await InsertWithinBoundaryStaticAsync(_repository, GetChildRepository, entitys, out var affrows, cancellationToken); + var ret = await InsertWithinBoundaryStaticAsync(_boundaryName, _repository, GetChildRepository, entitys, out var affrows, cancellationToken); Attach(ret); return ret; } @@ -35,112 +35,9 @@ namespace FreeSql _repository.FlushState(); } } - Task> InsertWithinBoundaryStaticAsync(IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys, out int affrows, CancellationToken cancellationToken) where T1 : class + Task> InsertWithinBoundaryStaticAsync(string boundaryName, IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys, out int affrows, CancellationToken cancellationToken) where T1 : class { - Dictionary> ignores = new Dictionary>(); - Dictionary> repos = new Dictionary>(); - var localAffrows = 0; - try - { - return LocalInsertAsync(rootRepository, rootEntitys); - } - 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; - } - async Task> LocalInsertAsync(IBaseRepository repository, IEnumerable entitys) 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); - - 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; - 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); - } - 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); - } - 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); - } - break; - case TableRefType.PgArrayToMany: - break; - } - } - return ret; - } + return Task.FromResult(InsertWithinBoundaryStatic(boundaryName, rootRepository, getChildRepository, rootEntitys, out affrows)); } #endregion @@ -178,7 +75,7 @@ namespace FreeSql { 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(Orm, EntityType, state.Value, entity, null, tracking); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking); } foreach (var entity in entitys) Attach(entity); @@ -202,7 +99,7 @@ namespace FreeSql foreach (var entity in entitys) { var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); - AggregateRootUtils.CompareEntityValue(Orm, EntityType, entity, null, null, tracking); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking); _states.Remove(stateKey); } var affrows = 0; @@ -227,7 +124,7 @@ namespace FreeSql 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(Orm, EntityType, state.Value, entity, propertyName, tracking); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking); Attach(entity); //应该只存储 propertyName 内容 await SaveTrackingChangeAsync(tracking, cancellationToken); } @@ -241,7 +138,7 @@ namespace FreeSql foreach (var il in insertLogDict) { var repo = GetChildRepository(il.Key); - await InsertWithinBoundaryStaticAsync(repo, GetChildRepository, il.Value, out var affrowsOut, cancellationToken); + await InsertWithinBoundaryStaticAsync(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut, cancellationToken); affrows += affrowsOut; } diff --git a/FreeSql.Repository/AggregateRootRepositorySync.cs b/FreeSql.Repository/AggregateRootRepositorySync.cs index ac1b3323..7d7ab373 100644 --- a/FreeSql.Repository/AggregateRootRepositorySync.cs +++ b/FreeSql.Repository/AggregateRootRepositorySync.cs @@ -33,7 +33,7 @@ namespace FreeSql _statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) => { - AggregateRootUtils.MapEntityValue(Orm, EntityType, item, ov.Value); + AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, item, ov.Value); ov.Time = DateTime.Now; return ov; }); @@ -57,10 +57,10 @@ namespace FreeSql continue; } _states[key] = state; - AggregateRootUtils.CompareEntityValue(Orm, EntityType, state.Value, item, null, tracking); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, item, null, tracking); } foreach (var item in _statesEditing.Values.OrderBy(a => a.Time)) - AggregateRootUtils.CompareEntityValue(Orm, EntityType, item, null, null, tracking); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, item, null, null, tracking); return SaveTrackingChange(tracking); } @@ -79,7 +79,7 @@ namespace FreeSql var repos = new Dictionary(); try { - var ret = InsertWithinBoundaryStatic(_repository, GetChildRepository, entitys, out var affrows); + var ret = InsertWithinBoundaryStatic(_boundaryName, _repository, GetChildRepository, entitys, out var affrows); Attach(ret); return ret; } @@ -89,13 +89,13 @@ namespace FreeSql _repository.FlushState(); } } - static List InsertWithinBoundaryStatic(IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys, out int affrows) where T1 : class { + 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); + return LocalInsert(rootRepository, rootEntitys, true); } finally { @@ -122,7 +122,7 @@ namespace FreeSql } return false; } - List LocalInsert(IBaseRepository repository, IEnumerable entitys) where T2 : class + 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)) @@ -130,15 +130,76 @@ namespace FreeSql foreach (var entity in entitys) repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity); } + + if (cascade) + { + 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.ManyToMany: + if (boundaryAttr?.Break == false) + { + var mtmList = entitys.Select(entity => + { + var mtmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable; + if (mtmEach == null) return null; + var mtmItems = new List(); + foreach (var mtmItem in mtmEach) + { + if (LocalCanInsert(tbref.RefEntityType, mtmItem, false) == false) continue; + mtmItems.Add(mtmItem); + } + return mtmItems; + }).Where(entity => entity != null).SelectMany(entity => entity).ToArray(); + if (mtmList.Any()) + { + var repo = getChildRepository(tbref.RefEntityType); + LocalInsert(repo, mtmList, boundaryAttr?.BreakThen == false); + } + } + break; + case TableRefType.PgArrayToMany: + break; + case TableRefType.ManyToOne: + if (boundaryAttr?.Break == false) + { + var mtoList = entitys.Select(entity => + { + var mtoItem = table.GetPropertyValue(entity, prop.Name); + if (LocalCanInsert(tbref.RefEntityType, mtoItem, false) == false) return null; + return NativeTuple.Create(entity, mtoItem); + }).Where(entity => entity != null).ToArray(); + if (mtoList.Any()) + { + var repo = getChildRepository(tbref.RefEntityType); + LocalInsert(repo, mtoList.Select(a => a.Item2), boundaryAttr?.BreakThen != true); + foreach (var mtoItem in mtoList) + AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, mtoItem.Item1, mtoItem.Item2); + } + } + break; + } + } + } + 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: @@ -152,7 +213,7 @@ namespace FreeSql if (otoList.Any()) { var repo = getChildRepository(tbref.RefEntityType); - LocalInsert(repo, otoList); + LocalInsert(repo, otoList, boundaryAttr?.BreakThen != true); } break; case TableRefType.OneToMany: @@ -172,7 +233,7 @@ namespace FreeSql if (otmList.Any()) { var repo = getChildRepository(tbref.RefEntityType); - LocalInsert(repo, otmList); + LocalInsert(repo, otmList, boundaryAttr?.BreakThen != true); } break; case TableRefType.ManyToMany: @@ -185,10 +246,11 @@ namespace FreeSql if (mtmMidList.Any()) { var repo = getChildRepository(tbref.RefMiddleEntityType); - LocalInsert(repo, mtmMidList); + LocalInsert(repo, mtmMidList, false); } break; case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //在插入前处理 break; } } @@ -231,7 +293,7 @@ namespace FreeSql { 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(Orm, EntityType, state.Value, entity, null, tracking); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking); } foreach (var entity in entitys) Attach(entity); @@ -254,7 +316,7 @@ namespace FreeSql foreach (var entity in entitys) { var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); - AggregateRootUtils.CompareEntityValue(Orm, EntityType, entity, null, null, tracking); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking); _states.Remove(stateKey); } var affrows = 0; @@ -279,7 +341,7 @@ namespace FreeSql 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(Orm, EntityType, state.Value, entity, propertyName, tracking); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking); Attach(entity); //应该只存储 propertyName 内容 SaveTrackingChange(tracking); } @@ -293,7 +355,7 @@ namespace FreeSql foreach (var il in insertLogDict) { var repo = GetChildRepository(il.Key); - InsertWithinBoundaryStatic(repo, GetChildRepository, il.Value, out var affrowsOut); + InsertWithinBoundaryStatic(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut); affrows += affrowsOut; } diff --git a/FreeSql.Repository/AggregateRootUtils.cs b/FreeSql.Repository/AggregateRootUtils.cs index 628f0041..708230b5 100644 --- a/FreeSql.Repository/AggregateRootUtils.cs +++ b/FreeSql.Repository/AggregateRootUtils.cs @@ -1,4 +1,5 @@ using FreeSql; +using FreeSql.DataAnnotations; using FreeSql.Extensions.EntityUtil; using FreeSql.Internal; using FreeSql.Internal.CommonProvider; @@ -15,15 +16,23 @@ using System.Text; namespace FreeSql { - public static class AggregateRootUtils + public class AggregateRootUtils { - public static void CompareEntityValue(IFreeSql fsql, Type rootEntityType, object rootEntityBefore, object rootEntityAfter, string rootNavigatePropertyName, AggregateRootTrackingChangeInfo tracking) + public static AggregateRootBoundaryAttribute GetPropertyBoundaryAttribute(PropertyInfo prop, string boundaryName) + { + if (string.IsNullOrWhiteSpace(boundaryName)) return null; + 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 == boundaryName).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); + LocalCompareEntityValue(rootEntityType, rootEntityBefore, rootEntityAfter, rootNavigatePropertyName, true); ignores.Clear(); - void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName) + void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName, bool cascade) { if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType(); @@ -53,7 +62,7 @@ namespace FreeSql if (entityBefore != null && entityAfter == null) { tracking.DeleteLog.Add(NativeTuple.Create(entityType, new[] { entityBefore })); - NavigateReader(fsql, entityType, entityBefore, (path, tr, ct, stackvs) => + 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)); @@ -74,14 +83,16 @@ namespace FreeSql continue; } } - if (changes.Any()) - tracking.UpdateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes)); + 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); @@ -90,27 +101,35 @@ namespace FreeSql case TableRefType.OneToOne: SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore); SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter); - LocalCompareEntityValue(tbref.RefEntityType, propvalBefore, propvalAfter, null); + 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, propvalBefore as IEnumerable, propvalAfter as IEnumerable); + 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, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable); + LocalCompareEntityValueCollection(tbref.RefMiddleEntityType, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable, false); + if (boundaryAttr?.BreakThen == false) + LocalCompareEntityValueCollection(tbref.RefEntityType, propvalBefore as IEnumerable, propvalAfter as IEnumerable, boundaryAttr?.BreakThen == false); break; case TableRefType.PgArrayToMany: - case TableRefType.ManyToOne: //不属于聚合根 + break; + case TableRefType.ManyToOne: + if (boundaryAttr?.BreakThen == false) + { + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore); + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter); + LocalCompareEntityValue(tbref.RefEntityType, propvalBefore, propvalAfter, null, boundaryAttr?.BreakThen == false); + } break; } } } - void LocalCompareEntityValueCollection(TableRef tbref, IEnumerable collectionBefore, IEnumerable collectionAfter) + void LocalCompareEntityValueCollection(Type elementType, IEnumerable collectionBefore, IEnumerable collectionAfter, bool cascade) { - var elementType = tbref.RefType == TableRefType.ManyToMany ? tbref.RefMiddleEntityType : tbref.RefEntityType; if (collectionBefore == null && collectionAfter == null) return; if (collectionBefore == null && collectionAfter != null) { @@ -154,7 +173,7 @@ namespace FreeSql { var value = dictBefore[key]; tracking.DeleteLog.Add(NativeTuple.Create(elementType, new[] { value })); - NavigateReader(fsql, elementType, value, (path, tr, ct, stackvs) => + 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)); @@ -171,7 +190,7 @@ namespace FreeSql } } foreach (var key in dictBefore.Keys) - LocalCompareEntityValue(elementType, dictBefore[key], dictAfter[key], null); + LocalCompareEntityValue(elementType, dictBefore[key], dictAfter[key], null, cascade); } } @@ -259,7 +278,7 @@ namespace FreeSql return object.Equals(propvalBefore, propvalAfter); } - public static void NavigateReader(IFreeSql fsql, Type rootType, object rootEntity, Action> callback) + public static void NavigateReader(string boundaryName, IFreeSql fsql, Type rootType, object rootEntity, Action> callback) { Dictionary> ignores = new Dictionary>(); var statckPath = new Stack(); @@ -286,6 +305,8 @@ namespace FreeSql 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: @@ -295,49 +316,70 @@ namespace FreeSql stackValues.Add(propval); SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propval); callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); - LocalNavigateReader(tbref.RefEntityType, propval); + 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); + 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 as IEnumerable) + 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); - foreach (var val in propvalOtm as IEnumerable) - LocalNavigateReader(tbref.RefEntityType, val); + 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 propvalMtm = table.GetPropertyValue(entity, prop.Name) as IEnumerable; + if (propvalMtm == null) continue; 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); + if (boundaryAttr?.BreakThen == false) + foreach (var val in propvalMtm) + LocalNavigateReader(tbref.RefEntityType, val); stackValues.RemoveAt(stackValues.Count - 1); statckPath.Pop(); break; case TableRefType.PgArrayToMany: - case TableRefType.ManyToOne: //不属于聚合根 + break; + case TableRefType.ManyToOne: + if (boundaryAttr?.Break == false) + { + var propvalMto = table.GetPropertyValue(entity, prop.Name); + if (propvalMto == null) continue; + statckPath.Push(prop.Name); + stackValues.Add(propvalMto); + SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propvalMto); + callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); + if (boundaryAttr?.BreakThen == false) + LocalNavigateReader(tbref.RefEntityType, propvalMto); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + } break; } } } } - public static void MapEntityValue(IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo) + public static void MapEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo) { Dictionary> ignores = new Dictionary>(); - LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo); + LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo, true); ignores.Clear(); - void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo) + void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo, bool cascade) { if (entityFrom == null || entityTo == null) return; if (entityType == null) entityType = entityFrom?.GetType() ?? entityTo?.GetType(); @@ -357,8 +399,11 @@ namespace FreeSql 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) { @@ -370,18 +415,26 @@ namespace FreeSql case TableRefType.OneToOne: var propvalTo = tbref.RefEntityType.CreateInstanceGetDefaultValue(); SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom); - LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo); + 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, true); + 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); + LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, boundaryAttr?.BreakThen == false); break; case TableRefType.PgArrayToMany: - case TableRefType.ManyToOne: //不属于聚合根 + break; + case TableRefType.ManyToOne: + if (boundaryAttr?.Break == false) + { + var propvalTo2 = tbref.RefEntityType.CreateInstanceGetDefaultValue(); + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom); + LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo2, boundaryAttr?.BreakThen == false); + EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo2); + } break; } } @@ -393,8 +446,7 @@ namespace FreeSql foreach (var fromItem in propvalFrom) { var toItem = tbref.RefEntityType.CreateInstanceGetDefaultValue(); - if (cascade) LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem); - else EntityUtilExtensions.MapEntityValue(fsql, tbref.RefEntityType, fromItem, toItem); + LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem, cascade); propvalToIList.Add(toItem); } var propvalType = prop.PropertyType.GetGenericTypeDefinition(); @@ -409,12 +461,14 @@ namespace FreeSql } } - static ConcurrentDictionary>> _dicGetAutoIncludeQuery = new ConcurrentDictionary>>(); - public static ISelect GetAutoIncludeQuery(ISelect select) + 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(typeof(TEntity), t => new ConcurrentDictionary>()).GetOrAdd(table0Type, t => + 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"); @@ -435,6 +489,8 @@ namespace FreeSql 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) @@ -442,17 +498,24 @@ namespace FreeSql case TableRefType.OneToOne: if (ignores.Any(a => a == tbref.RefEntityType)) break; LocalInclude(tbref, navigateExp); - queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores); + if (boundaryAttr?.BreakThen != true) + queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores); break; case TableRefType.OneToMany: - LocalIncludeMany(tbref, navigateExp, true); + LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen != true); break; case TableRefType.ManyToMany: - LocalIncludeMany(tbref, navigateExp, false); + LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false); break; case TableRefType.PgArrayToMany: break; - case TableRefType.ManyToOne: //不属于聚合根 + case TableRefType.ManyToOne: + if (boundaryAttr?.Break == false) + { + LocalInclude(tbref, navigateExp); + if (boundaryAttr?.BreakThen == false) + queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores); + } break; } } @@ -482,7 +545,8 @@ namespace FreeSql } } } - public static string GetAutoIncludeQueryStaicCode(IFreeSql fsql, Type rootEntityType) + + 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) @@ -497,6 +561,9 @@ namespace FreeSql { 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)); @@ -506,22 +573,33 @@ namespace FreeSql case TableRefType.OneToOne: if (ignores.Any(a => a == tbref.RefEntityType)) break; code.Append("\r\n").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")"); - code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores)); + 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); - var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack(ignores.ToArray())); - if (thencode.Length > 0) code.Append(", then => then").Append(thencode); + 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).Append(")"); + code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression); + if (boundaryAttr?.BreakThen == false) + { + var thencode2 = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack(ignores.ToArray())); + if (thencode2.Length > 0) code.Append(", then => then").Append(thencode2); + } + code.Append(")"); break; case TableRefType.PgArrayToMany: - code.Append("\r\n//").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression).Append(")"); break; - case TableRefType.ManyToOne: //不属于聚合根 - code.Append("\r\n//").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")"); + case TableRefType.ManyToOne: + 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; } } @@ -557,6 +635,7 @@ namespace FreeSql } return middles; } + public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem) { if (rightItem == null) return; diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest.cs b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest.cs index 93698b0a..e18c0999 100644 --- a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest.cs +++ b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest.cs @@ -26,7 +26,7 @@ namespace FreeSql.Tests.DbContext2 { new UserRepository(fsql, null); - var code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(fsql, typeof(User)); + var code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(User)); Assert.Equal(@"//fsql.Select() SelectDiy .Include(a => a.Ext)", code); @@ -147,7 +147,6 @@ SelectDiy public int Id { get; set; } public string UserName { get; set; } public string Password { get; set; } - [Navigate(nameof(Id))] public UserExt Ext { get; set; } } class UserExt @@ -155,7 +154,6 @@ SelectDiy [Column(IsPrimary = true)] public int UserId { get; set; } public string Remark { get; set; } - [Navigate(nameof(UserId))] public User Org { get; set; } } } diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs index a5d4da34..d153c4ff 100644 --- a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs +++ b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs @@ -1,4 +1,5 @@ using FreeSql.DataAnnotations; +using FreeSql.Internal; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -27,7 +28,16 @@ namespace FreeSql.Tests.DbContext2 { new OrderRepository(fsql, null); - var code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(fsql, typeof(Order)); + + fsql.Aop.CommandAfter += (_, e) => + { + if (e.Exception is DbUpdateVersionException) + { + throw new Exception(e.Exception.Message, e.Exception); + } + }; + + var code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order)); Assert.Equal(@"//fsql.Select() SelectDiy .Include(a => a.Extdata) @@ -165,7 +175,6 @@ SelectDiy public int Id { get; set; } public string Field2 { get; set; } - [Navigate(nameof(Id))] public OrderExt Extdata { get; set; } [Navigate(nameof(OrderDetail.OrderId))] public List Details { get; set; } @@ -178,7 +187,6 @@ SelectDiy public int OrderId { get; set; } public string Field3 { get; set; } - [Navigate(nameof(OrderId))] public Order Order { get; set; } } class OrderDetail @@ -188,7 +196,7 @@ SelectDiy public int OrderId { get; set; } public string Field4 { get; set; } - [Navigate(nameof(Id))] + [AggregateRootBoundary("name1", Break = true)] public OrderDetailExt Extdata { get; set; } } class OrderDetailExt @@ -197,7 +205,6 @@ SelectDiy public int OrderDetailId { get; set; } public string Field5 { get; set; } - [Navigate(nameof(OrderDetailId))] public OrderDetail OrderDetail { get; set; } } class OrderTag