- 增加 Repository/DbContext SaveMany 方法实现一对多,子数据的完整保存;

- 调整 SaveManyToMany 方法名为 SaveMany;
This commit is contained in:
28810 2019-11-21 16:42:20 +08:00
parent b5629b13a6
commit 04107d3d24
15 changed files with 150 additions and 109 deletions

View File

@ -158,14 +158,18 @@ namespace FreeSql
this.Set<TEntity>().AddOrUpdate(data);
}
/// <summary>
/// 保存实体的指定 ManyToMany 导航属性
/// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
/// 场景:在关闭级联保存功能之后,手工使用本方法<para></para>
/// 例子:保存商品的 OneToMany 集合属性SaveMany(goods, "Skus")<para></para>
/// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
/// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
/// </summary>
/// <param name="data">实体对象</param>
/// <param name="propertyName">属性名</param>
public void SaveManyToMany<TEntity>(TEntity data, string propertyName) where TEntity : class
public void SaveMany<TEntity>(TEntity data, string propertyName) where TEntity : class
{
CheckEntityTypeOrThrow(typeof(TEntity));
this.Set<TEntity>().SaveManyToMany(data, propertyName);
this.Set<TEntity>().SaveMany(data, propertyName);
}
/// <summary>
@ -212,10 +216,10 @@ namespace FreeSql
CheckEntityTypeOrThrow(typeof(TEntity));
return this.Set<TEntity>().AddOrUpdateAsync(data);
}
public Task SaveManyToManyAsync<TEntity>(TEntity data, string propertyName) where TEntity : class
public Task SaveManyAsync<TEntity>(TEntity data, string propertyName) where TEntity : class
{
CheckEntityTypeOrThrow(typeof(TEntity));
return this.Set<TEntity>().SaveManyToManyAsync(data, propertyName);
return this.Set<TEntity>().SaveManyAsync(data, propertyName);
}
#endif
#endregion

View File

@ -9,9 +9,9 @@ namespace FreeSql
{
/// <summary>
/// 是否开启一对多,多对多级保存功能<para></para>
/// 是否开启一对多,多对多保存功能<para></para>
/// <para></para>
/// 【一对多】模型下, 保存时可级保存实体的属性集合。出于使用安全考虑我们没做完整对比,只实现实体属性集合的添加或更新操作,所以不会删除实体属性集合的数据。<para></para>
/// 【一对多】模型下, 保存时可保存实体的属性集合。出于使用安全考虑我们没做完整对比,只实现实体属性集合的添加或更新操作,所以不会删除实体属性集合的数据。<para></para>
/// 完整对比的功能使用起来太危险,试想下面的场景:<para></para>
/// - 保存的时候,实体的属性集合是空的,如何操作?记录全部删除?<para></para>
/// - 保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作?<para></para>

View File

@ -133,22 +133,44 @@ namespace FreeSql
}
}
async public Task SaveManyToManyAsync(TEntity item, string propertyName)
async public Task SaveManyAsync(TEntity item, string propertyName)
{
if (item == null) return;
if (_table.Properties.ContainsKey(propertyName) == false) throw new KeyNotFoundException($"{_table.Type.FullName} 不存在属性 {propertyName}");
if (string.IsNullOrEmpty(propertyName)) return;
if (_table.Properties.TryGetValue(propertyName, out var prop) == false) throw new KeyNotFoundException($"{_table.Type.FullName} 不存在属性 {propertyName}");
if (_table.ColumnsByCsIgnore.ContainsKey(propertyName)) throw new ArgumentException($"{_table.Type.FullName} 类型已设置属性 {propertyName} 忽略特性");
var tref = _table.GetTableRef(propertyName, true);
if (tref.RefType != Internal.Model.TableRefType.ManyToMany) throw new ArgumentException($"{_table.Type.FullName} 类型的属性 {propertyName} 不是 ManyToMany 特性");
DbContextExecCommand();
if (tref == null) return;
switch (tref.RefType)
{
case Internal.Model.TableRefType.OneToOne:
case Internal.Model.TableRefType.ManyToOne:
throw new ArgumentException($"{_table.Type.FullName} 类型的属性 {propertyName} 不是 OneToMany 或 ManyToMany 特性");
}
await DbContextExecCommandAsync();
var oldEnable = _db.Options.EnableAddOrUpdateNavigateList;
_db.Options.EnableAddOrUpdateNavigateList = false;
try
{
await AddOrUpdateNavigateListAsync(item, false, propertyName);
if (tref.RefType == Internal.Model.TableRefType.OneToMany)
{
await DbContextExecCommandAsync();
//删除没有保存的数据
var propValEach = GetItemValue(item, prop) as IEnumerable;
await _db.Orm.Select<object>().AsType(tref.RefEntityType).WhereDynamic(propValEach, true)
.ToDelete().ExecuteAffrowsAsync();
}
}
finally
{
_db.Options.EnableAddOrUpdateNavigateList = oldEnable;
}
}
async Task AddOrUpdateNavigateListAsync(TEntity item, bool isAdd, string propertyName = null)
{
Type itemType = null;
Func<PropertyInfo, Task> action = async prop =>
{
if (_table.ColumnsByCsIgnore.ContainsKey(prop.Name)) return;
@ -163,26 +185,7 @@ namespace FreeSql
return;
}
object propVal = null;
if (itemType == null) itemType = item.GetType();
if (_table.TypeLazy != null && itemType == _table.TypeLazy)
{
var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary<string, FieldInfo>()).GetOrAdd(prop.Name, propName =>
_table.TypeLazy.GetField($"__lazy__{propName}", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance));
if (lazyField != null)
{
var lazyFieldValue = (bool)lazyField.GetValue(item);
if (lazyFieldValue == false) return;
}
propVal = prop.GetValue(item);
}
else
{
propVal = prop.GetValue(item);
if (propVal == null) return;
}
var propValEach = propVal as IEnumerable;
var propValEach = GetItemValue(item, prop) as IEnumerable;
if (propValEach == null) return;
DbSet<object> refSet = GetDbSetObject(tref.RefEntityType);
switch (tref.RefType)

