- 增加 DbSet/Repository DeleteCascade 级联删除功能;#609

This commit is contained in:
2881099
2022-05-04 14:54:29 +08:00
parent 88d7985d92
commit c9fa4d8983
12 changed files with 470 additions and 130 deletions

View File

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

View File

@ -209,16 +209,26 @@ namespace FreeSql
var tref = _table.GetTableRef(prop.Name, false); //防止非正常的导航属性报错
if (tref == null) return;
DbSet<object> refSet = null;
switch (tref.RefType)
{
case Internal.Model.TableRefType.OneToOne:
//var propValItem = GetItemValue(item, prop);
//for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
//{
// var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
// _db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
//}
//if (isAdd) await refSet.AddAsync(propValItem);
//else await refSet.AddOrUpdateAsync(propValItem);
//return;
case Internal.Model.TableRefType.ManyToOne:
return;
}
var propValEach = GetItemValue(item, prop) as IEnumerable;
if (propValEach == null) return;
DbSet<object> refSet = GetDbSetObject(tref.RefEntityType);
refSet = GetDbSetObject(tref.RefEntityType);
switch (tref.RefType)
{
case Internal.Model.TableRefType.ManyToMany:

View File

@ -221,16 +221,26 @@ namespace FreeSql
var tref = _table.GetTableRef(prop.Name, false); //防止非正常的导航属性报错
if (tref == null) return;
DbSet<object> refSet = null;
switch (tref.RefType)
{
case Internal.Model.TableRefType.OneToOne:
//var propValItem = GetItemValue(item, prop);
//for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
//{
// var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
// _db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
//}
//if (isAdd) refSet.Add(propValItem);
//else refSet.AddOrUpdate(propValItem);
//return;
case Internal.Model.TableRefType.ManyToOne:
return;
}
var propValEach = GetItemValue(item, prop) as IEnumerable;
if (propValEach == null) return;
DbSet<object> refSet = GetDbSetObject(tref.RefEntityType);
refSet = GetDbSetObject(tref.RefEntityType);
switch (tref.RefType)
{
case Internal.Model.TableRefType.ManyToMany:
@ -641,5 +651,163 @@ namespace FreeSql
return _db._affrows - beforeAffrows;
}
#endregion
#region RemoveCascade
/// <summary>
/// 根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public List<object> RemoveCascade(TEntity data) => RemoveRangeCascade(new[] { data });
public List<object> RemoveCascade(Expression<Func<TEntity, bool>> predicate) => RemoveRangeCascade(Select.Where(predicate).ToList());
public List<object> RemoveRangeCascade(IEnumerable<TEntity> data)
{
var returnDeleted = new List<object>();
if (data?.Any() != true) return returnDeleted;
DbContextFlushCommand();
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);
}
DbContextFlushCommand();
returnDeleted.AddRange(data.Select(a => (object)a));
return returnDeleted;
}
var commonUtils = (fsql.Select<object>() as Internal.CommonProvider.Select0Provider)._commonUtils;
var eachdic = new Dictionary<string, bool>();
var rootItems = data.Select(a => (object)a).ToArray();
var rootDbSet = _db.Set<object>();
rootDbSet.AsType(_table.Type);
rootDbSet.AttachRange(rootItems);
LocalEach(rootDbSet, rootItems, true);
return returnDeleted;
List<NativeTuple<TableRef, PropertyInfo>> LocalGetNavigates(TableInfo tb)
{
return tb.Properties.Where(a => tb.ColumnsByCs.ContainsKey(a.Key) == false)
.Select(a => new NativeTuple<TableRef, PropertyInfo>(tb.GetTableRef(a.Key, false), a.Value))
.Where(a => a.Item1 != null && a.Item1.RefType != TableRefType.ManyToOne)
.ToList();
}
void LocalEach(DbSet<object> dbset, IEnumerable<object> 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<object>();
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 = childDbSet.Select.Where(commonUtils.WhereItems(oto.Item1.RefColumns.ToArray(), "a.", refitems)).ToList();
LocalEach(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<object>();
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 = childDbSet.Select.Where(commonUtils.WhereItems(otm.Item1.RefColumns.ToArray(), "a.", refitems)).ToList();
LocalEach(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<object>();
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 = childDbSet.Select.Where(commonUtils.WhereItems(mtm.Item1.MiddleColumns.Take(mtm.Item1.Columns.Count).ToArray(), "a.", miditems)).ToList();
LocalEach(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);
}
DbContextFlushCommand();
}
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);
}
dbset.DbContextFlushCommand();
}
returnDeleted.AddRange(items);
}
}
#endregion
}
}

