mirror of
https://github.com/nsnail/FreeSql.git
synced 2025-04-22 18:52:50 +08:00
- 增加 DbContext/Repository ManyToMany联级保存功能(之前已支持OneToMany);
This commit is contained in:
parent
7514000490
commit
33cb3e2dae
@ -148,14 +148,15 @@ namespace FreeSql
|
|||||||
public ExecCommandInfoType actionType { get; set; }
|
public ExecCommandInfoType actionType { get; set; }
|
||||||
public IDbSet dbSet { get; set; }
|
public IDbSet dbSet { get; set; }
|
||||||
public Type stateType { get; set; }
|
public Type stateType { get; set; }
|
||||||
|
public Type entityType { get; set; }
|
||||||
public object state { get; set; }
|
public object state { get; set; }
|
||||||
}
|
}
|
||||||
internal enum ExecCommandInfoType { Insert, Update, Delete }
|
internal enum ExecCommandInfoType { Insert, Update, Delete }
|
||||||
Queue<ExecCommandInfo> _actions = new Queue<ExecCommandInfo>();
|
Queue<ExecCommandInfo> _actions = new Queue<ExecCommandInfo>();
|
||||||
internal int _affrows = 0;
|
internal int _affrows = 0;
|
||||||
|
|
||||||
internal void EnqueueAction(ExecCommandInfoType actionType, IDbSet dbSet, Type stateType, object state) =>
|
internal void EnqueueAction(ExecCommandInfoType actionType, IDbSet dbSet, Type stateType, Type entityType, object state) =>
|
||||||
_actions.Enqueue(new ExecCommandInfo { actionType = actionType, dbSet = dbSet, stateType = stateType, state = state });
|
_actions.Enqueue(new ExecCommandInfo { actionType = actionType, dbSet = dbSet, stateType = stateType, entityType = entityType, state = state });
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
~DbContext() => this.Dispose();
|
~DbContext() => this.Dispose();
|
||||||
|
@ -97,10 +97,11 @@ namespace FreeSql
|
|||||||
|
|
||||||
if (_actions.Any() == false && states.Any() ||
|
if (_actions.Any() == false && states.Any() ||
|
||||||
info != null && oldinfo.actionType != info.actionType ||
|
info != null && oldinfo.actionType != info.actionType ||
|
||||||
info != null && oldinfo.stateType != info.stateType)
|
info != null && oldinfo.stateType != info.stateType ||
|
||||||
|
info != null && oldinfo.entityType != info.entityType)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType)
|
if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType && oldinfo.entityType == info.entityType)
|
||||||
{
|
{
|
||||||
//最后一个,合起来发送
|
//最后一个,合起来发送
|
||||||
states.Add(info.state);
|
states.Add(info.state);
|
||||||
|
@ -5,7 +5,16 @@ namespace FreeSql
|
|||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否开启一对多,联级保存功能
|
/// 是否开启一对多,多对多联级保存功能<para></para>
|
||||||
|
/// <para></para>
|
||||||
|
/// 【一对多】模型下, 保存时可联级保存实体的属性集合。出于使用安全考虑我们没做完整对比,只实现实体属性集合的添加或更新操作,所以不会删除实体属性集合的数据。<para></para>
|
||||||
|
/// 完整对比的功能使用起来太危险,试想下面的场景:<para></para>
|
||||||
|
/// - 保存的时候,实体的属性集合是空的,如何操作?记录全部删除?<para></para>
|
||||||
|
/// - 保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作?<para></para>
|
||||||
|
/// <para></para>
|
||||||
|
/// 【多对多】模型下,我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(注意不会更新)
|
||||||
|
/// - 属性集合为空时,删除他们的所有关联数据(中间表)
|
||||||
|
/// - 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableAddOrUpdateNavigateList { get; set; } = true;
|
public bool EnableAddOrUpdateNavigateList { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
@ -97,10 +97,11 @@ namespace FreeSql
|
|||||||
|
|
||||||
if (_actions.Any() == false && states.Any() ||
|
if (_actions.Any() == false && states.Any() ||
|
||||||
info != null && oldinfo.actionType != info.actionType ||
|
info != null && oldinfo.actionType != info.actionType ||
|
||||||
info != null && oldinfo.stateType != info.stateType)
|
info != null && oldinfo.stateType != info.stateType ||
|
||||||
|
info != null && oldinfo.entityType != info.entityType)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType)
|
if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType && oldinfo.entityType == info.entityType)
|
||||||
{
|
{
|
||||||
//最后一个,合起来发送
|
//最后一个,合起来发送
|
||||||
states.Add(info.state);
|
states.Add(info.state);
|
||||||
|
@ -60,7 +60,7 @@ namespace FreeSql
|
|||||||
protected virtual IDelete<TEntity> OrmDelete(object dywhere) => _db.Orm.Delete<TEntity>().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).WhereDynamic(dywhere);
|
protected virtual IDelete<TEntity> OrmDelete(object dywhere) => _db.Orm.Delete<TEntity>().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).WhereDynamic(dywhere);
|
||||||
|
|
||||||
internal void EnqueueToDbContext(DbContext.ExecCommandInfoType actionType, EntityState state) =>
|
internal void EnqueueToDbContext(DbContext.ExecCommandInfoType actionType, EntityState state) =>
|
||||||
_db.EnqueueAction(actionType, this, typeof(EntityState), state);
|
_db.EnqueueAction(actionType, this, typeof(EntityState), _entityType, state);
|
||||||
|
|
||||||
internal void IncrAffrows(int affrows) =>
|
internal void IncrAffrows(int affrows) =>
|
||||||
_db._affrows += affrows;
|
_db._affrows += affrows;
|
||||||
@ -117,14 +117,15 @@ namespace FreeSql
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entityType"></param>
|
/// <param name="entityType"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public void AsType(Type entityType)
|
public DbSet<TEntity> AsType(Type entityType)
|
||||||
{
|
{
|
||||||
if (entityType == typeof(object)) throw new Exception("ISelect.AsType 参数不支持指定为 object");
|
if (entityType == typeof(object)) throw new Exception("ISelect.AsType 参数不支持指定为 object");
|
||||||
if (entityType == _entityType) return;
|
if (entityType == _entityType) return this;
|
||||||
var newtb = _db.Orm.CodeFirst.GetTableByEntity(entityType);
|
var newtb = _db.Orm.CodeFirst.GetTableByEntity(entityType);
|
||||||
_entityType = entityType;
|
_entityType = entityType;
|
||||||
_tablePriv = newtb ?? throw new Exception("DbSet.AsType 参数错误,请传入正确的实体类型");
|
_tablePriv = newtb ?? throw new Exception("DbSet.AsType 参数错误,请传入正确的实体类型");
|
||||||
_tableIdentitysPriv = null;
|
_tableIdentitysPriv = null;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EntityState
|
public class EntityState
|
||||||
|
@ -4,6 +4,8 @@ using System.Collections;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace FreeSql
|
namespace FreeSql
|
||||||
@ -131,46 +133,132 @@ namespace FreeSql
|
|||||||
{
|
{
|
||||||
if (_table.ColumnsByCsIgnore.ContainsKey(prop.Key)) continue;
|
if (_table.ColumnsByCsIgnore.ContainsKey(prop.Key)) continue;
|
||||||
if (_table.ColumnsByCs.ContainsKey(prop.Key)) continue;
|
if (_table.ColumnsByCs.ContainsKey(prop.Key)) continue;
|
||||||
|
|
||||||
var tref = _table.GetTableRef(prop.Key, true);
|
var tref = _table.GetTableRef(prop.Key, true);
|
||||||
if (tref == null) continue;
|
if (tref == null) continue;
|
||||||
|
|
||||||
switch (tref.RefType)
|
switch (tref.RefType)
|
||||||
{
|
{
|
||||||
case Internal.Model.TableRefType.OneToOne:
|
case Internal.Model.TableRefType.OneToOne:
|
||||||
case Internal.Model.TableRefType.ManyToOne:
|
case Internal.Model.TableRefType.ManyToOne:
|
||||||
case Internal.Model.TableRefType.ManyToMany:
|
|
||||||
continue;
|
continue;
|
||||||
case Internal.Model.TableRefType.OneToMany:
|
}
|
||||||
|
|
||||||
|
object propVal = null;
|
||||||
if (itemType == null) itemType = item.GetType();
|
if (itemType == null) itemType = item.GetType();
|
||||||
if (_table.TypeLazy != null && itemType == _table.TypeLazy)
|
if (_table.TypeLazy != null && itemType == _table.TypeLazy)
|
||||||
{
|
{
|
||||||
var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary<string, System.Reflection.FieldInfo>()).GetOrAdd(prop.Key, propName =>
|
var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary<string, FieldInfo>()).GetOrAdd(prop.Key, propName =>
|
||||||
_table.TypeLazy.GetField($"__lazy__{propName}", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
|
_table.TypeLazy.GetField($"__lazy__{propName}", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||||
if (lazyField != null)
|
if (lazyField != null)
|
||||||
{
|
{
|
||||||
var lazyFieldValue = (bool)lazyField.GetValue(item);
|
var lazyFieldValue = (bool)lazyField.GetValue(item);
|
||||||
if (lazyFieldValue == false) continue;
|
if (lazyFieldValue == false) continue;
|
||||||
}
|
}
|
||||||
|
propVal = prop.Value.GetValue(item);
|
||||||
}
|
}
|
||||||
var propVal = prop.Value.GetValue(item);
|
else
|
||||||
|
{
|
||||||
|
propVal = prop.Value.GetValue(item);
|
||||||
|
if (propVal == null) continue;
|
||||||
|
}
|
||||||
|
|
||||||
var propValEach = propVal as IEnumerable;
|
var propValEach = propVal as IEnumerable;
|
||||||
if (propValEach == null) continue;
|
if (propValEach == null) continue;
|
||||||
object dbset = null;
|
DbSet<object> refSet = _db.Set<object>().AsType(tref.RefEntityType);
|
||||||
System.Reflection.MethodInfo dbsetAddOrUpdate = null;
|
switch (tref.RefType)
|
||||||
|
{
|
||||||
|
case Internal.Model.TableRefType.ManyToMany:
|
||||||
|
var curList = new List<object>();
|
||||||
foreach (var propValItem in propValEach)
|
foreach (var propValItem in propValEach)
|
||||||
{
|
{
|
||||||
if (dbset == null)
|
curList.Add(propValItem);
|
||||||
{
|
var flagExists = refSet.ExistsInStates(propValItem);
|
||||||
dbset = _db.Set(tref.RefEntityType);
|
if (flagExists == false)
|
||||||
dbsetAddOrUpdate = dbset.GetType().GetMethod("AddOrUpdateAsync", new Type[] { tref.RefEntityType });
|
flagExists = await refSet.Select.WhereDynamic(propValItem).AnyAsync();
|
||||||
|
if (refSet.CanAdd(propValItem, false) && flagExists != true)
|
||||||
|
await refSet.AddAsync(propValItem);
|
||||||
}
|
}
|
||||||
|
var midSelectParam = Expression.Parameter(typeof(object), "a");
|
||||||
|
var midWheres = new List<Expression<Func<object, bool>>>();
|
||||||
|
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||||
|
midWheres.Add(Expression.Lambda<Func<object, bool>>(Expression.Equal(
|
||||||
|
Expression.MakeMemberAccess(Expression.Convert(midSelectParam, tref.RefMiddleEntityType), tref.MiddleColumns[colidx].Table.Properties[tref.MiddleColumns[colidx].CsName]),
|
||||||
|
Expression.Constant(
|
||||||
|
FreeSql.Internal.Utils.GetDataReaderValue(
|
||||||
|
tref.MiddleColumns[colidx].CsType,
|
||||||
|
_db.Orm.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName)), tref.MiddleColumns[colidx].CsType)
|
||||||
|
), midSelectParam));
|
||||||
|
|
||||||
|
if (curList.Any() == false) //全部删除
|
||||||
|
{
|
||||||
|
var delall = _db.Orm.Delete<object>().AsType(tref.RefMiddleEntityType);
|
||||||
|
foreach (var midWhere in midWheres) delall.Where(midWhere);
|
||||||
|
await delall.ExecuteAffrowsAsync();
|
||||||
|
}
|
||||||
|
else //保存
|
||||||
|
{
|
||||||
|
var midSet = _db.Set<object>().AsType(tref.RefMiddleEntityType);
|
||||||
|
var midSelect = midSet.Select;
|
||||||
|
foreach (var midWhere in midWheres) midSelect.Where(midWhere);
|
||||||
|
var midList = await midSelect.ToListAsync();
|
||||||
|
var midListDel = new List<object>();
|
||||||
|
var midListAdd = new List<object>();
|
||||||
|
|
||||||
|
foreach (var midItem in midList)
|
||||||
|
{
|
||||||
|
var curContains = new List<int>();
|
||||||
|
for (var curIdx = 0; curIdx < curList.Count; curIdx++)
|
||||||
|
{
|
||||||
|
var isEquals = true;
|
||||||
|
for (var midcolidx = tref.Columns.Count; midcolidx < tref.MiddleColumns.Count; midcolidx++)
|
||||||
|
{
|
||||||
|
var refcol = tref.Columns[midcolidx - tref.Columns.Count];
|
||||||
|
var midval = FreeSql.Internal.Utils.GetDataReaderValue(refcol.CsType, _db.Orm.GetEntityValueWithPropertyName(tref.RefMiddleEntityType, midItem, tref.MiddleColumns[midcolidx].CsName));
|
||||||
|
var refval = FreeSql.Internal.Utils.GetDataReaderValue(refcol.CsType, _db.Orm.GetEntityValueWithPropertyName(tref.RefEntityType, curList[curIdx], tref.Columns[midcolidx - tref.Columns.Count].CsName));
|
||||||
|
if (object.Equals(midval, refval) == false)
|
||||||
|
{
|
||||||
|
isEquals = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isEquals)
|
||||||
|
curContains.Add(curIdx);
|
||||||
|
}
|
||||||
|
if (curContains.Any())
|
||||||
|
foreach (var curIdx in curContains)
|
||||||
|
curList.RemoveAt(curIdx);
|
||||||
|
else
|
||||||
|
midListDel.Add(midItem);
|
||||||
|
}
|
||||||
|
midSet.RemoveRange(midListDel); //删除未保存的项
|
||||||
|
foreach (var curItem in curList)
|
||||||
|
{
|
||||||
|
var newItem = Activator.CreateInstance(tref.RefMiddleEntityType);
|
||||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||||
{
|
{
|
||||||
tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName]
|
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.MiddleColumns[colidx].CsType, _db.Orm.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||||
.SetValue(propValItem, tref.Columns[colidx].Table.Properties[tref.Columns[colidx].CsName].GetValue(item));
|
_db.Orm.SetEntityValueWithPropertyName(tref.RefMiddleEntityType, newItem, tref.MiddleColumns[colidx].CsName, val);
|
||||||
}
|
}
|
||||||
Task task = dbsetAddOrUpdate.Invoke(dbset, new object[] { propValItem }) as Task;
|
for (var midcolidx = tref.Columns.Count; midcolidx < tref.MiddleColumns.Count; midcolidx++)
|
||||||
await task;
|
{
|
||||||
|
var refcol = tref.Columns[midcolidx - tref.Columns.Count];
|
||||||
|
var refval = FreeSql.Internal.Utils.GetDataReaderValue(tref.MiddleColumns[midcolidx].CsType, _db.Orm.GetEntityValueWithPropertyName(tref.RefEntityType, curItem, refcol.CsName));
|
||||||
|
_db.Orm.SetEntityValueWithPropertyName(tref.RefMiddleEntityType, newItem, tref.MiddleColumns[midcolidx].CsName, refval);
|
||||||
|
}
|
||||||
|
midListAdd.Add(newItem);
|
||||||
|
}
|
||||||
|
await midSet.AddRangeAsync(midListAdd);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Internal.Model.TableRefType.OneToMany:
|
||||||
|
foreach (var propValItem in propValEach)
|
||||||
|
{
|
||||||
|
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||||
|
{
|
||||||
|
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.Orm.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||||
|
_db.Orm.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
|
||||||
|
}
|
||||||
|
await refSet.AddOrUpdateAsync(propValItem);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
namespace FreeSql
|
namespace FreeSql
|
||||||
{
|
{
|
||||||
@ -137,8 +139,16 @@ namespace FreeSql
|
|||||||
if (_table.ColumnsByCsIgnore.ContainsKey(prop.Key)) continue;
|
if (_table.ColumnsByCsIgnore.ContainsKey(prop.Key)) continue;
|
||||||
if (_table.ColumnsByCs.ContainsKey(prop.Key)) continue;
|
if (_table.ColumnsByCs.ContainsKey(prop.Key)) continue;
|
||||||
|
|
||||||
object propVal = null;
|
var tref = _table.GetTableRef(prop.Key, true);
|
||||||
|
if (tref == null) continue;
|
||||||
|
switch (tref.RefType)
|
||||||
|
{
|
||||||
|
case Internal.Model.TableRefType.OneToOne:
|
||||||
|
case Internal.Model.TableRefType.ManyToOne:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
object propVal = null;
|
||||||
if (itemType == null) itemType = item.GetType();
|
if (itemType == null) itemType = item.GetType();
|
||||||
if (_table.TypeLazy != null && itemType == _table.TypeLazy)
|
if (_table.TypeLazy != null && itemType == _table.TypeLazy)
|
||||||
{
|
{
|
||||||
@ -157,33 +167,103 @@ namespace FreeSql
|
|||||||
if (propVal == null) continue;
|
if (propVal == null) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tref = _table.GetTableRef(prop.Key, true);
|
|
||||||
if (tref == null) continue;
|
|
||||||
|
|
||||||
switch (tref.RefType)
|
|
||||||
{
|
|
||||||
case Internal.Model.TableRefType.OneToOne:
|
|
||||||
case Internal.Model.TableRefType.ManyToOne:
|
|
||||||
case Internal.Model.TableRefType.ManyToMany:
|
|
||||||
continue;
|
|
||||||
case Internal.Model.TableRefType.OneToMany:
|
|
||||||
var propValEach = propVal as IEnumerable;
|
var propValEach = propVal as IEnumerable;
|
||||||
if (propValEach == null) continue;
|
if (propValEach == null) continue;
|
||||||
object dbset = null;
|
DbSet<object> refSet = _db.Set<object>().AsType(tref.RefEntityType);
|
||||||
MethodInfo dbsetAddOrUpdate = null;
|
switch (tref.RefType)
|
||||||
|
{
|
||||||
|
case Internal.Model.TableRefType.ManyToMany:
|
||||||
|
var curList = new List<object>();
|
||||||
foreach (var propValItem in propValEach)
|
foreach (var propValItem in propValEach)
|
||||||
{
|
{
|
||||||
if (dbset == null)
|
curList.Add(propValItem);
|
||||||
{
|
var flagExists = refSet.ExistsInStates(propValItem);
|
||||||
dbset = _db.Set(tref.RefEntityType);
|
if (flagExists == false)
|
||||||
dbsetAddOrUpdate = dbset.GetType().GetMethod("AddOrUpdate", new Type[] { tref.RefEntityType });
|
flagExists = refSet.Select.WhereDynamic(propValItem).Any();
|
||||||
|
if (refSet.CanAdd(propValItem, false) && flagExists != true)
|
||||||
|
refSet.Add(propValItem);
|
||||||
}
|
}
|
||||||
|
var midSelectParam = Expression.Parameter(typeof(object), "a");
|
||||||
|
var midWheres = new List<Expression<Func<object, bool>>>();
|
||||||
|
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||||
|
midWheres.Add(Expression.Lambda<Func<object, bool>>(Expression.Equal(
|
||||||
|
Expression.MakeMemberAccess(Expression.Convert(midSelectParam, tref.RefMiddleEntityType), tref.MiddleColumns[colidx].Table.Properties[tref.MiddleColumns[colidx].CsName]),
|
||||||
|
Expression.Constant(
|
||||||
|
FreeSql.Internal.Utils.GetDataReaderValue(
|
||||||
|
tref.MiddleColumns[colidx].CsType,
|
||||||
|
_db.Orm.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName)), tref.MiddleColumns[colidx].CsType)
|
||||||
|
), midSelectParam));
|
||||||
|
|
||||||
|
if (curList.Any() == false) //全部删除
|
||||||
|
{
|
||||||
|
var delall = _db.Orm.Delete<object>().AsType(tref.RefMiddleEntityType);
|
||||||
|
foreach (var midWhere in midWheres) delall.Where(midWhere);
|
||||||
|
delall.ExecuteAffrows();
|
||||||
|
}
|
||||||
|
else //保存
|
||||||
|
{
|
||||||
|
var midSet = _db.Set<object>().AsType(tref.RefMiddleEntityType);
|
||||||
|
var midSelect = midSet.Select;
|
||||||
|
foreach (var midWhere in midWheres) midSelect.Where(midWhere);
|
||||||
|
var midList = midSelect.ToList();
|
||||||
|
var midListDel = new List<object>();
|
||||||
|
var midListAdd = new List<object>();
|
||||||
|
|
||||||
|
foreach (var midItem in midList)
|
||||||
|
{
|
||||||
|
var curContains = new List<int>();
|
||||||
|
for(var curIdx = 0; curIdx < curList.Count; curIdx ++)
|
||||||
|
{
|
||||||
|
var isEquals = true;
|
||||||
|
for (var midcolidx = tref.Columns.Count; midcolidx < tref.MiddleColumns.Count; midcolidx++)
|
||||||
|
{
|
||||||
|
var refcol = tref.Columns[midcolidx - tref.Columns.Count];
|
||||||
|
var midval = FreeSql.Internal.Utils.GetDataReaderValue(refcol.CsType, _db.Orm.GetEntityValueWithPropertyName(tref.RefMiddleEntityType, midItem, tref.MiddleColumns[midcolidx].CsName));
|
||||||
|
var refval = FreeSql.Internal.Utils.GetDataReaderValue(refcol.CsType, _db.Orm.GetEntityValueWithPropertyName(tref.RefEntityType, curList[curIdx], tref.Columns[midcolidx - tref.Columns.Count].CsName));
|
||||||
|
if (object.Equals(midval, refval) == false)
|
||||||
|
{
|
||||||
|
isEquals = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isEquals)
|
||||||
|
curContains.Add(curIdx);
|
||||||
|
}
|
||||||
|
if (curContains.Any())
|
||||||
|
foreach (var curIdx in curContains)
|
||||||
|
curList.RemoveAt(curIdx);
|
||||||
|
else
|
||||||
|
midListDel.Add(midItem);
|
||||||
|
}
|
||||||
|
midSet.RemoveRange(midListDel); //删除未保存的项
|
||||||
|
foreach (var curItem in curList)
|
||||||
|
{
|
||||||
|
var newItem = Activator.CreateInstance(tref.RefMiddleEntityType);
|
||||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||||
{
|
{
|
||||||
tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName]
|
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.MiddleColumns[colidx].CsType, _db.Orm.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||||
.SetValue(propValItem, tref.Columns[colidx].Table.Properties[tref.Columns[colidx].CsName].GetValue(item));
|
_db.Orm.SetEntityValueWithPropertyName(tref.RefMiddleEntityType, newItem, tref.MiddleColumns[colidx].CsName, val);
|
||||||
}
|
}
|
||||||
dbsetAddOrUpdate.Invoke(dbset, new object[] { propValItem });
|
for (var midcolidx = tref.Columns.Count; midcolidx < tref.MiddleColumns.Count; midcolidx++)
|
||||||
|
{
|
||||||
|
var refcol = tref.Columns[midcolidx - tref.Columns.Count];
|
||||||
|
var refval = FreeSql.Internal.Utils.GetDataReaderValue(tref.MiddleColumns[midcolidx].CsType, _db.Orm.GetEntityValueWithPropertyName(tref.RefEntityType, curItem, refcol.CsName));
|
||||||
|
_db.Orm.SetEntityValueWithPropertyName(tref.RefMiddleEntityType, newItem, tref.MiddleColumns[midcolidx].CsName, refval);
|
||||||
|
}
|
||||||
|
midListAdd.Add(newItem);
|
||||||
|
}
|
||||||
|
midSet.AddRange(midListAdd);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Internal.Model.TableRefType.OneToMany:
|
||||||
|
foreach (var propValItem in propValEach)
|
||||||
|
{
|
||||||
|
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||||
|
{
|
||||||
|
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.Orm.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||||
|
_db.Orm.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
|
||||||
|
}
|
||||||
|
refSet.AddOrUpdate(propValItem);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,16 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="P:FreeSql.DbContextOptions.EnableAddOrUpdateNavigateList">
|
<member name="P:FreeSql.DbContextOptions.EnableAddOrUpdateNavigateList">
|
||||||
<summary>
|
<summary>
|
||||||
是否开启一对多,联级保存功能
|
是否开启一对多,多对多联级保存功能<para></para>
|
||||||
|
<para></para>
|
||||||
|
【一对多】模型下, 保存时可联级保存实体的属性集合。出于使用安全考虑我们没做完整对比,只实现实体属性集合的添加或更新操作,所以不会删除实体属性集合的数据。<para></para>
|
||||||
|
完整对比的功能使用起来太危险,试想下面的场景:<para></para>
|
||||||
|
- 保存的时候,实体的属性集合是空的,如何操作?记录全部删除?<para></para>
|
||||||
|
- 保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作?<para></para>
|
||||||
|
<para></para>
|
||||||
|
【多对多】模型下,我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(注意不会更新)
|
||||||
|
- 属性集合为空时,删除他们的所有关联数据(中间表)
|
||||||
|
- 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:FreeSql.DbSet`1.AsType(System.Type)">
|
<member name="M:FreeSql.DbSet`1.AsType(System.Type)">
|
||||||
@ -158,6 +167,11 @@
|
|||||||
<param name="entityType"></param>
|
<param name="entityType"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="P:FreeSql.IBaseRepository.DbContextOptions">
|
||||||
|
<summary>
|
||||||
|
设置 DbContext 选项
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="M:FreeSql.IBasicRepository`1.FlushState">
|
<member name="M:FreeSql.IBasicRepository`1.FlushState">
|
||||||
<summary>
|
<summary>
|
||||||
清空状态数据
|
清空状态数据
|
||||||
@ -193,14 +207,14 @@
|
|||||||
开启工作单元
|
开启工作单元
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:FreeSqlDbContextExtenssions.CreateDbContext(IFreeSql)">
|
<member name="M:FreeSqlDbContextExtensions.CreateDbContext(IFreeSql)">
|
||||||
<summary>
|
<summary>
|
||||||
创建普通数据上下文档对象
|
创建普通数据上下文档对象
|
||||||
</summary>
|
</summary>
|
||||||
<param name="that"></param>
|
<param name="that"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:FreeSqlDbContextExtenssions.NoTracking``1(FreeSql.ISelect{``0})">
|
<member name="M:FreeSqlDbContextExtensions.NoTracking``1(FreeSql.ISelect{``0})">
|
||||||
<summary>
|
<summary>
|
||||||
不跟踪查询的实体数据(在不需要更新其数据时使用),可提长查询性能
|
不跟踪查询的实体数据(在不需要更新其数据时使用),可提长查询性能
|
||||||
</summary>
|
</summary>
|
||||||
@ -208,7 +222,7 @@
|
|||||||
<param name="select"></param>
|
<param name="select"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:FreeSqlDbContextExtenssions.SetDbContextOptions(IFreeSql,System.Action{FreeSql.DbContextOptions})">
|
<member name="M:FreeSqlDbContextExtensions.SetDbContextOptions(IFreeSql,System.Action{FreeSql.DbContextOptions})">
|
||||||
<summary>
|
<summary>
|
||||||
设置 DbContext 选项设置
|
设置 DbContext 选项设置
|
||||||
</summary>
|
</summary>
|
||||||
|
@ -59,6 +59,7 @@ namespace FreeSql
|
|||||||
}
|
}
|
||||||
public Type EntityType => _dbsetPriv?.EntityType ?? typeof(TEntity);
|
public Type EntityType => _dbsetPriv?.EntityType ?? typeof(TEntity);
|
||||||
public void AsType(Type entityType) => _dbset.AsType(entityType);
|
public void AsType(Type entityType) => _dbset.AsType(entityType);
|
||||||
|
public DbContextOptions DbContextOptions { get => _db.Options; set => _db.Options = value; }
|
||||||
|
|
||||||
public IFreeSql Orm { get; private set; }
|
public IFreeSql Orm { get; private set; }
|
||||||
public IUnitOfWork UnitOfWork { get; set; }
|
public IUnitOfWork UnitOfWork { get; set; }
|
||||||
|
@ -17,6 +17,11 @@ namespace FreeSql
|
|||||||
/// <param name="entityType"></param>
|
/// <param name="entityType"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
void AsType(Type entityType);
|
void AsType(Type entityType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置 DbContext 选项
|
||||||
|
/// </summary>
|
||||||
|
DbContextOptions DbContextOptions { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IBaseRepository<TEntity> : IReadOnlyRepository<TEntity>, IBasicRepository<TEntity>
|
public interface IBaseRepository<TEntity> : IReadOnlyRepository<TEntity>, IBasicRepository<TEntity>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
using FreeSql.DataAnnotations;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace FreeSql.Tests
|
namespace FreeSql.Tests
|
||||||
@ -277,5 +279,179 @@ namespace FreeSql.Tests
|
|||||||
repos.DataFilter.Apply("xxx", a => (a as AddUpdateInfo).Clicks == 2);
|
repos.DataFilter.Apply("xxx", a => (a as AddUpdateInfo).Clicks == 2);
|
||||||
Assert.Null(repos.Find(item.Id));
|
Assert.Null(repos.Find(item.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnableAddOrUpdateNavigateList_OneToMany()
|
||||||
|
{
|
||||||
|
var repo = g.sqlite.GetRepository<Cagetory>();
|
||||||
|
var cts = new[] {
|
||||||
|
new Cagetory
|
||||||
|
{
|
||||||
|
Name = "分类1",
|
||||||
|
Goodss = new List<Goods>(new[]
|
||||||
|
{
|
||||||
|
new Goods { Name = "商品1" },
|
||||||
|
new Goods { Name = "商品2" },
|
||||||
|
new Goods { Name = "商品3" }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
new Cagetory
|
||||||
|
{
|
||||||
|
Name = "分类2",
|
||||||
|
Goodss = new List<Goods>(new[]
|
||||||
|
{
|
||||||
|
new Goods { Name = "商品4" },
|
||||||
|
new Goods { Name = "商品5" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
repo.Insert(cts);
|
||||||
|
cts[0].Name = "分类11";
|
||||||
|
cts[0].Goodss.Clear();
|
||||||
|
cts[1].Name = "分类22";
|
||||||
|
cts[1].Goodss.Clear();
|
||||||
|
repo.Update(cts);
|
||||||
|
cts[0].Name = "分类111";
|
||||||
|
cts[0].Goodss.Clear();
|
||||||
|
cts[0].Goodss.Add(new Goods { Name = "商品33" });
|
||||||
|
cts[1].Name = "分类222";
|
||||||
|
cts[1].Goodss.Clear();
|
||||||
|
cts[1].Goodss.Add(new Goods { Name = "商品55" });
|
||||||
|
repo.Update(cts);
|
||||||
|
}
|
||||||
|
[Table(Name = "EAUNL_OTM_CT")]
|
||||||
|
class Cagetory
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Navigate("CagetoryId")]
|
||||||
|
public List<Goods> Goodss { get; set; }
|
||||||
|
}
|
||||||
|
[Table(Name = "EAUNL_OTM_GD")]
|
||||||
|
class Goods
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid CagetoryId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnableAddOrUpdateNavigateList_OneToMany_Parent()
|
||||||
|
{
|
||||||
|
var repo = g.sqlite.GetRepository<CagetoryParent>();
|
||||||
|
var cts = new[] {
|
||||||
|
new CagetoryParent
|
||||||
|
{
|
||||||
|
Name = "分类1",
|
||||||
|
Childs = new List<CagetoryParent>(new[]
|
||||||
|
{
|
||||||
|
new CagetoryParent { Name = "分类1_1" },
|
||||||
|
new CagetoryParent { Name = "分类1_2" },
|
||||||
|
new CagetoryParent { Name = "分类1_3" }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
new CagetoryParent
|
||||||
|
{
|
||||||
|
Name = "分类2",
|
||||||
|
Childs = new List<CagetoryParent>(new[]
|
||||||
|
{
|
||||||
|
new CagetoryParent { Name = "分类2_1" },
|
||||||
|
new CagetoryParent { Name = "分类2_2" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
repo.Insert(cts);
|
||||||
|
cts[0].Name = "分类11";
|
||||||
|
cts[0].Childs.Clear();
|
||||||
|
cts[1].Name = "分类22";
|
||||||
|
cts[1].Childs.Clear();
|
||||||
|
repo.Update(cts);
|
||||||
|
cts[0].Name = "分类111";
|
||||||
|
cts[0].Childs.Clear();
|
||||||
|
cts[0].Childs.Add(new CagetoryParent { Name = "分类1_33" });
|
||||||
|
cts[1].Name = "分类222";
|
||||||
|
cts[1].Childs.Clear();
|
||||||
|
cts[1].Childs.Add(new CagetoryParent { Name = "分类2_22" });
|
||||||
|
repo.Update(cts);
|
||||||
|
}
|
||||||
|
[Table(Name = "EAUNL_OTMP_CT")]
|
||||||
|
class CagetoryParent
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Guid ParentId { get; set; }
|
||||||
|
[Navigate("ParentId")]
|
||||||
|
public List<CagetoryParent> Childs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnableAddOrUpdateNavigateList_ManyToMany()
|
||||||
|
{
|
||||||
|
var tags = new[] {
|
||||||
|
new Tag { TagName = "流行" },
|
||||||
|
new Tag { TagName = "80后" },
|
||||||
|
new Tag { TagName = "00后" },
|
||||||
|
new Tag { TagName = "摇滚" }
|
||||||
|
};
|
||||||
|
var ss = new[]
|
||||||
|
{
|
||||||
|
new Song
|
||||||
|
{
|
||||||
|
Name = "爱你一万年.mp3",
|
||||||
|
Tags = new List<Tag>(new[]
|
||||||
|
{
|
||||||
|
tags[0], tags[1]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
new Song
|
||||||
|
{
|
||||||
|
Name = "李白.mp3",
|
||||||
|
Tags = new List<Tag>(new[]
|
||||||
|
{
|
||||||
|
tags[0], tags[2]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var repo = g.sqlite.GetRepository<Song>();
|
||||||
|
repo.Insert(ss);
|
||||||
|
|
||||||
|
ss[0].Name = "爱你一万年.mp5";
|
||||||
|
ss[0].Tags.Clear();
|
||||||
|
ss[0].Tags.Add(tags[0]);
|
||||||
|
ss[1].Name = "李白.mp5";
|
||||||
|
ss[1].Tags.Clear();
|
||||||
|
ss[1].Tags.Add(tags[3]);
|
||||||
|
repo.Update(ss);
|
||||||
|
|
||||||
|
ss[0].Name = "爱你一万年.mp4";
|
||||||
|
ss[0].Tags.Clear();
|
||||||
|
ss[1].Name = "李白.mp4";
|
||||||
|
ss[1].Tags.Clear();
|
||||||
|
repo.Update(ss);
|
||||||
|
}
|
||||||
|
[Table(Name = "EAUNL_MTM_SONG")]
|
||||||
|
class Song
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public List<Tag> Tags { get; set; }
|
||||||
|
}
|
||||||
|
[Table(Name = "EAUNL_MTM_TAG")]
|
||||||
|
class Tag
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string TagName { get; set; }
|
||||||
|
public List<Song> Songs { get; set; }
|
||||||
|
}
|
||||||
|
[Table(Name = "EAUNL_MTM_SONGTAG")]
|
||||||
|
class SongTag
|
||||||
|
{
|
||||||
|
public Guid SongId { get; set; }
|
||||||
|
public Song Song { get; set; }
|
||||||
|
public Guid TagId { get; set; }
|
||||||
|
public Tag Tag { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user