View File

@ -136,34 +136,53 @@ namespace FreeSql
}
}
static ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>> _dicLazyIsSetField = new ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>>();
/// <summary>
/// 保存实体的指定 ManyToMany 导航属性
/// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
/// 场景:在关闭级联保存功能之后,手工使用本方法<para></para>
/// 例子:保存商品的 OneToMany 集合属性SaveMany(goods, "Skus")<para></para>
/// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
/// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
/// </summary>
/// <param name="item">实体对象</param>
/// <param name="propertyName">属性名</param>
public void SaveManyToMany(TEntity item, string propertyName)
public void SaveMany(TEntity item, string propertyName)
{
if (item == null) return;
if (_table.Properties.ContainsKey(propertyName) == false) throw new KeyNotFoundException($"{_table.Type.FullName} 不存在属性 {propertyName}");
if (string.IsNullOrEmpty(propertyName)) return;
if (_table.Properties.TryGetValue(propertyName, out var prop) == false) throw new KeyNotFoundException($"{_table.Type.FullName} 不存在属性 {propertyName}");
if (_table.ColumnsByCsIgnore.ContainsKey(propertyName)) throw new ArgumentException($"{_table.Type.FullName} 类型已设置属性 {propertyName} 忽略特性");
var tref = _table.GetTableRef(propertyName, true);
if (tref.RefType != Internal.Model.TableRefType.ManyToMany) throw new ArgumentException($"{_table.Type.FullName} 类型的属性 {propertyName} 不是 ManyToMany 特性");
if (tref == null) return;
switch (tref.RefType)
{
case Internal.Model.TableRefType.OneToOne:
case Internal.Model.TableRefType.ManyToOne:
throw new ArgumentException($"{_table.Type.FullName} 类型的属性 {propertyName} 不是 OneToMany 或 ManyToMany 特性");
}
DbContextExecCommand();
var oldEnable = _db.Options.EnableAddOrUpdateNavigateList;
_db.Options.EnableAddOrUpdateNavigateList = false;
try
{
AddOrUpdateNavigateList(item, false, propertyName);
if (tref.RefType == Internal.Model.TableRefType.OneToMany)
{
DbContextExecCommand();
//删除没有保存的数据
var propValEach = GetItemValue(item, prop) as IEnumerable;
_db.Orm.Select<object>().AsType(tref.RefEntityType).WhereDynamic(propValEach, true)
.ToDelete().ExecuteAffrows();
}
}
finally
{
_db.Options.EnableAddOrUpdateNavigateList = oldEnable;
}
/// <summary>
/// 联级保存导航集合
/// </summary>
/// <param name="item">实体对象</param>
/// <param name="isAdd">是否为新增的实体对象</param>
/// <param name="propertyName">指定保存的属性</param>
}
void AddOrUpdateNavigateList(TEntity item, bool isAdd, string propertyName = null)
{
Type itemType = null;
Action<PropertyInfo> action = prop =>
{
if (_table.ColumnsByCsIgnore.ContainsKey(prop.Name)) return;
@ -178,26 +197,7 @@ namespace FreeSql
return;
}
object propVal = null;
if (itemType == null) itemType = item.GetType();
if (_table.TypeLazy != null && itemType == _table.TypeLazy)
{
var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary<string, FieldInfo>()).GetOrAdd(prop.Name, propName =>
_table.TypeLazy.GetField($"__lazy__{propName}", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance));
if (lazyField != null)
{
var lazyFieldValue = (bool)lazyField.GetValue(item);
if (lazyFieldValue == false) return;
}
propVal = prop.GetValue(item, null);
}
else
{
propVal = prop.GetValue(item, null);
if (propVal == null) return;
}
var propValEach = propVal as IEnumerable;
var propValEach = GetItemValue(item, prop) as IEnumerable;
if (propValEach == null) return;
DbSet<object> refSet = GetDbSetObject(tref.RefEntityType);
switch (tref.RefType)
@ -313,6 +313,29 @@ namespace FreeSql
else if (_table.Properties.TryGetValue(propertyName, out var prop))
action(prop);
}
static ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>> _dicLazyIsSetField = new ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>>();
object GetItemValue(TEntity item, PropertyInfo prop)
{
object propVal = null;
var itemType = item.GetType();
if (_table.TypeLazy != null && itemType == _table.TypeLazy)
{
var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary<string, FieldInfo>()).GetOrAdd(prop.Name, propName =>
_table.TypeLazy.GetField($"__lazy__{propName}", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance));
if (lazyField != null)
{
var lazyFieldValue = (bool)lazyField.GetValue(item);
if (lazyFieldValue == false) return null;
}
propVal = prop.GetValue(item, null);
}
else
{
propVal = prop.GetValue(item, null);
if (propVal == null) return null;
}
return propVal;
}
#endregion
#region Update