View File

@ -99,7 +99,7 @@
- 保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作?<para></para>
<para></para>
【多对多】模型下,我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(*注意不会更新)<para></para>
- 属性集合为空时,删除他们的所有关联数据(中间表)<para></para>
- 属性集合为空时(!=null),删除他们的所有关联数据(中间表)<para></para>
- 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录
</summary>
</member>
@ -209,6 +209,13 @@
<param name="data">可选参数:手工传递最终的 data 值进行对比<para></para>默认:如果不传递,则使用 BeginEdit 传入的 data 引用进行对比</param>
<returns></returns>
</member>
<member name="M:FreeSql.DbSet`1.RemoveCascade(`0)">
<summary>
根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据
</summary>
<param name="data"></param>
<returns></returns>
</member>
<member name="M:FreeSql.Extensions.EfCoreFluentApi.EfCoreColumnFluent.Help">
<summary>
使用 FreeSql FluentApi 方法,当 EFCore FluentApi 方法无法表示的时候使用
@ -346,6 +353,13 @@
<param name="entity">实体对象</param>
<param name="propertyName">属性名</param>
</member>
<member name="M:FreeSql.IBaseRepository`1.DeleteCascade(`0)">
<summary>
根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据
</summary>
<param name="entity"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IBaseRepository`1.BeginEdit(System.Collections.Generic.List{`0})">
<summary>
开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行<para></para>
@ -538,5 +552,14 @@
<param name="that"></param>
<returns></returns>
</member>
<member name="M:Microsoft.Extensions.DependencyInjection.FreeSqlRepositoryDependencyInjection.AddFreeRepository(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Action{FreeSql.FluentDataFilter},System.Reflection.Assembly[])">
<summary>
批量注入 Repository可以参考代码自行调整
</summary>
<param name="services"></param>
<param name="globalDataFilter"></param>
<param name="assemblies"></param>
<returns></returns>
</member>
</members>
</doc>

View File

@ -94,6 +94,9 @@ namespace FreeSql
_dbset.RemoveRange(entitys);
return _db.SaveChanges();
}
public List<object> DeleteCascade(TEntity entity) => _dbset.RemoveCascade(entity);
public List<object> DeleteCascade(IEnumerable<TEntity> entitys) => _dbset.RemoveRangeCascade(entitys);
public List<object> DeleteCascade(Expression<Func<TEntity, bool>> predicate) => _dbset.RemoveCascade(predicate);
public virtual TEntity Insert(TEntity entity)
{
@ -189,6 +192,7 @@ namespace FreeSql
return ret;
}
public virtual List<object> DeleteCascade(TKey id) => _dbset.RemoveCascade(Find(id));
public virtual int Delete(TKey id) => Delete(CheckTKeyAndReturnIdEntity(id));
public virtual TEntity Find(TKey id) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOne();
public TEntity Get(TKey id) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOne();

View File

@ -85,6 +85,14 @@ namespace FreeSql
int Delete(TEntity entity);
int Delete(IEnumerable<TEntity> entitys);
int Delete(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
List<object> DeleteCascade(TEntity entity);
List<object> DeleteCascade(IEnumerable<TEntity> entitys);
List<object> DeleteCascade(Expression<Func<TEntity, bool>> predicate);
/// <summary>
/// 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行<para></para>
@ -125,6 +133,7 @@ namespace FreeSql
TEntity Get(TKey id);
TEntity Find(TKey id);
int Delete(TKey id);
List<object> DeleteCascade(TKey id);
#if net40
#else