Skip to content

Commit c9fa4d8

Browse files
committed
- 增加 DbSet/Repository DeleteCascade 级联删除功能;#609
1 parent 88d7985 commit c9fa4d8

File tree

12 files changed

+470
-130
lines changed

12 files changed

+470
-130
lines changed

FreeSql.DbContext/DbContext/DbContextOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class DbContextOptions
1717
/// - 保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作?<para></para>
1818
/// <para></para>
1919
/// 【多对多】模型下,我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(*注意不会更新)<para></para>
20-
/// - 属性集合为空时,删除他们的所有关联数据(中间表)<para></para>
20+
/// - 属性集合为空时(!=null),删除他们的所有关联数据(中间表)<para></para>
2121
/// - 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录
2222
/// </summary>
2323
public bool EnableAddOrUpdateNavigateList { get; set; } = false;

FreeSql.DbContext/DbSet/DbSetAsync.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,16 +209,26 @@ async Task AddOrUpdateNavigateListAsync(TEntity item, bool isAdd, string propert
209209

210210
var tref = _table.GetTableRef(prop.Name, false); //防止非正常的导航属性报错
211211
if (tref == null) return;
212+
DbSet<object> refSet = null;
212213
switch (tref.RefType)
213214
{
214215
case Internal.Model.TableRefType.OneToOne:
216+
//var propValItem = GetItemValue(item, prop);
217+
//for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
218+
//{
219+
// var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
220+
// _db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
221+
//}
222+
//if (isAdd) await refSet.AddAsync(propValItem);
223+
//else await refSet.AddOrUpdateAsync(propValItem);
224+
//return;
215225
case Internal.Model.TableRefType.ManyToOne:
216226
return;
217227
}
218228

219229
var propValEach = GetItemValue(item, prop) as IEnumerable;
220230
if (propValEach == null) return;
221-
DbSet<object> refSet = GetDbSetObject(tref.RefEntityType);
231+
refSet = GetDbSetObject(tref.RefEntityType);
222232
switch (tref.RefType)
223233
{
224234
case Internal.Model.TableRefType.ManyToMany:

FreeSql.DbContext/DbSet/DbSetSync.cs

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,26 @@ void AddOrUpdateNavigateList(TEntity item, bool isAdd, string propertyName)
221221

222222
var tref = _table.GetTableRef(prop.Name, false); //防止非正常的导航属性报错
223223
if (tref == null) return;
224+
DbSet<object> refSet = null;
224225
switch (tref.RefType)
225226
{
226227
case Internal.Model.TableRefType.OneToOne:
228+
//var propValItem = GetItemValue(item, prop);
229+
//for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
230+
//{
231+
// var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
232+
// _db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
233+
//}
234+
//if (isAdd) refSet.Add(propValItem);
235+
//else refSet.AddOrUpdate(propValItem);
236+
//return;
227237
case Internal.Model.TableRefType.ManyToOne:
228238
return;
229239
}
230240

231241
var propValEach = GetItemValue(item, prop) as IEnumerable;
232242
if (propValEach == null) return;
233-
DbSet<object> refSet = GetDbSetObject(tref.RefEntityType);
243+
refSet = GetDbSetObject(tref.RefEntityType);
234244
switch (tref.RefType)
235245
{
236246
case Internal.Model.TableRefType.ManyToMany:
@@ -641,5 +651,163 @@ public int EndEdit(List<TEntity> data = null)
641651
return _db._affrows - beforeAffrows;
642652
}
643653
#endregion
654+
655+
#region RemoveCascade
656+
/// <summary>
657+
/// 根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据
658+
/// </summary>
659+
/// <param name="data"></param>
660+
/// <returns></returns>
661+
public List<object> RemoveCascade(TEntity data) => RemoveRangeCascade(new[] { data });
662+
public List<object> RemoveCascade(Expression<Func<TEntity, bool>> predicate) => RemoveRangeCascade(Select.Where(predicate).ToList());
663+
public List<object> RemoveRangeCascade(IEnumerable<TEntity> data)
664+
{
665+
var returnDeleted = new List<object>();
666+
if (data?.Any() != true) return returnDeleted;
667+
DbContextFlushCommand();
668+
var fsql = _db.Orm;
669+
if (LocalGetNavigates(_table).Any() == false)
670+
{
671+
if (CanRemove(data, true) == false) return returnDeleted;
672+
foreach (var item in data) //防止清除 Identity/Guid
673+
{
674+
var state = CreateEntityState(item);
675+
_states.TryRemove(state.Key, out var trystate);
676+
677+
EnqueueToDbContext(DbContext.EntityChangeType.Delete, state);
678+
}
679+
DbContextFlushCommand();
680+
returnDeleted.AddRange(data.Select(a => (object)a));
681+
return returnDeleted;
682+
}
683+
684+
var commonUtils = (fsql.Select<object>() as Internal.CommonProvider.Select0Provider)._commonUtils;
685+
var eachdic = new Dictionary<string, bool>();
686+
var rootItems = data.Select(a => (object)a).ToArray();
687+
var rootDbSet = _db.Set<object>();
688+
rootDbSet.AsType(_table.Type);
689+
rootDbSet.AttachRange(rootItems);
690+
LocalEach(rootDbSet, rootItems, true);
691+
return returnDeleted;
692+
693+
List<NativeTuple<TableRef, PropertyInfo>> LocalGetNavigates(TableInfo tb)
694+
{
695+
return tb.Properties.Where(a => tb.ColumnsByCs.ContainsKey(a.Key) == false)
696+
.Select(a => new NativeTuple<TableRef, PropertyInfo>(tb.GetTableRef(a.Key, false), a.Value))
697+
.Where(a => a.Item1 != null && a.Item1.RefType != TableRefType.ManyToOne)
698+
.ToList();
699+
}
700+
void LocalEach(DbSet<object> dbset, IEnumerable<object> items, bool isOneToOne)
701+
{
702+
items = items?.Where(item =>
703+
{
704+
var itemkeyStr = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetEntityKeyString(fsql, dbset.EntityType, item, false);
705+
var eachdicKey = $"{dbset.EntityType.FullName},{itemkeyStr}";
706+
if (eachdic.ContainsKey(eachdicKey)) return false;
707+
eachdic.Add(eachdicKey, true);
708+
return true;
709+
}).ToList();
710+
if (items?.Any() != true) return;
711+
712+
var tb = fsql.CodeFirst.GetTableByEntity(dbset.EntityType);
713+
var navs = LocalGetNavigates(tb);
714+
715+
var otos = navs.Where(a => a.Item1.RefType == TableRefType.OneToOne).ToList();
716+
if (isOneToOne && otos.Any())
717+
{
718+
foreach (var oto in otos)
719+
{
720+
var childTable = fsql.CodeFirst.GetTableByEntity(oto.Item1.RefEntityType);
721+
var childDbSet = _db.Set<object>();
722+
childDbSet.AsType(oto.Item1.RefEntityType);
723+
var refitems = items.Select(item =>
724+
{
725+
var refitem = oto.Item1.RefEntityType.CreateInstanceGetDefaultValue();
726+
for (var a = 0; a < oto.Item1.Columns.Count; a++)
727+
{
728+
var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, oto.Item1.Columns[a].CsName);
729+
FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, refitem, oto.Item1.RefColumns[a].CsName, colval);
730+
}
731+
return refitem;
732+
}).ToList();
733+
var childs = childDbSet.Select.Where(commonUtils.WhereItems(oto.Item1.RefColumns.ToArray(), "a.", refitems)).ToList();
734+
LocalEach(childDbSet, childs, false);
735+
}
736+
}
737+
738+
var otms = navs.Where(a => a.Item1.RefType == TableRefType.OneToMany).ToList();
739+
if (otms.Any())
740+
{
741+
foreach (var otm in otms)
742+
{
743+
var childTable = fsql.CodeFirst.GetTableByEntity(otm.Item1.RefEntityType);
744+
var childDbSet = _db.Set<object>();
745+
childDbSet.AsType(otm.Item1.RefEntityType);
746+
var refitems = items.Select(item =>
747+
{
748+
var refitem = otm.Item1.RefEntityType.CreateInstanceGetDefaultValue();
749+
for (var a = 0; a < otm.Item1.Columns.Count; a++)
750+
{
751+
var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, otm.Item1.Columns[a].CsName);
752+
FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, refitem, otm.Item1.RefColumns[a].CsName, colval);
753+
}
754+
return refitem;
755+
}).ToList();
756+
var childs = childDbSet.Select.Where(commonUtils.WhereItems(otm.Item1.RefColumns.ToArray(), "a.", refitems)).ToList();
757+
LocalEach(childDbSet, childs, true);
758+
}
759+
}
760+
761+
var mtms = navs.Where(a => a.Item1.RefType == TableRefType.ManyToMany).ToList();
762+
if (mtms.Any())
763+
{
764+
foreach (var mtm in mtms)
765+
{
766+
var childTable = fsql.CodeFirst.GetTableByEntity(mtm.Item1.RefMiddleEntityType);
767+
var childDbSet = _db.Set<object>();
768+
childDbSet.AsType(mtm.Item1.RefMiddleEntityType);
769+
var miditems = items.Select(item =>
770+
{
771+
var refitem = mtm.Item1.RefMiddleEntityType.CreateInstanceGetDefaultValue();
772+
for (var a = 0; a < mtm.Item1.Columns.Count; a++)
773+
{
774+
var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, mtm.Item1.Columns[a].CsName);
775+
FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, refitem, mtm.Item1.MiddleColumns[a].CsName, colval);
776+
}
777+
return refitem;
778+
}).ToList();
779+
var childs = childDbSet.Select.Where(commonUtils.WhereItems(mtm.Item1.MiddleColumns.Take(mtm.Item1.Columns.Count).ToArray(), "a.", miditems)).ToList();
780+
LocalEach(childDbSet, childs, true);
781+
}
782+
}
783+
784+
if (dbset == rootDbSet)
785+
{
786+
if (CanRemove(data, true) == false) return;
787+
foreach (var item in data) //防止清除 Identity/Guid
788+
{
789+
var state = CreateEntityState(item);
790+
_states.TryRemove(state.Key, out var trystate);
791+
792+
EnqueueToDbContext(DbContext.EntityChangeType.Delete, state);
793+
}
794+
DbContextFlushCommand();
795+
}
796+
else
797+
{
798+
if (dbset.CanRemove(items, true) == false) return;
799+
foreach (var item in items) //防止清除 Identity/Guid
800+
{
801+
var state = dbset.CreateEntityState(item);
802+
dbset._states.TryRemove(state.Key, out var trystate);
803+
804+
dbset.EnqueueToDbContext(DbContext.EntityChangeType.Delete, state);
805+
}
806+
dbset.DbContextFlushCommand();
807+
}
808+
returnDeleted.AddRange(items);
809+
}
810+
}
811+
#endregion
644812
}
645813
}