View File

@ -32,9 +32,13 @@
<typeparam name="TEntity"></typeparam>
<param name="data"></param>
</member>
<member name="M:FreeSql.DbContext.SaveManyToMany``1(``0,System.String)">
<member name="M:FreeSql.DbContext.SaveMany``1(``0,System.String)">
<summary>
保存实体的指定 ManyToMany 导航属性
保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
场景:在关闭级联保存功能之后,手工使用本方法<para></para>
例子:保存商品的 OneToMany 集合属性SaveMany(goods, "Skus")<para></para>
当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
</summary>
<param name="data">实体对象</param>
<param name="propertyName">属性名</param>
@ -65,9 +69,9 @@
</member>
<member name="P:FreeSql.DbContextOptions.EnableAddOrUpdateNavigateList">
<summary>
是否开启一对多,多对多级保存功能<para></para>
是否开启一对多,多对多级保存功能<para></para>
<para></para>
【一对多】模型下, 保存时可级保存实体的属性集合。出于使用安全考虑我们没做完整对比,只实现实体属性集合的添加或更新操作,所以不会删除实体属性集合的数据。<para></para>
【一对多】模型下, 保存时可级保存实体的属性集合。出于使用安全考虑我们没做完整对比,只实现实体属性集合的添加或更新操作,所以不会删除实体属性集合的数据。<para></para>
完整对比的功能使用起来太危险,试想下面的场景:<para></para>
- 保存的时候,实体的属性集合是空的,如何操作?记录全部删除?<para></para>
- 保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作?<para></para>
@ -106,34 +110,23 @@
清空状态数据
</summary>
</member>
<member name="M:FreeSql.DbSet`1.RemoveAsync(System.Linq.Expressions.Expression{System.Func{`0,System.Boolean}})">
<summary>
根据 lambda 条件删除数据
</summary>
<param name="predicate"></param>
<returns></returns>
</member>
<member name="M:FreeSql.DbSet`1.Add(`0)">
<summary>
添加
</summary>
<param name="data"></param>
</member>
<member name="M:FreeSql.DbSet`1.SaveManyToMany(`0,System.String)">
<member name="M:FreeSql.DbSet`1.SaveMany(`0,System.String)">
<summary>
保存实体的指定 ManyToMany 导航属性
保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
场景:在关闭级联保存功能之后,手工使用本方法<para></para>
例子:保存商品的 OneToMany 集合属性SaveMany(goods, "Skus")<para></para>
当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
</summary>
<param name="item">实体对象</param>
<param name="propertyName">属性名</param>
</member>
<member name="M:FreeSql.DbSet`1.AddOrUpdateNavigateList(`0,System.Boolean,System.String)">
<summary>
联级保存导航集合
</summary>
<param name="item">实体对象</param>
<param name="isAdd">是否为新增的实体对象</param>
<param name="propertyName">指定保存的属性</param>
</member>
<member name="M:FreeSql.DbSet`1.Update(`0)">
<summary>
更新
@ -240,9 +233,13 @@
</summary>
<param name="data"></param>
</member>
<member name="M:FreeSql.IBasicRepository`1.SaveManyToMany(`0,System.String)">
<member name="M:FreeSql.IBasicRepository`1.SaveMany(`0,System.String)">
<summary>
保存实体的指定 ManyToMany 导航属性
保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
场景:在关闭级联保存功能之后,手工使用本方法<para></para>
例子:保存商品的 OneToMany 集合属性SaveMany(goods, "Skus")<para></para>
当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
</summary>
<param name="entity">实体对象</param>
<param name="propertyName">属性名</param>

