diff --git a/FreeSql.DbContext/DbContext/DbContextOptions.cs b/FreeSql.DbContext/DbContext/DbContextOptions.cs index 25cd8df4..1263a0b7 100644 --- a/FreeSql.DbContext/DbContext/DbContextOptions.cs +++ b/FreeSql.DbContext/DbContext/DbContextOptions.cs @@ -17,7 +17,7 @@ namespace FreeSql /// - 保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作? /// /// 【多对多】模型下,我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(*注意不会更新) - /// - 属性集合为空时,删除他们的所有关联数据(中间表) + /// - 属性集合为空时(!=null),删除他们的所有关联数据(中间表) /// - 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录 /// public bool EnableAddOrUpdateNavigateList { get; set; } = false; diff --git a/FreeSql.DbContext/DbSet/DbSetAsync.cs b/FreeSql.DbContext/DbSet/DbSetAsync.cs index ece0f850..504d9282 100644 --- a/FreeSql.DbContext/DbSet/DbSetAsync.cs +++ b/FreeSql.DbContext/DbSet/DbSetAsync.cs @@ -209,16 +209,26 @@ namespace FreeSql var tref = _table.GetTableRef(prop.Name, false); //防止非正常的导航属性报错 if (tref == null) return; + DbSet 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 refSet = GetDbSetObject(tref.RefEntityType); + refSet = GetDbSetObject(tref.RefEntityType); switch (tref.RefType) { case Internal.Model.TableRefType.ManyToMany: diff --git a/FreeSql.DbContext/DbSet/DbSetSync.cs b/FreeSql.DbContext/DbSet/DbSetSync.cs index 54a8f216..a05ca9b6 100644 --- a/FreeSql.DbContext/DbSet/DbSetSync.cs +++ b/FreeSql.DbContext/DbSet/DbSetSync.cs @@ -221,16 +221,26 @@ namespace FreeSql var tref = _table.GetTableRef(prop.Name, false); //防止非正常的导航属性报错 if (tref == null) return; + DbSet 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 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 + /// + /// 根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据 + /// + /// + /// + public List RemoveCascade(TEntity data) => RemoveRangeCascade(new[] { data }); + public List RemoveCascade(Expression> predicate) => RemoveRangeCascade(Select.Where(predicate).ToList()); + public List RemoveRangeCascade(IEnumerable data) + { + var returnDeleted = new List(); + 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() 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); + LocalEach(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(); + } + void LocalEach(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 = 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(); + 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(); + 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 } } diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index da7ace6b..506d7964 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -99,7 +99,7 @@ - 保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作? 【多对多】模型下,我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(*注意不会更新) - - 属性集合为空时,删除他们的所有关联数据(中间表) + - 属性集合为空时(!=null),删除他们的所有关联数据(中间表) - 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录 @@ -209,6 +209,13 @@ 可选参数:手工传递最终的 data 值进行对比默认:如果不传递,则使用 BeginEdit 传入的 data 引用进行对比 + + + 根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据 + + + + 使用 FreeSql FluentApi 方法,当 EFCore FluentApi 方法无法表示的时候使用 @@ -346,6 +353,13 @@ 实体对象 属性名 + + + 根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据 + + + + 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行 @@ -538,5 +552,14 @@ + + + 批量注入 Repository,可以参考代码自行调整 + + + + + + diff --git a/FreeSql.DbContext/Repository/Repository/BaseRepository.cs b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs index 1faae1a2..c6771de4 100644 --- a/FreeSql.DbContext/Repository/Repository/BaseRepository.cs +++ b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs @@ -94,6 +94,9 @@ namespace FreeSql _dbset.RemoveRange(entitys); return _db.SaveChanges(); } + public List DeleteCascade(TEntity entity) => _dbset.RemoveCascade(entity); + public List DeleteCascade(IEnumerable entitys) => _dbset.RemoveRangeCascade(entitys); + public List DeleteCascade(Expression> predicate) => _dbset.RemoveCascade(predicate); public virtual TEntity Insert(TEntity entity) { @@ -189,6 +192,7 @@ namespace FreeSql return ret; } + public virtual List 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(); diff --git a/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs b/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs index e78a3db6..14dca340 100644 --- a/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs +++ b/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs @@ -85,6 +85,14 @@ namespace FreeSql int Delete(TEntity entity); int Delete(IEnumerable entitys); int Delete(Expression> predicate); + /// + /// 根据设置的导航属性,递归查询删除 OneToOne/OneToMany/ManyToMany 数据,并返回已删除的数据 + /// + /// + /// + List DeleteCascade(TEntity entity); + List DeleteCascade(IEnumerable entitys); + List DeleteCascade(Expression> predicate); /// /// 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行 @@ -125,6 +133,7 @@ namespace FreeSql TEntity Get(TKey id); TEntity Find(TKey id); int Delete(TKey id); + List DeleteCascade(TKey id); #if net40 #else diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext/RepositoryTests.cs b/FreeSql.Tests/FreeSql.Tests.DbContext/RepositoryTests.cs index 32403d20..404f8bc4 100644 --- a/FreeSql.Tests/FreeSql.Tests.DbContext/RepositoryTests.cs +++ b/FreeSql.Tests/FreeSql.Tests.DbContext/RepositoryTests.cs @@ -9,6 +9,252 @@ namespace FreeSql.Tests { public class RepositoryTests { + [Fact] + public void DeleteCascade() + { + var fsql = g.sqlite; + var groupRepo = fsql.GetRepository(); + var userRepo = fsql.GetRepository(); + //OneToOne + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + var user = new DeleteCascadeUser { Username = "admin01", Password = "pwd01" }; + userRepo.Insert(user); + var userExt = new DeleteCascadeUserExt { UserId = user.Id, Remark = "用户备注01" }; + Assert.Equal(1, fsql.Insert(userExt).ExecuteAffrows()); + var ret = userRepo.DeleteCascade(user); + Assert.Equal(2, ret.Count); + Assert.IsType(ret[0]); + Assert.Equal(userExt.UserId, (ret[0] as DeleteCascadeUserExt).UserId); + Assert.Equal(userExt.Remark, (ret[0] as DeleteCascadeUserExt).Remark); + Assert.IsType(ret[1]); + Assert.Equal(user.Id, (ret[1] as DeleteCascadeUser).Id); + Assert.Equal(user.Username, (ret[1] as DeleteCascadeUser).Username); + Assert.Equal(user.Password, (ret[1] as DeleteCascadeUser).Password); + + //OneToOne 先删除 UserExt + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + user = new DeleteCascadeUser { Username = "admin01", Password = "pwd01" }; + userRepo.Insert(user); + userExt = new DeleteCascadeUserExt { UserId = user.Id, Remark = "用户备注01" }; + Assert.Equal(1, fsql.Insert(userExt).ExecuteAffrows()); + ret = fsql.GetRepository().DeleteCascade(userExt); + Assert.Equal(2, ret.Count); + Assert.IsType(ret[1]); + Assert.Equal(userExt.UserId, (ret[1] as DeleteCascadeUserExt).UserId); + Assert.Equal(userExt.Remark, (ret[1] as DeleteCascadeUserExt).Remark); + Assert.IsType(ret[0]); + Assert.Equal(user.Id, (ret[0] as DeleteCascadeUser).Id); + Assert.Equal(user.Username, (ret[0] as DeleteCascadeUser).Username); + Assert.Equal(user.Password, (ret[0] as DeleteCascadeUser).Password); + + //OneToMany + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + groupRepo.DbContextOptions.EnableAddOrUpdateNavigateList = true; + var group = new DeleteCascadeUserGroup + { + GroupName = "group01", + Users = new List + { + new DeleteCascadeUser { Username = "admin01", Password = "pwd01" }, + new DeleteCascadeUser { Username = "admin02", Password = "pwd02" }, + new DeleteCascadeUser { Username = "admin03", Password = "pwd03" }, + } + }; + groupRepo.Insert(group); + Assert.Equal(group.Id, group.Users[0].GroupId); + Assert.Equal(group.Id, group.Users[1].GroupId); + Assert.Equal(group.Id, group.Users[2].GroupId); + var userExt0 = new DeleteCascadeUserExt { UserId = group.Users[0].Id, Remark = "用户备注01" }; + Assert.Equal(1, fsql.Insert(userExt0).ExecuteAffrows()); + var userExt1 = new DeleteCascadeUserExt { UserId = group.Users[1].Id, Remark = "用户备注02" }; + Assert.Equal(1, fsql.Insert(userExt1).ExecuteAffrows()); + var userExt2 = new DeleteCascadeUserExt { UserId = group.Users[2].Id, Remark = "用户备注03" }; + Assert.Equal(1, fsql.Insert(userExt2).ExecuteAffrows()); + ret = groupRepo.DeleteCascade(group); + Assert.Equal(7, ret.Count); + Assert.IsType(ret[0]); + Assert.Equal(userExt0.UserId, (ret[0] as DeleteCascadeUserExt).UserId); + Assert.Equal(userExt0.Remark, (ret[0] as DeleteCascadeUserExt).Remark); + Assert.IsType(ret[1]); + Assert.Equal(userExt1.UserId, (ret[1] as DeleteCascadeUserExt).UserId); + Assert.Equal(userExt1.Remark, (ret[1] as DeleteCascadeUserExt).Remark); + Assert.IsType(ret[2]); + Assert.Equal(userExt2.UserId, (ret[2] as DeleteCascadeUserExt).UserId); + Assert.Equal(userExt2.Remark, (ret[2] as DeleteCascadeUserExt).Remark); + Assert.IsType(ret[3]); + Assert.Equal(group.Users[0].Id, (ret[3] as DeleteCascadeUser).Id); + Assert.Equal(group.Users[0].Username, (ret[3] as DeleteCascadeUser).Username); + Assert.Equal(group.Users[0].Password, (ret[3] as DeleteCascadeUser).Password); + Assert.IsType(ret[4]); + Assert.Equal(group.Users[1].Id, (ret[4] as DeleteCascadeUser).Id); + Assert.Equal(group.Users[1].Username, (ret[4] as DeleteCascadeUser).Username); + Assert.Equal(group.Users[1].Password, (ret[4] as DeleteCascadeUser).Password); + Assert.IsType(ret[5]); + Assert.Equal(group.Users[2].Id, (ret[5] as DeleteCascadeUser).Id); + Assert.Equal(group.Users[2].Username, (ret[5] as DeleteCascadeUser).Username); + Assert.Equal(group.Users[2].Password, (ret[5] as DeleteCascadeUser).Password); + Assert.IsType(ret[6]); + Assert.Equal(group.Id, (ret[6] as DeleteCascadeUserGroup).Id); + Assert.Equal(group.GroupName, (ret[6] as DeleteCascadeUserGroup).GroupName); + + //ManyToMany + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + fsql.Delete().Where("1=1").ExecuteAffrows(); + var tags = new[] { + new DeleteCascadeTag { TagName = "tag01" }, + new DeleteCascadeTag { TagName = "tag02" }, + new DeleteCascadeTag { TagName = "tag03" }, + new DeleteCascadeTag { TagName = "tag04" }, + new DeleteCascadeTag { TagName = "tag05" }, + new DeleteCascadeTag { TagName = "tag06" }, + new DeleteCascadeTag { TagName = "tag07" }, + new DeleteCascadeTag { TagName = "tag08" }, + }; + fsql.GetRepository().Insert(tags); + groupRepo.DbContextOptions.EnableAddOrUpdateNavigateList = true; + group = new DeleteCascadeUserGroup + { + GroupName = "group01", + Users = new List + { + new DeleteCascadeUser { Username = "admin01", Password = "pwd01", Tags = new List { tags[0], tags[2], tags[3], tags[6] } }, + new DeleteCascadeUser { Username = "admin02", Password = "pwd02", Tags = new List { tags[1], tags[2], tags[5] } }, + new DeleteCascadeUser { Username = "admin03", Password = "pwd03", Tags = new List { tags[3], tags[4], tags[6], tags[7] } }, + } + }; + groupRepo.Insert(group); + Assert.Equal(group.Id, group.Users[0].GroupId); + Assert.Equal(group.Id, group.Users[1].GroupId); + Assert.Equal(group.Id, group.Users[2].GroupId); + userExt0 = new DeleteCascadeUserExt { UserId = group.Users[0].Id, Remark = "用户备注01" }; + Assert.Equal(1, fsql.Insert(userExt0).ExecuteAffrows()); + userExt1 = new DeleteCascadeUserExt { UserId = group.Users[1].Id, Remark = "用户备注02" }; + Assert.Equal(1, fsql.Insert(userExt1).ExecuteAffrows()); + userExt2 = new DeleteCascadeUserExt { UserId = group.Users[2].Id, Remark = "用户备注03" }; + Assert.Equal(1, fsql.Insert(userExt2).ExecuteAffrows()); + ret = groupRepo.DeleteCascade(group); + Assert.Equal(18, ret.Count); + + Assert.IsType(ret[0]); + Assert.Equal(userExt0.UserId, (ret[0] as DeleteCascadeUserExt).UserId); + Assert.Equal(userExt0.Remark, (ret[0] as DeleteCascadeUserExt).Remark); + Assert.IsType(ret[1]); + Assert.Equal(userExt1.UserId, (ret[1] as DeleteCascadeUserExt).UserId); + Assert.Equal(userExt1.Remark, (ret[1] as DeleteCascadeUserExt).Remark); + Assert.IsType(ret[2]); + Assert.Equal(userExt2.UserId, (ret[2] as DeleteCascadeUserExt).UserId); + Assert.Equal(userExt2.Remark, (ret[2] as DeleteCascadeUserExt).Remark); + + Assert.IsType(ret[3]); + Assert.Equal(group.Users[0].Id, (ret[3] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[0].Id, (ret[3] as DeleteCascadeUserTag).TagId); + Assert.IsType(ret[4]); + Assert.Equal(group.Users[0].Id, (ret[4] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[2].Id, (ret[4] as DeleteCascadeUserTag).TagId); + Assert.IsType(ret[5]); + Assert.Equal(group.Users[0].Id, (ret[5] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[3].Id, (ret[5] as DeleteCascadeUserTag).TagId); + Assert.IsType(ret[6]); + Assert.Equal(group.Users[0].Id, (ret[6] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[6].Id, (ret[6] as DeleteCascadeUserTag).TagId); + + Assert.IsType(ret[7]); + Assert.Equal(group.Users[1].Id, (ret[7] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[1].Id, (ret[7] as DeleteCascadeUserTag).TagId); + Assert.IsType(ret[8]); + Assert.Equal(group.Users[1].Id, (ret[8] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[2].Id, (ret[8] as DeleteCascadeUserTag).TagId); + Assert.IsType(ret[9]); + Assert.Equal(group.Users[1].Id, (ret[9] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[5].Id, (ret[9] as DeleteCascadeUserTag).TagId); + + Assert.IsType(ret[10]); + Assert.Equal(group.Users[2].Id, (ret[10] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[3].Id, (ret[10] as DeleteCascadeUserTag).TagId); + Assert.IsType(ret[11]); + Assert.Equal(group.Users[2].Id, (ret[11] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[4].Id, (ret[11] as DeleteCascadeUserTag).TagId); + Assert.IsType(ret[12]); + Assert.Equal(group.Users[2].Id, (ret[12] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[6].Id, (ret[12] as DeleteCascadeUserTag).TagId); + Assert.IsType(ret[13]); + Assert.Equal(group.Users[2].Id, (ret[13] as DeleteCascadeUserTag).UserId); + Assert.Equal(tags[7].Id, (ret[13] as DeleteCascadeUserTag).TagId); + + Assert.IsType(ret[14]); + Assert.Equal(group.Users[0].Id, (ret[14] as DeleteCascadeUser).Id); + Assert.Equal(group.Users[0].Username, (ret[14] as DeleteCascadeUser).Username); + Assert.Equal(group.Users[0].Password, (ret[14] as DeleteCascadeUser).Password); + Assert.IsType(ret[15]); + Assert.Equal(group.Users[1].Id, (ret[15] as DeleteCascadeUser).Id); + Assert.Equal(group.Users[1].Username, (ret[15] as DeleteCascadeUser).Username); + Assert.Equal(group.Users[1].Password, (ret[15] as DeleteCascadeUser).Password); + Assert.IsType(ret[16]); + Assert.Equal(group.Users[2].Id, (ret[16] as DeleteCascadeUser).Id); + Assert.Equal(group.Users[2].Username, (ret[16] as DeleteCascadeUser).Username); + Assert.Equal(group.Users[2].Password, (ret[16] as DeleteCascadeUser).Password); + Assert.IsType(ret[17]); + Assert.Equal(group.Id, (ret[17] as DeleteCascadeUserGroup).Id); + Assert.Equal(group.GroupName, (ret[17] as DeleteCascadeUserGroup).GroupName); + } + public class DeleteCascadeUser + { + [Column(IsIdentity = true)] + public int Id { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public int GroupId { get; set; } + + [Navigate(nameof(Id))] + public DeleteCascadeUserExt UserExt { get; set; } + [Navigate(ManyToMany = typeof(DeleteCascadeUserTag))] + public List Tags { get; set; } + } + public class DeleteCascadeUserExt + { + [Column(IsPrimary = true)] + public int UserId { get; set; } + public string Remark { get; set; } + + [Navigate(nameof(UserId))] + public DeleteCascadeUser User { get; set; } + } + public class DeleteCascadeUserGroup + { + [Column(IsIdentity = true)] + public int Id { get; set; } + public string GroupName { get; set; } + + [Navigate(nameof(DeleteCascadeUser.GroupId))] + public List Users { get; set; } + } + public class DeleteCascadeTag + { + [Column(IsIdentity = true)] + public int Id { get; set; } + public string TagName { get; set; } + + [Navigate(ManyToMany = typeof(DeleteCascadeUserTag))] + public List Users { get; set; } + } + public class DeleteCascadeUserTag + { + public int UserId { get; set; } + public int TagId { get; set; } + + [Navigate(nameof(UserId))] + public DeleteCascadeUser User { get; set; } + [Navigate(nameof(TagId))] + public DeleteCascadeTag Tag { get; set; } + } + /// /// 更一条无法更新。 /// diff --git a/FreeSql.Tests/FreeSql.Tests/Internal/UtilsTest.cs b/FreeSql.Tests/FreeSql.Tests/Internal/UtilsTest.cs index f71d7ad5..f39a7b71 100644 --- a/FreeSql.Tests/FreeSql.Tests/Internal/UtilsTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Internal/UtilsTest.cs @@ -148,7 +148,7 @@ UNION ALL SELECT * from (SELECT a.""id"", a.""msg"", a.""createtime"" FROM ""as_table_log_202201"" a WHERE (a.""createtime"" < '2022-05-01 00:00:00')) ftb", dict); - Assert.Equal(1, dict.Count); + Assert.Single(dict); sql3 = sql2; dict.Select(a => sql3 = sql3.Replace(a.Key, "{0}".FormatMySql(a.Value))).ToList(); Assert.Equal(sql1, sql3); diff --git a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteDeleteTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteDeleteTest.cs index f2e80606..dd3d75f8 100644 --- a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteDeleteTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteDeleteTest.cs @@ -5,6 +5,7 @@ using FreeSql.Internal.Model; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Text; using Xunit; @@ -109,120 +110,3 @@ namespace FreeSql.Tests.Sqlite } } } - -public static class DeleteExtensions -{ - public static string ToSqlCascade(this IDelete that) - { - var delete = that as DeleteProvider; - if (delete == null) return null; - if (delete._whereTimes <= 0 || delete._where.Length == 0) return null; - if (LocalGetNavigates(delete._table).Any() == false) return that.ToSql(); - - var fsql = delete._orm; - var sb = new StringBuilder(); - Dictionary eachdic = new Dictionary(); - - var rootSel = fsql.Select().AsType(delete._table.Type).Where(delete._where.ToString()); - var rootItems = rootSel.ToList(); - LocalEach(delete._table.Type, rootItems, true); - return sb.ToString(); - - 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(); - } - void LocalEach(Type itemType, List items, bool isOneToOne) - { - items = items?.Where(item => - { - var itemKeystr = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetEntityKeyString(fsql, itemType, item, false); - var eachdicKey = $"{itemType.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(itemType); - var navs = LocalGetNavigates(tb); - - var otos = navs.Where(a => a.Item1.RefType == TableRefType.OneToOne).ToList(); - if (otos.Any()) - { - foreach (var oto in otos) - { - var childsSel = fsql.Select().AsType(oto.Item1.RefEntityType) as Select1Provider; - 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(childsSel._tables[0].Table, refitem, oto.Item1.RefColumns[a].CsName, colval); - } - return refitem; - }).ToList(); - - childsSel.Where(childsSel._commonUtils.WhereItems(oto.Item1.RefColumns.ToArray(), "a.", refitems)); - var childs = childsSel.ToList(); - LocalEach(oto.Item1.RefEntityType, childs, false); - } - } - - var otms = navs.Where(a => a.Item1.RefType == TableRefType.OneToMany).ToList(); - if (otms.Any()) - { - foreach (var otm in otms) - { - var childsSel = fsql.Select().AsType(otm.Item1.RefEntityType) as Select1Provider; - 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(childsSel._tables[0].Table, refitem, otm.Item1.RefColumns[a].CsName, colval); - } - return refitem; - }).ToList(); - - childsSel.Where(childsSel._commonUtils.WhereItems(otm.Item1.RefColumns.ToArray(), "a.", refitems)); - var childs = childsSel.ToList(); - LocalEach(otm.Item1.RefEntityType, childs, true); - } - } - - var mtms = navs.Where(a => a.Item1.RefType == TableRefType.ManyToMany).ToList(); - if (mtms.Any()) - { - foreach (var mtm in mtms) - { - var childsSel = fsql.Select().AsType(mtm.Item1.RefMiddleEntityType) as Select1Provider; - 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(childsSel._tables[0].Table, refitem, mtm.Item1.MiddleColumns[a].CsName, colval); - } - return refitem; - }).ToList(); - - childsSel.Where(childsSel._commonUtils.WhereItems(mtm.Item1.MiddleColumns.Take(mtm.Item1.Columns.Count).ToArray(), "a.", miditems)); - var childs = childsSel.ToList(); - LocalEach(mtm.Item1.RefEntityType, childs, true); - } - } - - var delSql = fsql.Delete().AsType(itemType).WhereDynamic(items).ToSql(); - if (string.IsNullOrWhiteSpace(delSql)) throw new Exception($"ToSqlCascade 失败"); - if (sb.Length > 0) sb.Append("\r\n\r\n;\r\n\r\n"); - sb.Append(delSql); - } - } -} diff --git a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs index 4d09e927..7472ee66 100644 --- a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs @@ -373,8 +373,6 @@ WHERE (((a.""Name"") in (SELECT s.""Title"" as1 var ddd = g.sqlite.Select().LeftJoin(d => d.ParentCode == d.Parent.Code).ToTreeList(); Assert.Single(ddd); Assert.Equal(2, ddd[0].Childs.Count); - - var sql = g.sqlite.Delete().Where(a => a.Code == "001").ToSqlCascade(); } public class District { @@ -2238,8 +2236,6 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" }) }); - var sql = g.sqlite.Delete().Where(a => a.Code == "100000").ToSqlCascade(); - var t1 = fsql.Select() .InnerJoin(a => a.ParentCode == a.Parent.Code) .Where(a => a.Code == "110101") diff --git a/Providers/FreeSql.Provider.SqlServer/Curd/SqlServerDelete.cs b/Providers/FreeSql.Provider.SqlServer/Curd/SqlServerDelete.cs index 05ae3498..bd724930 100644 --- a/Providers/FreeSql.Provider.SqlServer/Curd/SqlServerDelete.cs +++ b/Providers/FreeSql.Provider.SqlServer/Curd/SqlServerDelete.cs @@ -56,7 +56,7 @@ namespace FreeSql.SqlServer.Curd catch (Exception ex) { exception = ex; - throw ex; + throw; } finally { @@ -113,7 +113,7 @@ namespace FreeSql.SqlServer.Curd catch (Exception ex) { exception = ex; - throw ex; + throw; } finally { diff --git a/Providers/FreeSql.Provider.SqlServer/Curd/SqlServerUpdate.cs b/Providers/FreeSql.Provider.SqlServer/Curd/SqlServerUpdate.cs index 4d6fb211..d3ada5f8 100644 --- a/Providers/FreeSql.Provider.SqlServer/Curd/SqlServerUpdate.cs +++ b/Providers/FreeSql.Provider.SqlServer/Curd/SqlServerUpdate.cs @@ -65,7 +65,7 @@ namespace FreeSql.SqlServer.Curd catch (Exception ex) { exception = ex; - throw ex; + throw; } finally { @@ -158,7 +158,7 @@ namespace FreeSql.SqlServer.Curd catch (Exception ex) { exception = ex; - throw ex; + throw; } finally {