FreeSql.DbContext/FreeSql.DbContext.xml

Lines changed: 24 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

FreeSql.DbContext/Repository/Repository/BaseRepository.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ public virtual int Delete(IEnumerable<TEntity> entitys)
9494
_dbset.RemoveRange(entitys);
9595
return _db.SaveChanges();
9696
}
97+
public List<object> DeleteCascade(TEntity entity) => _dbset.RemoveCascade(entity);
98+
public List<object> DeleteCascade(IEnumerable<TEntity> entitys) => _dbset.RemoveRangeCascade(entitys);
99+
public List<object> DeleteCascade(Expression<Func<TEntity, bool>> predicate) => _dbset.RemoveCascade(predicate);
97100

98101
public virtual TEntity Insert(TEntity entity)
99102
{
@@ -189,6 +192,7 @@ TEntity CheckTKeyAndReturnIdEntity(TKey id)
189192
return ret;
190193
}
191194

195+
public virtual List<object> DeleteCascade(TKey id) => _dbset.RemoveCascade(Find(id));
192196
public virtual int Delete(TKey id) => Delete(CheckTKeyAndReturnIdEntity(id));
193197
public virtual TEntity Find(TKey id) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOne();
194198
public TEntity Get(TKey id) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOne();

FreeSql.DbContext/Repository/Repository/IBaseRepository.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ public interface IBaseRepository<TEntity> : IBaseRepository
8585
int Delete(TEntity entity);
8686
int Delete(IEnumerable<TEntity> entitys);
8787
int Delete(Expression<Func<TEntity, bool>> predicate);
88+
/// <summary>
89+
/// 根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据
90+
/// </summary>
91+
/// <param name="entity"></param>
92+
/// <returns></returns>
93+
List<object> DeleteCascade(TEntity entity);
94+
List<object> DeleteCascade(IEnumerable<TEntity> entitys);
95+
List<object> DeleteCascade(Expression<Func<TEntity, bool>> predicate);
8896

8997
/// <summary>
9098
/// 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行<para></para>
@@ -125,6 +133,7 @@ public interface IBaseRepository<TEntity, TKey> : IBaseRepository<TEntity>
125133
TEntity Get(TKey id);
126134
TEntity Find(TKey id);
127135
int Delete(TKey id);
136+
List<object> DeleteCascade(TKey id);
128137

129138
#if net40
130139
#else

0 commit comments

Comments
 (0)