View File

@ -130,9 +130,9 @@ namespace FreeSql
return entity;
}
public void SaveManyToMany(TEntity entity, string propertyName)
public void SaveMany(TEntity entity, string propertyName)
{
_dbset.SaveManyToMany(entity, propertyName);
_dbset.SaveMany(entity, propertyName);
_db.SaveChanges();
}
}

View File

@ -64,9 +64,9 @@ namespace FreeSql
return entity;
}
async public Task SaveManyToManyAsync(TEntity entity, string propertyName)
async public Task SaveManyAsync(TEntity entity, string propertyName)
{
await _dbset.SaveManyToManyAsync(entity, propertyName);
await _dbset.SaveManyAsync(entity, propertyName);
await _db.SaveChangesAsync();
}
}

View File

@ -30,11 +30,15 @@ namespace FreeSql
TEntity InsertOrUpdate(TEntity entity);
/// <summary>
/// 保存实体的指定 ManyToMany 导航属性
/// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
/// 场景:在关闭级联保存功能之后,手工使用本方法<para></para>
/// 例子:保存商品的 OneToMany 集合属性SaveMany(goods, "Skus")<para></para>
/// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
/// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
/// </summary>
/// <param name="entity">实体对象</param>
/// <param name="propertyName">属性名</param>
void SaveManyToMany(TEntity entity, string propertyName);
void SaveMany(TEntity entity, string propertyName);
IUpdate<TEntity> UpdateDiy { get; }
@ -49,7 +53,7 @@ namespace FreeSql
Task<int> UpdateAsync(TEntity entity);
Task<int> UpdateAsync(IEnumerable<TEntity> entitys);
Task<TEntity> InsertOrUpdateAsync(TEntity entity);
Task SaveManyToManyAsync(TEntity entity, string propertyName);
Task SaveManyAsync(TEntity entity, string propertyName);
Task<int> DeleteAsync(TEntity entity);
Task<int> DeleteAsync(IEnumerable<TEntity> entitys);

View File

@ -6,7 +6,7 @@
### v0.6.5
- 修复 Repository 级保存的 bug
- 修复 Repository 级保存的 bug
- 添加工作单元开启方法;
- 适配 .net framework 4.5、netstandard 2.0
@ -192,7 +192,7 @@ fsql.GetGuidRepository<User>().Select.FromRepository(logRepository)
- 修复 AddOrUpdate/InsertOrUpdate 当主键无值时,仍然查询了一次数据库;
- 增加 查询数据时 TrackToList 对导航集合的状态跟踪;
- 完善 AddOrUpdateNavigateList 级保存,忽略标记 IsIgnore 的集合属性;
- 完善 AddOrUpdateNavigateList 级保存,忽略标记 IsIgnore 的集合属性;
- 完成 IFreeSql.Include、IncludeMany 功能;
### v0.5.12

View File

