From 2b8ec5c57094ea2bdb19f22af754ee6418b3cab9 Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Fri, 2 Sep 2022 14:19:48 +0800 Subject: [PATCH] AggregateRootRepository --- FreeSql.Repository/AggregateRootRepository.cs | 289 ++++-------------- .../AggregateRootRepositoryAsync.cs | 35 +++ .../AggregateRootRepositorySync.cs | 257 ++++++++++++++++ FreeSql.Repository/AggregateRootUtils.cs | 46 ++- 4 files changed, 380 insertions(+), 247 deletions(-) create mode 100644 FreeSql.Repository/AggregateRootRepositoryAsync.cs create mode 100644 FreeSql.Repository/AggregateRootRepositorySync.cs diff --git a/FreeSql.Repository/AggregateRootRepository.cs b/FreeSql.Repository/AggregateRootRepository.cs index effe1d8e..12e5f0a7 100644 --- a/FreeSql.Repository/AggregateRootRepository.cs +++ b/FreeSql.Repository/AggregateRootRepository.cs @@ -13,79 +13,49 @@ using System.Threading.Tasks; namespace FreeSql { - public class AggregateRootRepository : IBaseRepository where TEntity : class + public partial class AggregateRootRepository : IBaseRepository where TEntity : class { - readonly IBaseRepository _repositoryPriv; - readonly Dictionary> _repositorys = new Dictionary>(); - protected IBaseRepository MainRepository - { - get - { - _repositoryPriv.DbContextOptions.EnableCascadeSave = false; - return _repositoryPriv; - } - } - protected IBaseRepository GetOrAddRepository(Type otherEntityType) - { - if (_repositorys.TryGetValue(otherEntityType, out var repo) == false) - { - repo = Orm.GetRepository(); - repo.AsType(otherEntityType); - _repositorys.Add(otherEntityType, repo); - } - repo.UnitOfWork = UnitOfWork; - repo.DbContextOptions = DbContextOptions; - repo.DbContextOptions.EnableCascadeSave = false; - repo.AsTable(_asTableRule); - return repo; - } - + readonly IBaseRepository _repository; public AggregateRootRepository(IFreeSql fsql) { - _repositoryPriv = fsql.GetRepository(); + if (fsql == null) throw new ArgumentNullException(nameof(fsql)); + _repository = fsql.GetRepository(); + _repository.DbContextOptions.EnableCascadeSave = false; } - public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager) + public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager) : this(uowManager?.Orm ?? fsql) { - _repositoryPriv = (uowManager?.Orm ?? fsql).GetRepository(); - uowManager?.Binding(_repositoryPriv); - } - - protected void DisposeRepositorys() - { - foreach (var repo in _repositorys.Values) - { - repo.FlushState(); - repo.Dispose(); - } - _repositorys.Clear(); + uowManager?.Binding(_repository); } public void Dispose() { - foreach (var repo in _repositorys.Values) - { - repo.FlushState(); - repo.Dispose(); - } - _repositorys.Clear(); - _repositoryPriv.FlushState(); - _repositoryPriv.Dispose(); + DisposeChildRepositorys(); + _repository.Dispose(); + FlushState(); } - public IUnitOfWork UnitOfWork - { - get => _repositoryPriv.UnitOfWork; - set => _repositoryPriv.UnitOfWork = value; - } + public IFreeSql Orm => _repository.Orm; + public IUnitOfWork UnitOfWork { get => _repository.UnitOfWork; set => _repository.UnitOfWork = value; } public DbContextOptions DbContextOptions { - get => _repositoryPriv.DbContextOptions; - set => _repositoryPriv.DbContextOptions = value ?? throw new ArgumentNullException(nameof(DbContextOptions)); + get => _repository.DbContextOptions; set + { + if (value != null) + { + _repository.DbContextOptions = value; + _repository.DbContextOptions.EnableCascadeSave = false; + } + } } - public void AsType(Type entityType) => _repositoryPriv.AsType(entityType); + public void AsType(Type entityType) => _repository.AsType(entityType); Func _asTableRule; - public void AsTable(Func rule) => _repositoryPriv.AsTable(_asTableRule = rule); - public Type EntityType => _repositoryPriv.EntityType; - public IDataFilter DataFilter => _repositoryPriv.DataFilter; + public void AsTable(Func rule) + { + _repository.AsTable(rule); + _asTableRule = rule; + } + TableInfo _table; + public Type EntityType => _repository.EntityType; + public IDataFilter DataFilter => _repository.DataFilter; public void Attach(TEntity entity) => AttachCascade(entity); public void Attach(IEnumerable entity) @@ -93,53 +63,44 @@ namespace FreeSql foreach (var item in entity) AttachCascade(item); } - public IBaseRepository AttachOnlyPrimary(TEntity data) => _repositoryPriv.AttachOnlyPrimary(data); - public Dictionary CompareState(TEntity newdata) => _repositoryPriv.CompareState(newdata); - public void FlushState() => _repositoryPriv.FlushState(); + public IBaseRepository AttachOnlyPrimary(TEntity data) => _repository.AttachOnlyPrimary(data); + public Dictionary CompareState(TEntity newdata) => _repository.CompareState(newdata); + public void FlushState() + { + _repository.FlushState(); + _states.Clear(); + } - public IFreeSql Orm => _repositoryPriv.Orm; - public IUpdate UpdateDiy => _repositoryPriv.UpdateDiy; + public IUpdate UpdateDiy => _repository.UpdateDiy; public ISelect Where(Expression> exp) => Select.Where(exp); public ISelect WhereIf(bool condition, Expression> exp) => Select.WhereIf(condition, exp); - public TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault(); - public List Insert(IEnumerable entitys) + readonly Dictionary> _childRepositorys = new Dictionary>(); + IBaseRepository GetChildRepository(Type type) { - var ret = InsertCascade(MainRepository, GetOrAddRepository, entitys, new Dictionary()); - DisposeRepositorys(); - foreach (var item in ret) - AttachCascade(item); - return ret; + if (_childRepositorys.TryGetValue(type, out var repo) == false) + { + repo = Orm.GetRepository(); + repo.AsType(type); + _childRepositorys.Add(type, repo); + } + repo.UnitOfWork = UnitOfWork; + repo.DbContextOptions = DbContextOptions; + repo.DbContextOptions.EnableCascadeSave = false; + repo.AsTable(_asTableRule); + return repo; + } + void DisposeChildRepositorys() + { + foreach (var repo in _childRepositorys.Values) + { + repo.FlushState(); + repo.Dispose(); + } + _childRepositorys.Clear(); } - public int Update(TEntity entity) => Update(new[] { entity }); - public int Update(IEnumerable entitys) - { - var ret = UpdateCascade(MainRepository, GetOrAddRepository, entitys, new Dictionary()); - DisposeRepositorys(); - foreach (var item in entitys) - AttachCascade(item); - return ret; - } - - public int Delete(TEntity entity) => Delete(new[] { entity }); - -#if net40 -#else - public Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.InsertAsync(entity, cancellationToken); - public Task> InsertAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repositoryPriv.InsertAsync(entitys, cancellationToken); - public Task InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.InsertOrUpdateAsync(entity, cancellationToken); - public Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default) => _repositoryPriv.SaveManyAsync(entity, propertyName, cancellationToken); - - public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.UpdateAsync(entity, cancellationToken); - public Task UpdateAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repositoryPriv.UpdateAsync(entitys, cancellationToken); - - public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(entity, cancellationToken); - public Task DeleteAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(entitys, cancellationToken); - public Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(predicate, cancellationToken); - public Task> DeleteCascadeByDatabaseAsync(Expression> predicate, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteCascadeByDatabaseAsync(predicate, cancellationToken); -#endif - + #region State protected Dictionary _states = new Dictionary(); protected class EntityState { @@ -175,13 +136,14 @@ namespace FreeSql if (_states.ContainsKey(state.Key)) _states[state.Key] = state; else _states.Add(state.Key, state); } + #endregion #region Select - public ISelect Select + public virtual ISelect Select { get { - var query = MainRepository.Select.TrackToList(SelectTrackingAggregateRootNavigate); + var query = _repository.Select.TrackToList(SelectTrackingAggregateRootNavigate); SelectFetchAggregateRootNavigate(query, EntityType, "", new Stack()); return query; } @@ -190,10 +152,10 @@ namespace FreeSql { if (ignores.Any(a => a == entityType)) return; ignores.Push(entityType); - var tb = Orm.CodeFirst.GetTableByEntity(entityType); - foreach (var prop in tb.Properties.Values) + var table = Orm.CodeFirst.GetTableByEntity(entityType); + foreach (var prop in table.Properties.Values) { - var tbref = tb.GetTableRef(prop.Name, false); + var tbref = table.GetTableRef(prop.Name, false); if (tbref == null) continue; if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}."; var navigateExpression = $"{navigatePath}{prop.Name}"; @@ -247,124 +209,5 @@ namespace FreeSql } #endregion - protected static List InsertCascade(IBaseRepository repository, Func> getOrAddRepository, IEnumerable entitys, Dictionary states) where T1 : class - { - var ret = repository.Insert(entitys); - foreach (var entity in entitys) IsCascade(repository.EntityType, entity, true); - - var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType); - foreach (var prop in table.Properties.Values) - { - var tbref = table.GetTableRef(prop.Name, false); - if (tbref == null) continue; - switch (tbref.RefType) - { - case TableRefType.OneToOne: - var otoList = ret.Select(entity => - { - var otoItem = table.GetPropertyValue(entity, prop.Name); - if (IsCascade(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 = getOrAddRepository(tbref.RefEntityType); - InsertCascade(repo, getOrAddRepository, otoList, states); - } - 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 (IsCascade(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 = getOrAddRepository(tbref.RefEntityType); - InsertCascade(repo, getOrAddRepository, otmList, states); - } - 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 = getOrAddRepository(tbref.RefMiddleEntityType); - InsertCascade(repo, getOrAddRepository, mtmMidList, states); - } - break; - case TableRefType.PgArrayToMany: - break; - } - } - return ret; - - bool IsCascade(Type entityType, object entity, bool isadd) - { - var stateKey = repository.Orm.GetEntityKeyString(entityType, entity, false); - if (stateKey == null) return true; - stateKey = $"{stateKey}*|_,[,_|*{entityType.DisplayCsharp()}"; - if (states.ContainsKey(stateKey)) return false; - if (isadd) states.Add(stateKey, entity); - return true; - } - } - public TEntity InsertOrUpdate(TEntity entity) => MainRepository.InsertOrUpdate(entity); - public int UpdateCascade(IBaseRepository repository, Func> getOrAddRepository, IEnumerable entitys, Dictionary states) where T1 : class - { - return 0; - } - public int Delete(IEnumerable entitys) => MainRepository.Delete(entitys); - public int Delete(Expression> predicate) => MainRepository.Delete(predicate); - public List DeleteCascadeByDatabase(Expression> predicate) => MainRepository.DeleteCascadeByDatabase(predicate); - - public void SaveMany(TEntity entity, string propertyName) => MainRepository.SaveMany(entity, propertyName); - - public void BeginEdit(List data) => MainRepository.BeginEdit(data); - public int EndEdit(List data = null) => MainRepository.EndEdit(data); - - protected static void FetchAggregateRootNavigate(IFreeSql orm, Type entityType, Func callback, Dictionary ignores) - { - if (ignores.ContainsKey(entityType)) return; - ignores.Add(entityType, true); - var tb = orm.CodeFirst.GetTableByEntity(entityType); - foreach (var prop in tb.Properties.Values) - { - var tbref = tb.GetTableRef(prop.Name, false); - if (tbref == null) continue; - switch (tbref.RefType) - { - case TableRefType.OneToOne: - callback(tb.Type, tbref); - FetchAggregateRootNavigate(orm, tbref.RefEntityType, callback, ignores); - break; - case TableRefType.OneToMany: - callback(tb.Type, tbref); - FetchAggregateRootNavigate(orm, tbref.RefEntityType, callback, ignores); - break; - case TableRefType.ManyToMany: - callback(tb.Type, tbref); - FetchAggregateRootNavigate(orm, tbref.RefMiddleEntityType, callback, ignores); - break; - case TableRefType.PgArrayToMany: - break; - } - } - } - - } } diff --git a/FreeSql.Repository/AggregateRootRepositoryAsync.cs b/FreeSql.Repository/AggregateRootRepositoryAsync.cs new file mode 100644 index 00000000..fde1bd79 --- /dev/null +++ b/FreeSql.Repository/AggregateRootRepositoryAsync.cs @@ -0,0 +1,35 @@ +#if net40 +#else +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql +{ + partial class AggregateRootRepository + { + public Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => _repository.InsertAsync(entity, cancellationToken); + public Task> InsertAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repository.InsertAsync(entitys, cancellationToken); + public Task InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repository.InsertOrUpdateAsync(entity, cancellationToken); + public Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default) => _repository.SaveManyAsync(entity, propertyName, cancellationToken); + + public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repository.UpdateAsync(entity, cancellationToken); + public Task UpdateAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repository.UpdateAsync(entitys, cancellationToken); + + public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => _repository.DeleteAsync(entity, cancellationToken); + public Task DeleteAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repository.DeleteAsync(entitys, cancellationToken); + public Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) => _repository.DeleteAsync(predicate, cancellationToken); + public Task> DeleteCascadeByDatabaseAsync(Expression> predicate, CancellationToken cancellationToken = default) => _repository.DeleteCascadeByDatabaseAsync(predicate, cancellationToken); + + } +} +#endif \ No newline at end of file diff --git a/FreeSql.Repository/AggregateRootRepositorySync.cs b/FreeSql.Repository/AggregateRootRepositorySync.cs new file mode 100644 index 00000000..5d40f0ec --- /dev/null +++ b/FreeSql.Repository/AggregateRootRepositorySync.cs @@ -0,0 +1,257 @@ +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FreeSql +{ + partial class AggregateRootRepository + { + public TEntity Insert(TEntity entity) => InsertCascade(new[] { entity }).FirstOrDefault(); + public List Insert(IEnumerable entitys) => InsertCascade(entitys); + public TEntity InsertOrUpdate(TEntity entity) => InsertOrUpdateCascade(entity); + public int Update(TEntity entity) => UpdateCascade(new[] { entity }); + public int Update(IEnumerable entitys) => UpdateCascade(entitys); + public int Delete(TEntity entity) => DeleteCascade(new[] { entity }); + public int Delete(IEnumerable entitys) => DeleteCascade(entitys); + public int Delete(Expression> predicate) => DeleteCascade(Where(predicate).ToList()); + public List DeleteCascadeByDatabase(Expression> predicate) + { + var deletedOutput = new List(); + DeleteCascade(Where(predicate).ToList(), deletedOutput); + return deletedOutput; + } + public void SaveMany(TEntity entity, string propertyName) => SaveManyCascade(entity, propertyName); + + protected virtual List InsertCascade(IEnumerable entitys) + { + var repos = new Dictionary(); + try + { + return InsertCascadeStatic(_repository, GetChildRepository, entitys); + } + finally + { + DisposeChildRepositorys(); + _repository.FlushState(); + } + } + protected static List InsertCascadeStatic(IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys) { + Dictionary> ignores = new Dictionary>(); + Dictionary> repos = new Dictionary>(); + return LocalInsertCascade(rootRepository, rootEntitys); + + bool LocalCanCascade(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 LocalInsertCascade(IBaseRepository repository, IEnumerable entitys) where T1 : class + { + var ret = repository.Insert(entitys); + foreach (var entity in entitys) LocalCanCascade(repository.EntityType, entity, true); + + var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType); + foreach (var prop in table.Properties.Values) + { + var tbref = table.GetTableRef(prop.Name, false); + if (tbref == null) continue; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + var otoList = ret.Select(entity => + { + var otoItem = table.GetPropertyValue(entity, prop.Name); + if (LocalCanCascade(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); + LocalInsertCascade(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 (LocalCanCascade(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); + LocalInsertCascade(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); + LocalInsertCascade(repo, mtmMidList); + } + break; + case TableRefType.PgArrayToMany: + break; + } + } + return ret; + } + } + + protected virtual TEntity InsertOrUpdateCascade(TEntity entity) + { + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (entity == null) throw new ArgumentNullException(nameof(entity)); + 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 = UpdateCascade(new[] { entity }); + if (affrows > 0) return entity; + } + Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity); + return InsertCascade(new[] { entity }).FirstOrDefault(); + } + protected virtual int UpdateCascade(IEnumerable entitys) + { + List> insertLog = new List>(); + List>> updateLog = new List>>(); + List> deleteLog = new List>(); + 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.CompareEntityValueCascade(Orm, EntityType, state.Value, entity, null, insertLog, updateLog, deleteLog); + AttachCascade(entity); + } + return insertLog.Count + updateLog.Count + deleteLog.Count; + } + protected virtual int DeleteCascade(IEnumerable entitys, List deletedOutput = null) + { + List> insertLog = new List>(); + List>> updateLog = new List>>(); + List> deleteLog = new List>(); + foreach (var entity in entitys) + { + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + AggregateRootUtils.CompareEntityValueCascade(Orm, EntityType, entity, null, null, insertLog, updateLog, deleteLog); + _states.Remove(stateKey); + } + if (deletedOutput != null) deletedOutput.AddRange(deleteLog.Select(a => a.Item2)); + return deleteLog.Count; + } + + protected virtual void SaveManyCascade(TEntity entity, string propertyName) + { + List> insertLog = new List>(); + List>> updateLog = new List>>(); + List> deleteLog = new List>(); + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}"); + AggregateRootUtils.CompareEntityValueCascade(Orm, EntityType, state.Value, entity, propertyName, insertLog, updateLog, deleteLog); + AttachCascade(entity); + } + + protected List _dataEditing; + protected ConcurrentDictionary _statesEditing = new ConcurrentDictionary(); + public void BeginEdit(List data) + { + if (data == null) return; + var table = Orm.CodeFirst.GetTableByEntity(EntityType); + if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotEdit_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, data.First()))); + _statesEditing.Clear(); + _dataEditing = data; + foreach (var item in data) + { + var key = Orm.GetEntityKeyString(EntityType, item, false); + if (string.IsNullOrEmpty(key)) continue; + + _statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) => + { + AggregateRootUtils.MapEntityValueCascade(Orm, EntityType, item, ov.Value); + ov.Time = DateTime.Now; + return ov; + }); + } + } + public int EndEdit(List data = null) + { + if (data == null) data = _dataEditing; + if (data == null) return 0; + List> insertLog = new List>(); + List>> updateLog = new List>>(); + List> deleteLog = new List>(); + try + { + var addList = new List(); + var ediList = new List(); + foreach (var item in data) + { + var key = Orm.GetEntityKeyString(EntityType, item, false); + if (_statesEditing.TryRemove(key, out var state) == false) + { + insertLog.Add(NativeTuple.Create(EntityType, (object)item)); + continue; + } + _states[key] = state; + AggregateRootUtils.CompareEntityValueCascade(Orm, EntityType, state.Value, item, null, insertLog, updateLog, deleteLog); + } + foreach (var item in _statesEditing.Values.OrderBy(a => a.Time)) + { + AggregateRootUtils.CompareEntityValueCascade(Orm, EntityType, item, null, null, insertLog, updateLog, deleteLog); + } + } + finally + { + _dataEditing = null; + _statesEditing.Clear(); + } + return insertLog.Count + updateLog.Count + deleteLog.Count; + } + + } +} diff --git a/FreeSql.Repository/AggregateRootUtils.cs b/FreeSql.Repository/AggregateRootUtils.cs index 9ec6a78c..a391b71e 100644 --- a/FreeSql.Repository/AggregateRootUtils.cs +++ b/FreeSql.Repository/AggregateRootUtils.cs @@ -1,4 +1,4 @@ -using FreeSql.Aop; +using FreeSql; using FreeSql.Extensions.EntityUtil; using FreeSql.Internal; using FreeSql.Internal.Model; @@ -8,20 +8,32 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Linq.Expressions; using System.Reflection; -using System.Text; static class AggregateRootUtils { - - public static void CompareEntityValueCascade(IFreeSql fsql, Type entityType, object entityBefore, object entityAfter, - List> insertLog, + public static void CompareEntityValueCascade(IFreeSql fsql, Type entityType, object entityBefore, object entityAfter, string navigatePropertyName, + List> insertLog, List>> updateLog, List> deleteLog) { if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType(); var table = fsql.CodeFirst.GetTableByEntity(entityType); + if (entityBefore == null && entityAfter == null) return; + if (entityBefore == null && entityAfter != null) + { + insertLog.Add(NativeTuple.Create(entityType, entityAfter)); + return; + } + if (entityBefore != null && entityAfter == null) + { + deleteLog.Add(NativeTuple.Create(entityType, entityBefore)); + EachNavigateCascade(fsql, entityType, entityBefore, (path, tr, ct, stackvs) => + { + deleteLog.Add(NativeTuple.Create(ct, stackvs.First())); + }); + return; + } var changes = new List(); foreach (var col in table.ColumnsByCs.Values) { @@ -42,27 +54,13 @@ static class AggregateRootUtils { var tbref = table.GetTableRef(prop.Name, false); if (tbref == null) continue; + if (navigatePropertyName != null && prop.Name != navigatePropertyName) continue; var propvalBefore = table.GetPropertyValue(entityBefore, prop.Name); var propvalAfter = table.GetPropertyValue(entityBefore, prop.Name); switch (tbref.RefType) { case TableRefType.OneToOne: - if (propvalBefore == null && propvalAfter == null) return; - if (propvalBefore == null && propvalAfter != null) - { - insertLog.Add(NativeTuple.Create(tbref.RefEntityType, propvalAfter)); - return; - } - if (propvalBefore != null && propvalAfter == null) - { - deleteLog.Add(NativeTuple.Create(tbref.RefEntityType, propvalBefore)); - EachNavigateCascade(fsql, tbref.RefEntityType, propvalBefore, (path, tr, ct, stackvs) => - { - deleteLog.Add(NativeTuple.Create(ct, stackvs.First())); - }); - return; - } - CompareEntityValueCascade(fsql, tbref.RefEntityType, propvalBefore, propvalAfter, insertLog, updateLog, deleteLog); + CompareEntityValueCascade(fsql, tbref.RefEntityType, propvalBefore, propvalAfter, null, insertLog, updateLog, deleteLog); break; case TableRefType.OneToMany: LocalCompareEntityValueCollection(tbref, propvalBefore as IEnumerable, propvalAfter as IEnumerable); @@ -84,7 +82,7 @@ static class AggregateRootUtils if (collectionBefore == null && collectionAfter == null) return; if (collectionBefore == null && collectionAfter != null) { - foreach(var item in collectionAfter) + foreach (var item in collectionAfter) insertLog.Add(NativeTuple.Create(elementType, item)); return; } @@ -135,7 +133,7 @@ static class AggregateRootUtils } } foreach (var key in dictBefore.Keys) - CompareEntityValueCascade(fsql, elementType, dictBefore[key], dictAfter[key], insertLog, updateLog, deleteLog); + CompareEntityValueCascade(fsql, elementType, dictBefore[key], dictAfter[key], null, insertLog, updateLog, deleteLog); } } public static void EachNavigateCascade(IFreeSql fsql, Type rootType, object rootEntity, Action> callback)