From ffe5f5f004147de9caab3cfb045f82edc91cf6f6 Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Wed, 4 May 2022 16:28:35 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20DbSet/Repository=20Del?= =?UTF-8?q?eteCascadeAsync=20=E5=BC=82=E6=AD=A5=E6=96=B9=E6=B3=95=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FreeSql.DbContext/DbSet/DbSetAsync.cs | 154 ++++++++++++++++++ FreeSql.DbContext/FreeSql.DbContext.xml | 5 + .../Repository/BaseRepositoryAsync.cs | 4 + .../Repository/Repository/IBaseRepository.cs | 4 + 4 files changed, 167 insertions(+) diff --git a/FreeSql.DbContext/DbSet/DbSetAsync.cs b/FreeSql.DbContext/DbSet/DbSetAsync.cs index 4fcbe3d2..9d428528 100644 --- a/FreeSql.DbContext/DbSet/DbSetAsync.cs +++ b/FreeSql.DbContext/DbSet/DbSetAsync.cs @@ -1,4 +1,5 @@ using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal.Model; using System; using System.Collections; using System.Collections.Concurrent; @@ -493,6 +494,159 @@ namespace FreeSql } } #endregion + + #region RemoveCascadeAsync + public Task> RemoveCascadeAsync(TEntity data, CancellationToken cancellationToken = default) => RemoveRangeCascadeAsync(new[] { data }, cancellationToken); + public Task> RemoveCascadeAsync(Expression> predicate, CancellationToken cancellationToken = default) => RemoveRangeCascadeAsync(Select.Where(predicate).ToList(), cancellationToken); + async public Task> RemoveRangeCascadeAsync(IEnumerable data, CancellationToken cancellationToken = default) + { + var returnDeleted = new List(); + if (data?.Any() != true) return returnDeleted; + await DbContextFlushCommandAsync(cancellationToken); + var fsql = _db.Orm; + if (LocalGetNavigates(_table).Any() == false) + { + if (CanRemove(data, true) == false) return returnDeleted; + foreach (var item in data) //防止清除 Identity/Guid + { + var state = CreateEntityState(item); + _states.TryRemove(state.Key, out var trystate); + + EnqueueToDbContext(DbContext.EntityChangeType.Delete, state); + } + await DbContextFlushCommandAsync(cancellationToken); + returnDeleted.AddRange(data.Select(a => (object)a)); + return returnDeleted; + } + + var commonUtils = (fsql.Select() as Internal.CommonProvider.Select0Provider)._commonUtils; + var eachdic = new Dictionary(); + var rootItems = data.Select(a => (object)a).ToArray(); + var rootDbSet = _db.Set(); + rootDbSet.AsType(_table.Type); + rootDbSet.AttachRange(rootItems); + await LocalEachAsync(rootDbSet, rootItems, true); + return returnDeleted; + + List> LocalGetNavigates(TableInfo tb) + { + return tb.Properties.Where(a => tb.ColumnsByCs.ContainsKey(a.Key) == false) + .Select(a => new NativeTuple(tb.GetTableRef(a.Key, false), a.Value)) + .Where(a => a.Item1 != null && a.Item1.RefType != TableRefType.ManyToOne) + .ToList(); + } + async Task LocalEachAsync(DbSet dbset, IEnumerable items, bool isOneToOne) + { + items = items?.Where(item => + { + var itemkeyStr = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetEntityKeyString(fsql, dbset.EntityType, item, false); + var eachdicKey = $"{dbset.EntityType.FullName},{itemkeyStr}"; + if (eachdic.ContainsKey(eachdicKey)) return false; + eachdic.Add(eachdicKey, true); + return true; + }).ToList(); + if (items?.Any() != true) return; + + var tb = fsql.CodeFirst.GetTableByEntity(dbset.EntityType); + var navs = LocalGetNavigates(tb); + + var otos = navs.Where(a => a.Item1.RefType == TableRefType.OneToOne).ToList(); + if (isOneToOne && otos.Any()) + { + foreach (var oto in otos) + { + var childTable = fsql.CodeFirst.GetTableByEntity(oto.Item1.RefEntityType); + var childDbSet = _db.Set(); + childDbSet.AsType(oto.Item1.RefEntityType); + var refitems = items.Select(item => + { + var refitem = oto.Item1.RefEntityType.CreateInstanceGetDefaultValue(); + for (var a = 0; a < oto.Item1.Columns.Count; a++) + { + var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, oto.Item1.Columns[a].CsName); + FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, refitem, oto.Item1.RefColumns[a].CsName, colval); + } + return refitem; + }).ToList(); + var childs = await childDbSet.Select.Where(commonUtils.WhereItems(oto.Item1.RefColumns.ToArray(), "a.", refitems)).ToListAsync(false, cancellationToken); + await LocalEachAsync(childDbSet, childs, false); + } + } + + var otms = navs.Where(a => a.Item1.RefType == TableRefType.OneToMany).ToList(); + if (otms.Any()) + { + foreach (var otm in otms) + { + var childTable = fsql.CodeFirst.GetTableByEntity(otm.Item1.RefEntityType); + var childDbSet = _db.Set(); + childDbSet.AsType(otm.Item1.RefEntityType); + var refitems = items.Select(item => + { + var refitem = otm.Item1.RefEntityType.CreateInstanceGetDefaultValue(); + for (var a = 0; a < otm.Item1.Columns.Count; a++) + { + var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, otm.Item1.Columns[a].CsName); + FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, refitem, otm.Item1.RefColumns[a].CsName, colval); + } + return refitem; + }).ToList(); + var childs = await childDbSet.Select.Where(commonUtils.WhereItems(otm.Item1.RefColumns.ToArray(), "a.", refitems)).ToListAsync(false, cancellationToken); + await LocalEachAsync(childDbSet, childs, true); + } + } + + var mtms = navs.Where(a => a.Item1.RefType == TableRefType.ManyToMany).ToList(); + if (mtms.Any()) + { + foreach (var mtm in mtms) + { + var childTable = fsql.CodeFirst.GetTableByEntity(mtm.Item1.RefMiddleEntityType); + var childDbSet = _db.Set(); + childDbSet.AsType(mtm.Item1.RefMiddleEntityType); + var miditems = items.Select(item => + { + var refitem = mtm.Item1.RefMiddleEntityType.CreateInstanceGetDefaultValue(); + for (var a = 0; a < mtm.Item1.Columns.Count; a++) + { + var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, mtm.Item1.Columns[a].CsName); + FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, refitem, mtm.Item1.MiddleColumns[a].CsName, colval); + } + return refitem; + }).ToList(); + var childs = await childDbSet.Select.Where(commonUtils.WhereItems(mtm.Item1.MiddleColumns.Take(mtm.Item1.Columns.Count).ToArray(), "a.", miditems)).ToListAsync(false, cancellationToken); + await LocalEachAsync(childDbSet, childs, true); + } + } + + if (dbset == rootDbSet) + { + if (CanRemove(data, true) == false) return; + foreach (var item in data) //防止清除 Identity/Guid + { + var state = CreateEntityState(item); + _states.TryRemove(state.Key, out var trystate); + + EnqueueToDbContext(DbContext.EntityChangeType.Delete, state); + } + await DbContextFlushCommandAsync(cancellationToken); + } + else + { + if (dbset.CanRemove(items, true) == false) return; + foreach (var item in items) //防止清除 Identity/Guid + { + var state = dbset.CreateEntityState(item); + dbset._states.TryRemove(state.Key, out var trystate); + + dbset.EnqueueToDbContext(DbContext.EntityChangeType.Delete, state); + } + await DbContextFlushCommandAsync(cancellationToken); + } + returnDeleted.AddRange(items); + } + } + #endregion } } #endif \ No newline at end of file diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index 75091f80..c9355f4b 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -105,6 +105,11 @@ - 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录 + + + 因增加支持 OneToOne 级联保存,请了解机制,已改名为 EnableAddOrUpdateNavigate + + 使用无参数化设置(对应 IInsert/IUpdate) diff --git a/FreeSql.DbContext/Repository/Repository/BaseRepositoryAsync.cs b/FreeSql.DbContext/Repository/Repository/BaseRepositoryAsync.cs index 959d8d8f..cb5df42c 100644 --- a/FreeSql.DbContext/Repository/Repository/BaseRepositoryAsync.cs +++ b/FreeSql.DbContext/Repository/Repository/BaseRepositoryAsync.cs @@ -32,6 +32,9 @@ namespace FreeSql _dbset.RemoveRange(entitys); return _db.SaveChangesAsync(cancellationToken); } + public virtual Task> DeleteCascade(TEntity entity, CancellationToken cancellationToken = default) => _dbset.RemoveCascadeAsync(entity, cancellationToken); + public virtual Task> DeleteCascade(IEnumerable entitys, CancellationToken cancellationToken = default) => _dbset.RemoveRangeCascadeAsync(entitys, cancellationToken); + public virtual Task> DeleteCascade(Expression> predicate, CancellationToken cancellationToken = default) => _dbset.RemoveCascadeAsync(predicate, cancellationToken); async public virtual Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) { @@ -73,6 +76,7 @@ namespace FreeSql partial class BaseRepository { + public virtual Task> DeleteCascadeAsync(TKey id, CancellationToken cancellationToken = default) => _dbset.RemoveCascadeAsync(Find(id), cancellationToken); public virtual Task DeleteAsync(TKey id, CancellationToken cancellationToken = default) => DeleteAsync(CheckTKeyAndReturnIdEntity(id), cancellationToken); public virtual Task FindAsync(TKey id, CancellationToken cancellationToken = default) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOneAsync(cancellationToken); public Task GetAsync(TKey id, CancellationToken cancellationToken = default) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOneAsync(cancellationToken); diff --git a/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs b/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs index 14dca340..03edcaa6 100644 --- a/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs +++ b/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs @@ -124,6 +124,9 @@ namespace FreeSql Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default); Task DeleteAsync(IEnumerable entitys, CancellationToken cancellationToken = default); Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default); + Task> DeleteCascade(TEntity entity, CancellationToken cancellationToken = default); + Task> DeleteCascade(IEnumerable entitys, CancellationToken cancellationToken = default); + Task> DeleteCascade(Expression> predicate, CancellationToken cancellationToken = default); #endif } @@ -140,6 +143,7 @@ namespace FreeSql Task GetAsync(TKey id, CancellationToken cancellationToken = default); Task FindAsync(TKey id, CancellationToken cancellationToken = default); Task DeleteAsync(TKey id, CancellationToken cancellationToken = default); + Task> DeleteCascadeAsync(TKey id, CancellationToken cancellationToken = default); #endif } } \ No newline at end of file