using FreeSql.Extensions.EntityUtil; using FreeSql.Internal; using FreeSql.Internal.Model; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading; using System.Threading.Tasks; namespace FreeSql { partial class AggregateRootRepository { #region BeginEdit/EndEdit List _dataEditing; ConcurrentDictionary _statesEditing = new ConcurrentDictionary(); public virtual void BeginEdit(List data) { if (data == null) return; var table = Orm.CodeFirst.GetTableByEntity(EntityType); if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotEdit_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, data.First()))); _statesEditing.Clear(); _dataEditing = data; foreach (var item in data) { var key = Orm.GetEntityKeyString(EntityType, item, false); if (string.IsNullOrEmpty(key)) continue; _statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) => { AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, item, ov.Value); ov.Time = DateTime.Now; return ov; }); } } public virtual int EndEdit(List data = null) { if (data == null) data = _dataEditing; if (data == null) return 0; var tracking = new AggregateRootTrackingChangeInfo(); try { var addList = new List(); var ediList = new List(); foreach (var item in data) { var key = Orm.GetEntityKeyString(EntityType, item, false); if (_statesEditing.TryRemove(key, out var state) == false) { tracking.InsertLog.Add(NativeTuple.Create(EntityType, (object)item)); continue; } _states[key] = state; AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, item, null, tracking); } foreach (var item in _statesEditing.Values.OrderBy(a => a.Time)) AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, item, null, null, tracking); return SaveTrackingChange(tracking); } finally { _dataEditing = null; _statesEditing.Clear(); } } #endregion #region Insert public virtual TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault(); public virtual List Insert(IEnumerable entitys) { var repos = new Dictionary(); try { var ret = InsertWithinBoundaryStatic(_boundaryName, _repository, GetChildRepository, entitys, out var affrows); Attach(ret); return ret; } finally { DisposeChildRepositorys(); _repository.FlushState(); } } static List InsertWithinBoundaryStatic(string boundaryName, IBaseRepository rootRepository, Func> getChildRepository, IEnumerable rootEntitys, out int affrows) where T1 : class { Dictionary> ignores = new Dictionary>(); Dictionary> repos = new Dictionary>(); var localAffrows = 0; try { rootRepository.DbContextOptions.EnableCascadeSave = false; return LocalInsert(rootRepository, rootEntitys, true); } finally { affrows = localAffrows; } bool LocalCanInsert(Type entityType, object entity, bool isadd) { var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false); if (stateKey == null) return true; if (ignores.TryGetValue(entityType, out var stateKeys) == false) { if (isadd) { ignores.Add(entityType, stateKeys = new Dictionary()); stateKeys.Add(stateKey, true); } return true; } if (stateKeys.ContainsKey(stateKey) == false) { if (isadd) stateKeys.Add(stateKey, true); return true; } return false; } List LocalInsert(IBaseRepository repository, IEnumerable entitys, bool cascade) where T2 : class { var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType); if (table.Primarys.Any(col => col.Attribute.IsIdentity)) { foreach (var entity in entitys) repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity); } var ret = repository.Insert(entitys); localAffrows += ret.Count; foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true); if (cascade == false) return ret; foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) { var tbref = tr.Value; if (tbref.Exception != null) continue; if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName); if (boundaryAttr?.Break == true) continue; switch (tbref.RefType) { case TableRefType.OneToOne: var otoList = ret.Select(entity => { var otoItem = table.GetPropertyValue(entity, prop.Name); if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null; AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem); return otoItem; }).Where(entity => entity != null).ToArray(); if (otoList.Any()) { var repo = getChildRepository(tbref.RefEntityType); LocalInsert(repo, otoList, boundaryAttr?.BreakThen != true); } break; case TableRefType.OneToMany: var otmList = ret.Select(entity => { var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable; if (otmEach == null) return null; var otmItems = new List(); foreach (var otmItem in otmEach) { if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue; otmItems.Add(otmItem); } AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems); return otmItems; }).Where(entity => entity != null).SelectMany(entity => entity).ToArray(); if (otmList.Any()) { var repo = getChildRepository(tbref.RefEntityType); LocalInsert(repo, otmList, boundaryAttr?.BreakThen != true); } break; case TableRefType.ManyToMany: var mtmMidList = new List(); ret.ForEach(entity => { var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop); if (mids != null) mtmMidList.AddRange(mids); }); if (mtmMidList.Any()) { var repo = getChildRepository(tbref.RefMiddleEntityType); LocalInsert(repo, mtmMidList, false); } break; case TableRefType.PgArrayToMany: case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改 break; } } return ret; } } #endregion public virtual TEntity InsertOrUpdate(TEntity entity) { var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); if (entity == null) throw new ArgumentNullException(nameof(entity)); var table = Orm.CodeFirst.GetTableByEntity(EntityType); if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity))); var flagExists = ExistsInStates(entity); if (flagExists == false) { var olddata = Select.WhereDynamic(entity).First(); flagExists = olddata != null; } if (flagExists == true) { var affrows = Update(entity); return entity; } if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length) Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity); return Insert(entity); } public virtual int Update(TEntity entity) => Update(new[] { entity }); public virtual int Update(IEnumerable entitys) { var tracking = new AggregateRootTrackingChangeInfo(); foreach(var entity in entitys) { var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}"); AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking); } var affrows = SaveTrackingChange(tracking); foreach (var entity in entitys) Attach(entity); return affrows; } public virtual int Delete(TEntity entity) => DeleteWithinBoundary(new[] { entity }, null); public virtual int Delete(IEnumerable entitys) => DeleteWithinBoundary(entitys, null); public virtual int Delete(Expression> predicate) => DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), null); public virtual List DeleteCascadeByDatabase(Expression> predicate) { var deletedOutput = new List(); DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), deletedOutput); return deletedOutput; } int DeleteWithinBoundary(IEnumerable entitys, List deletedOutput) { var tracking = new AggregateRootTrackingChangeInfo(); foreach (var entity in entitys) { var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking); _states.Remove(stateKey); } var affrows = 0; for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--) { var delete = Orm.Delete().AsType(tracking.DeleteLog[a].Item1); if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old)); affrows += delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows(); if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2); UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x => new DbContext.EntityChangeReport.ChangeInfo { Type = DbContext.EntityChangeType.Delete, EntityType = tracking.DeleteLog[a].Item1, Object = x })); } return affrows; } public virtual void SaveMany(TEntity entity, string propertyName) { var tracking = new AggregateRootTrackingChangeInfo(); var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}"); AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking); SaveTrackingChange(tracking); Attach(entity); //应该只存储 propertyName 内容 } int SaveTrackingChange(AggregateRootTrackingChangeInfo tracking) { var affrows = 0; DisposeChildRepositorys(); var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray()); foreach (var il in insertLogDict) { var repo = GetChildRepository(il.Key); InsertWithinBoundaryStatic(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut); affrows += affrowsOut; } for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--) { var delete = Orm.Delete().AsType(tracking.DeleteLog[a].Item1); if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old)); affrows += delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows(); UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x => new DbContext.EntityChangeReport.ChangeInfo { Type = DbContext.EntityChangeType.Delete, EntityType = tracking.DeleteLog[a].Item1, Object = x })); } var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => new { BeforeObject = b.Item2, AfterObject = b.Item3, UpdateColumns = b.Item4, UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c)) }).ToArray()); var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.GroupBy(b => b.UpdateColumnsString).ToDictionary(b => b.Key, b => a.Value.Where(c => c.UpdateColumnsString == b.Key).ToArray())); foreach (var dl in updateLogDict2) { foreach (var dl2 in dl.Value) { var update = Orm.Update().AsType(dl.Key); if (_asTableRule != null) update.AsTable(old => _asTableRule(dl.Key, old)); affrows += update .SetSource(dl2.Value.Select(a => a.AfterObject).ToArray()) .UpdateColumns(dl2.Value.First().UpdateColumns.ToArray()) .ExecuteAffrows(); UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x => new DbContext.EntityChangeReport.ChangeInfo { Type = DbContext.EntityChangeType.Update, EntityType = dl.Key, Object = x.AfterObject, BeforeObject = x.BeforeObject })); } } DisposeChildRepositorys(); return affrows; } } }