@ -361,7 +361,9 @@ namespace FreeSql.Tests
})
}
};
repo.DbContextOptions.EnableAddOrUpdateNavigateList = false; //关闭级联保存功能
repo.Insert(cts);
repo.SaveMany(cts[0], "Childs"); //指定保存 Childs 一对多属性
cts[0].Name = "分类11";
cts[0].Childs.Clear();
cts[1].Name = "分类22";
@ -415,9 +417,9 @@ namespace FreeSql.Tests
}
};
var repo = g.sqlite.GetRepository<Song>();
//repo.DbContextOptions.EnableAddOrUpdateNavigateList = false; //关闭级保存功能
//repo.DbContextOptions.EnableAddOrUpdateNavigateList = false; //关闭保存功能
repo.Insert(ss);
repo.SaveManyToMany(ss[0], "Tags"); //指定保存 Tags 多对多属性
//repo.SaveMany(ss[0], "Tags"); //指定保存 Tags 多对多属性
ss[0].Name = "爱你一万年.mp5";
ss[0].Tags.Clear();

View File

@ -98,7 +98,7 @@ namespace FreeSql.Tests
var sql = g.mysql.Select<testenumWhere>().Where(a => a.type == testenumWhereType.Blaaa).ToSql();
var tolist = g.mysql.Select<testenumWhere>().Where(a => a.type == testenumWhereType.Blaaa).ToList();
//支持 1对多 级保存
//支持 1对多 保存
using (var ctx = new FreeContext(g.sqlite))
{
@ -127,7 +127,7 @@ namespace FreeSql.Tests
[Fact]
public void Update()
{
//查询 1对多级保存
//查询 1对多保存
using (var ctx = new FreeContext(g.sqlite))
{

View File

@ -1431,11 +1431,12 @@
<param name="exp">lambda表达式</param>
<returns></returns>
</member>
<member name="M:FreeSql.ISelect`1.WhereDynamic(System.Object)">
<member name="M:FreeSql.ISelect`1.WhereDynamic(System.Object,System.Boolean)">
<summary>
传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1}
</summary>
<param name="dywhere">主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合</param>
<param name="not">是否标识为NOT</param>
<returns></returns>
</member>
<member name="M:FreeSql.ISelect`1.WhereCascade(System.Linq.Expressions.Expression{System.Func{`0,System.Boolean}})">

View File

@ -293,8 +293,9 @@ namespace FreeSql
/// 传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1}
/// </summary>
/// <param name="dywhere">主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合</param>
/// <param name="not">是否标识为NOT</param>
/// <returns></returns>
ISelect<T1> WhereDynamic(object dywhere);
ISelect<T1> WhereDynamic(object dywhere, bool not = false);
/// <summary>
/// 多表查询时,该方法标记后,表达式条件将对所有表进行附加

View File

@ -888,12 +888,16 @@ namespace FreeSql.Internal.CommonProvider
public IDelete<T1> ToDelete()
{
if (_tables[0].Table.Primarys.Any() == false) throw new Exception($"ToDelete 功能要求实体类 {_tables[0].Table.CsName} 必须有主键");
return _orm.Delete<T1>().Where(GetToDeleteWhere("ftb_del"));
var del = _orm.Delete<T1>();
if (_tables[0].Table.Type != typeof(T1)) del.AsType(_tables[0].Table.Type);
return del.Where(GetToDeleteWhere("ftb_del"));
}
public IUpdate<T1> ToUpdate()
{
if (_tables[0].Table.Primarys.Any() == false) throw new Exception($"ToUpdate 功能要求实体类 {_tables[0].Table.CsName} 必须有主键");
return _orm.Update<T1>().Where(GetToDeleteWhere("ftb_upd"));
var upd = _orm.Update<T1>();
if (_tables[0].Table.Type != typeof(T1)) upd.AsType(_tables[0].Table.Type);
return upd.Where(GetToDeleteWhere("ftb_upd"));
}
protected List<Dictionary<Type, string>> GetTableRuleUnions()

View File

@ -319,7 +319,9 @@ namespace FreeSql.Internal.CommonProvider
_tables[0].Parameter = exp.Parameters[0];
return this.InternalWhere(exp?.Body);
}
public ISelect<T1> WhereDynamic(object dywhere) => this.Where(_commonUtils.WhereObject(_tables.First().Table, $"{_tables.First().Alias}.", dywhere));
public ISelect<T1> WhereDynamic(object dywhere, bool not = false) => not == false ?
this.Where(_commonUtils.WhereObject(_tables.First().Table, $"{_tables.First().Alias}.", dywhere)) :
this.Where($"not({_commonUtils.WhereObject(_tables.First().Table, $"{_tables.First().Alias}.", dywhere)})");
public ISelect<T1> WhereCascade(Expression<Func<T1, bool>> exp)
{