diff --git a/FreeSql.Repository/AggregateRootRepositoryAsync.cs b/FreeSql.Repository/AggregateRootRepositoryAsync.cs index 4ac32a9e..dfe62fff 100644 --- a/FreeSql.Repository/AggregateRootRepositoryAsync.cs +++ b/FreeSql.Repository/AggregateRootRepositoryAsync.cs @@ -17,23 +17,247 @@ namespace FreeSql { partial class AggregateRootRepository { - //以下临时调用同步方法 - public Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => Task.FromResult(Insert(entity)); - public Task> InsertAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => Task.FromResult(Insert(entitys)); - public Task InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => Task.FromResult(InsertOrUpdate(entity)); - async public Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default) + + #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) { - SaveMany(entity, propertyName); + var repos = new Dictionary(); + try + { + var ret = await InsertWithinBoundaryStaticAsync(_repository, GetChildRepository, entitys, out var affrows, cancellationToken); + Attach(ret); + return ret; + } + finally + { + DisposeChildRepositorys(); + _repository.FlushState(); + } + } + Task> InsertWithinBoundaryStaticAsync(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; + } + } + #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 Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => Task.FromResult(Update(entity)); - public Task UpdateAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => Task.FromResult(Update(entitys)); + 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(Orm, EntityType, state.Value, entity, null, tracking); + } + foreach (var entity in entitys) + Attach(entity); - public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => Task.FromResult(Delete(entity)); - public Task DeleteAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => Task.FromResult(Delete(entitys)); - public Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) => Task.FromResult(Delete(predicate)); - public Task> DeleteCascadeByDatabaseAsync(Expression> predicate, CancellationToken cancellationToken = default) => Task.FromResult(DeleteCascadeByDatabase(predicate)); + 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(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); + } + 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(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); + await InsertWithinBoundaryStaticAsync(repo, GetChildRepository, il.Value, out var affrowsOut, cancellationToken); + affrows += affrowsOut; + } + + 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); + + var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => + NativeTuple.Create(b.Item2, b.Item3, string.Join(",", b.Item4.OrderBy(c => c)), b.Item4)).ToArray()); + var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Item3, b => a.Value.Where(c => c.Item3 == b.Item3).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.Item2).ToArray()) + .UpdateColumns(dl2.Value.First().Item4.ToArray()) + .ExecuteAffrowsAsync(cancellationToken); + } + } + DisposeChildRepositorys(); + return affrows; + } } } #endif \ No newline at end of file diff --git a/FreeSql.Repository/AggregateRootRepositorySync.cs b/FreeSql.Repository/AggregateRootRepositorySync.cs index 34487011..ffc28490 100644 --- a/FreeSql.Repository/AggregateRootRepositorySync.cs +++ b/FreeSql.Repository/AggregateRootRepositorySync.cs @@ -15,242 +15,11 @@ namespace FreeSql { partial class AggregateRootRepository { - public TEntity Insert(TEntity entity) => InsertAggregateRoot(new[] { entity }).FirstOrDefault(); - public List Insert(IEnumerable entitys) => InsertAggregateRoot(entitys); - public TEntity InsertOrUpdate(TEntity entity) => InsertOrUpdateAggregateRoot(entity); - public int Update(TEntity entity) => UpdateAggregateRoot(new[] { entity }); - public int Update(IEnumerable entitys) => UpdateAggregateRoot(entitys); - public int Delete(TEntity entity) => DeleteAggregateRoot(new[] { entity }); - public int Delete(IEnumerable entitys) => DeleteAggregateRoot(entitys); - public int Delete(Expression> predicate) => DeleteAggregateRoot(SelectAggregateRoot.Where(predicate).ToList()); - public List DeleteCascadeByDatabase(Expression> predicate) - { - var deletedOutput = new List(); - DeleteAggregateRoot(SelectAggregateRoot.Where(predicate).ToList(), deletedOutput); - return deletedOutput; - } - public void SaveMany(TEntity entity, string propertyName) => SaveManyAggregateRoot(entity, propertyName); - protected virtual List InsertAggregateRoot(IEnumerable entitys) - { - var repos = new Dictionary(); - try - { - var ret = InsertAggregateRootStatic(_repository, GetChildRepository, entitys, out var affrows); - Attach(ret); - return ret; - } - finally - { - DisposeChildRepositorys(); - _repository.FlushState(); - } - } - protected static List InsertAggregateRootStatic(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 LocalInsertAggregateRoot(rootRepository, rootEntitys); - } - finally - { - affrows = localAffrows; - } - - bool LocalCanAggregateRoot(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 LocalInsertAggregateRoot(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 = repository.Insert(entitys); - localAffrows += ret.Count; - foreach (var entity in entitys) LocalCanAggregateRoot(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 (LocalCanAggregateRoot(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); - LocalInsertAggregateRoot(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 (LocalCanAggregateRoot(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); - LocalInsertAggregateRoot(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); - LocalInsertAggregateRoot(repo, mtmMidList); - } - break; - case TableRefType.PgArrayToMany: - break; - } - } - return ret; - } - } - - protected virtual TEntity InsertOrUpdateAggregateRoot(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 = UpdateAggregateRoot(new[] { entity }); - if (affrows > 0) return entity; - } - if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length) - { - Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity); - return InsertAggregateRoot(new[] { entity }).FirstOrDefault(); - } - throw new Exception(DbContextStrings.CannotAdd_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, entity))); - } - protected virtual int UpdateAggregateRoot(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(Orm, EntityType, state.Value, entity, null, 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); - InsertAggregateRootStatic(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(); - - var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => - NativeTuple.Create(b.Item2, b.Item3, string.Join(",", b.Item4.OrderBy(c => c)), b.Item4)).ToArray()); - var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Item3, b => a.Value.Where(c => c.Item3 == b.Item3).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.Item2).ToArray()) - .UpdateColumns(dl2.Value.First().Item4.ToArray()) - .ExecuteAffrows(); - } - } - DisposeChildRepositorys(); - foreach (var entity in entitys) - Attach(entity); - - return affrows; - } - protected virtual int DeleteAggregateRoot(IEnumerable entitys, List deletedOutput = null) - { - var tracking = new AggregateRootTrackingChangeInfo(); - foreach (var entity in entitys) - { - var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); - AggregateRootUtils.CompareEntityValue(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); - } - return affrows; - } - - protected virtual void SaveManyAggregateRoot(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(Orm, EntityType, state.Value, entity, propertyName, tracking); - Attach(entity); - } - - protected List _dataEditing; - protected ConcurrentDictionary _statesEditing = new ConcurrentDictionary(); - public void BeginEdit(List data) + #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); @@ -270,7 +39,7 @@ namespace FreeSql }); } } - public int EndEdit(List data = null) + public virtual int EndEdit(List data = null) { if (data == null) data = _dataEditing; if (data == null) return 0; @@ -293,15 +62,253 @@ namespace FreeSql foreach (var item in _statesEditing.Values.OrderBy(a => a.Time)) AggregateRootUtils.CompareEntityValue(Orm, EntityType, item, null, null, tracking); - + return SaveTrackingChange(tracking); } finally { _dataEditing = null; _statesEditing.Clear(); } - return tracking.InsertLog.Count + tracking.UpdateLog.Count + tracking.DeleteLog.Count; + } + #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(_repository, GetChildRepository, entitys, out var affrows); + Attach(ret); + return ret; + } + finally + { + DisposeChildRepositorys(); + _repository.FlushState(); + } + } + static List InsertWithinBoundaryStatic(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); + } + 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) 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); + + 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); + LocalInsert(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); + LocalInsert(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); + LocalInsert(repo, mtmMidList); + } + break; + case TableRefType.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(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(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); + } + 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(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(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(); + + var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => + NativeTuple.Create(b.Item2, b.Item3, string.Join(",", b.Item4.OrderBy(c => c)), b.Item4)).ToArray()); + var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Item3, b => a.Value.Where(c => c.Item3 == b.Item3).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.Item2).ToArray()) + .UpdateColumns(dl2.Value.First().Item4.ToArray()) + .ExecuteAffrows(); + } + } + DisposeChildRepositorys(); + return affrows; + } } }