From f601d9b9e0afe62cfa838cca924e153fbdcd832c Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Mon, 5 Sep 2022 13:08:22 +0800 Subject: [PATCH] AggregateRootRepository Boundary --- .../AggregateRootBoundaryAttribute.cs | 6 + FreeSql.Repository/AggregateRootModel.cs | 5 + FreeSql.Repository/AggregateRootRepository.cs | 2 +- .../AggregateRootRepositoryAsync.cs | 118 +++++++++++++++++- .../AggregateRootRepositorySync.cs | 60 +-------- FreeSql.Repository/AggregateRootUtils.cs | 83 +++++------- .../AggregateRootRepositoryTest2.cs | 18 ++- 7 files changed, 174 insertions(+), 118 deletions(-) diff --git a/FreeSql.Repository/AggregateRootBoundaryAttribute.cs b/FreeSql.Repository/AggregateRootBoundaryAttribute.cs index b45601ca..a2603b44 100644 --- a/FreeSql.Repository/AggregateRootBoundaryAttribute.cs +++ b/FreeSql.Repository/AggregateRootBoundaryAttribute.cs @@ -4,6 +4,12 @@ using System.Linq; namespace FreeSql.DataAnnotations { + /// + /// 设置 AggregateRootRepository 边界范围 + /// 在边界范围之内的规则 : + /// 1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改 + /// 2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略) + /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class AggregateRootBoundaryAttribute : Attribute { diff --git a/FreeSql.Repository/AggregateRootModel.cs b/FreeSql.Repository/AggregateRootModel.cs index b0a02486..7bff61bd 100644 --- a/FreeSql.Repository/AggregateRootModel.cs +++ b/FreeSql.Repository/AggregateRootModel.cs @@ -3,10 +3,15 @@ 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.Repository/AggregateRootRepository.cs b/FreeSql.Repository/AggregateRootRepository.cs index 7910b77e..ed043f80 100644 --- a/FreeSql.Repository/AggregateRootRepository.cs +++ b/FreeSql.Repository/AggregateRootRepository.cs @@ -40,7 +40,7 @@ namespace FreeSql DisposeChildRepositorys(); _repository.FlushState(); FlushState(); - _boundaryName = name; + _boundaryName = string.Concat(name).Trim(); return this; } diff --git a/FreeSql.Repository/AggregateRootRepositoryAsync.cs b/FreeSql.Repository/AggregateRootRepositoryAsync.cs index f6a94d82..1da92ee8 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(_boundaryName, _repository, GetChildRepository, entitys, out var affrows, cancellationToken); + var ret = await InsertWithinBoundaryStaticAsync(_boundaryName, _repository, GetChildRepository, entitys, null, cancellationToken); Attach(ret); return ret; } @@ -35,9 +35,116 @@ namespace FreeSql _repository.FlushState(); } } - Task> InsertWithinBoundaryStaticAsync(string boundaryName, IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys, out int affrows, CancellationToken cancellationToken) where T1 : class + async Task> InsertWithinBoundaryStaticAsync(string boundaryName, IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys, int[] affrows, CancellationToken cancellationToken) where T1 : class { - return Task.FromResult(InsertWithinBoundaryStatic(boundaryName, rootRepository, getChildRepository, rootEntitys, out affrows)); + 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 @@ -138,8 +245,9 @@ namespace FreeSql foreach (var il in insertLogDict) { var repo = GetChildRepository(il.Key); - await InsertWithinBoundaryStaticAsync(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut, cancellationToken); - affrows += affrowsOut; + 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--) diff --git a/FreeSql.Repository/AggregateRootRepositorySync.cs b/FreeSql.Repository/AggregateRootRepositorySync.cs index 7d7ab373..cdd18e00 100644 --- a/FreeSql.Repository/AggregateRootRepositorySync.cs +++ b/FreeSql.Repository/AggregateRootRepositorySync.cs @@ -130,64 +130,6 @@ 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); @@ -250,7 +192,7 @@ namespace FreeSql } break; case TableRefType.PgArrayToMany: - case TableRefType.ManyToOne: //在插入前处理 + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 break; } } diff --git a/FreeSql.Repository/AggregateRootUtils.cs b/FreeSql.Repository/AggregateRootUtils.cs index 708230b5..3f09558c 100644 --- a/FreeSql.Repository/AggregateRootUtils.cs +++ b/FreeSql.Repository/AggregateRootUtils.cs @@ -18,12 +18,17 @@ namespace FreeSql { public class AggregateRootUtils { + static ConcurrentDictionary> _dicGetPropertyBoundaryAttribute = new ConcurrentDictionary>(); 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(); + 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) @@ -112,18 +117,9 @@ namespace FreeSql 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); - if (boundaryAttr?.BreakThen == false) - LocalCompareEntityValueCollection(tbref.RefEntityType, propvalBefore as IEnumerable, propvalAfter as IEnumerable, boundaryAttr?.BreakThen == false); break; case TableRefType.PgArrayToMany: - 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); - } + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 break; } } @@ -142,7 +138,7 @@ namespace FreeSql //foreach (var item in collectionBefore as IEnumerable) //{ // changelog.DeleteLog.Add(NativeTuple.Create(elementType, new[] { item })); - // NavigateReader(fsql, elementType, item, (path, tr, ct, stackvs) => + // 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)); @@ -338,35 +334,16 @@ namespace FreeSql 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: - 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(); - } + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 break; } } @@ -423,18 +400,10 @@ namespace FreeSql 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, boundaryAttr?.BreakThen == false); + LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, false); break; case TableRefType.PgArrayToMany: - 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); - } + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 break; } } @@ -508,8 +477,10 @@ namespace FreeSql LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false); break; case TableRefType.PgArrayToMany: + if (boundaryAttr?.Break == false) + LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false); break; - case TableRefType.ManyToOne: + case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 if (boundaryAttr?.Break == false) { LocalInclude(tbref, navigateExp); @@ -589,14 +560,21 @@ namespace FreeSql 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); + 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: + 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)); @@ -638,10 +616,10 @@ namespace FreeSql public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem) { - if (rightItem == null) return; 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)); @@ -649,6 +627,7 @@ namespace FreeSql } break; case TableRefType.OneToMany: + if (rightItem == null) return; var rightEachOtm = rightItem as IEnumerable; if (rightEachOtm == null) break; var leftColValsOtm = new object[tbref.Columns.Count]; @@ -661,7 +640,9 @@ namespace FreeSql case TableRefType.ManyToOne: for (var idx = 0; idx < tbref.RefColumns.Count; idx++) { - var colval = Utils.GetDataReaderValue(tbref.Columns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName)); + 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; diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs index d153c4ff..3338f67e 100644 --- a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs +++ b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs @@ -38,12 +38,25 @@ namespace FreeSql.Tests.DbContext2 }; var code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order)); + var code1 = AggregateRootUtils.GetAutoIncludeQueryStaicCode("code1", fsql, typeof(Order)); + var code2 = AggregateRootUtils.GetAutoIncludeQueryStaicCode("code2", fsql, typeof(Order)); Assert.Equal(@"//fsql.Select() SelectDiy .Include(a => a.Extdata) .IncludeMany(a => a.Details, then => then .Include(b => b.Extdata)) .IncludeMany(a => a.Tags)", code); + Assert.Equal(@"//fsql.Select() +SelectDiy + .Include(a => a.Extdata) + .IncludeMany(a => a.Details, then => then + .Include(b => b.Extdata)) + .IncludeMany(a => a.Tags, then => then + .IncludeMany(b => b.Orders))", code1); + Assert.Equal(@"//fsql.Select() +SelectDiy + .IncludeMany(a => a.Details) + .IncludeMany(a => a.Tags)", code2); fsql.Insert(new[] { @@ -175,10 +188,11 @@ SelectDiy public int Id { get; set; } public string Field2 { get; set; } + [AggregateRootBoundary("code2", Break = true)] public OrderExt Extdata { get; set; } - [Navigate(nameof(OrderDetail.OrderId))] + [Navigate(nameof(OrderDetail.OrderId)), AggregateRootBoundary("code2", BreakThen = true)] public List Details { get; set; } - [Navigate(ManyToMany = typeof(OrderTag))] + [Navigate(ManyToMany = typeof(OrderTag)), AggregateRootBoundary("code1", Break = false, BreakThen = false)] public List Tags { get; set; } } class OrderExt