diff --git a/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepositorySync.cs b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepositorySync.cs index 5e0306e37..cc864d1ff 100644 --- a/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepositorySync.cs +++ b/Extensions/FreeSql.Extensions.AggregateRoot/AggregateRootRepository/AggregateRootRepositorySync.cs @@ -15,6 +15,15 @@ namespace FreeSql { partial class AggregateRootRepository { + public virtual void SaveMany(TEntity entity, string propertyName) + { + var tracking = new AggregateRootTrackingChangeInfo(); + var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); + if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}"); + AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking); + SaveTrackingChange(tracking); + Attach(entity); //应该只存储 propertyName 内容 + } #region BeginEdit/EndEdit List _dataEditing; diff --git a/FreeSql.DbContext/DbSet/DbSetSync.cs b/FreeSql.DbContext/DbSet/DbSetSync.cs index 8cc04d29d..660acaad8 100644 --- a/FreeSql.DbContext/DbSet/DbSetSync.cs +++ b/FreeSql.DbContext/DbSet/DbSetSync.cs @@ -843,5 +843,73 @@ void LocalEach(DbSet dbset, IEnumerable items, bool isOneToOne) } } #endregion + + /// + /// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比) + /// 场景:在关闭级联保存功能之后,手工使用本方法 + /// 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus") + /// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据 + /// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录 + /// + /// 实体对象 + /// 属性名 + public void SaveMany(TEntity item, string propertyName) + { + if (item == null) return; + if (string.IsNullOrEmpty(propertyName)) return; + if (_table.Properties.TryGetValue(propertyName, out var prop) == false) throw new KeyNotFoundException(DbContextStrings.NotFound_Property(_table.Type.FullName, propertyName)); + if (_table.ColumnsByCsIgnore.ContainsKey(propertyName)) throw new ArgumentException(DbContextStrings.TypeHasSetProperty_IgnoreAttribute(_table.Type.FullName, propertyName)); + + var tref = _table.GetTableRef(propertyName, true, false); + if (tref == null) return; + switch (tref.RefType) + { + case TableRefType.OneToOne: + case TableRefType.ManyToOne: + case TableRefType.PgArrayToMany: + throw new ArgumentException(DbContextStrings.PropertyOfType_IsNot_OneToManyOrManyToMany(_table.Type.FullName, propertyName)); + } + + DbContextFlushCommand(); + var oldEnable = _db.Options.EnableCascadeSave; + _db.Options.EnableCascadeSave = false; + try + { + AddOrUpdateNavigate(item, false, propertyName); + if (tref.RefType == TableRefType.OneToMany) + { + DbContextFlushCommand(); + //删除没有保存的数据,求出主体的条件 + var deleteWhereParentParam = Expression.Parameter(typeof(object), "a"); + Expression whereParentExp = null; + for (var colidx = 0; colidx < tref.Columns.Count; colidx++) + { + var whereExp = Expression.Equal( + Expression.MakeMemberAccess(Expression.Convert(deleteWhereParentParam, tref.RefEntityType), tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName]), + Expression.Constant( + FreeSql.Internal.Utils.GetDataReaderValue( + tref.Columns[colidx].CsType, + _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName)), tref.RefColumns[colidx].CsType) + ); + if (whereParentExp == null) whereParentExp = whereExp; + else whereParentExp = Expression.AndAlso(whereParentExp, whereExp); + } + var propValEach = GetItemValue(item, prop) as IEnumerable; + var subDelete = _db.OrmOriginal.Delete().AsType(tref.RefEntityType) + .WithTransaction(_uow?.GetOrBeginTransaction()) + .Where(Expression.Lambda>(whereParentExp, deleteWhereParentParam)); + foreach (var propValItem in propValEach) + { + subDelete.WhereDynamic(propValEach, true); + break; + } + subDelete.ExecuteAffrows(); + } + } + finally + { + _db.Options.EnableCascadeSave = oldEnable; + } + } } } diff --git a/FreeSql.DbContext/FreeSql.DbContext.xml b/FreeSql.DbContext/FreeSql.DbContext.xml index f596cf012..7bd2291ce 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.xml +++ b/FreeSql.DbContext/FreeSql.DbContext.xml @@ -221,6 +221,17 @@ + + + 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比) + 场景:在关闭级联保存功能之后,手工使用本方法 + 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus") + 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据 + 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录 + + 实体对象 + 属性名 + 使用 FreeSql FluentApi 方法,当 EFCore FluentApi 方法无法表示的时候使用 @@ -573,6 +584,17 @@ + + + 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比) + 场景:在关闭级联保存功能之后,手工使用本方法 + 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus") + 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据 + 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录 + + 实体对象 + 属性名 + 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行 diff --git a/FreeSql.DbContext/Repository/Repository/BaseRepository.cs b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs index 430e4df7c..c22418f93 100644 --- a/FreeSql.DbContext/Repository/Repository/BaseRepository.cs +++ b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs @@ -151,6 +151,12 @@ public virtual TEntity InsertOrUpdate(TEntity entity) return entity; } + public virtual void SaveMany(TEntity entity, string propertyName) + { + _dbset.SaveMany(entity, propertyName); + _db.SaveChanges(); + } + public virtual void BeginEdit(List data) => _dbset.BeginEdit(data); public virtual int EndEdit(List data = null) { diff --git a/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs b/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs index 4a229d74e..662a6ee10 100644 --- a/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs +++ b/FreeSql.DbContext/Repository/Repository/IBaseRepository.cs @@ -90,6 +90,17 @@ public interface IBaseRepository : IBaseRepository /// List DeleteCascadeByDatabase(Expression> predicate); + /// + /// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比) + /// 场景:在关闭级联保存功能之后,手工使用本方法 + /// 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus") + /// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据 + /// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录 + /// + /// 实体对象 + /// 属性名 + void SaveMany(TEntity entity, string propertyName); + /// /// 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行 /// 场景:winform 加载表数据后,一顿添加、修改、删除操作之后,最后才点击【保存】 diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index adc11411f..a7c8c5598 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -1087,6 +1087,93 @@ + + + 动态创建实体类型 + + + + + 配置Class + + 类名 + 类标记的特性[Table(Name = "xxx")] [Index(xxxx)] + + + + + 获取类型构建器,可作为要构建的Type来引用 + + + + + 配置属性 + + 属性名称 + 属性类型 + 属性标记的特性-支持多个 + + + + + 配置属性 + + 属性名称 + 属性类型 + 该属性是否重写父类属性 + 属性标记的特性-支持多个 + + + + + 配置属性 + + 属性名称 + 属性类型 + 该属性是否重写父类属性 + 属性默认值 + 属性标记的特性-支持多个 + + + + + 配置父类 + + 父类类型 + + + + + Override属性 + + + + + + Emit动态创建出Class - Type + + + + + + Emit动态创建出Class - Type,不附带获取TableInfo + + + + + + 首字母小写 + + + + + + + 首字母大写 + + + + 获取实体的主键值,以 "*|_,[,_|*" 分割,当任意一个主键属性无值时,返回 "" @@ -3271,6 +3358,13 @@ + + + 执行SQL语句,返回更新后的记录 + 注意:此方法只有 Postgresql/SqlServer 有效果 + + + 指定事务对象 @@ -3615,6 +3709,177 @@ + + + 测试数据库是否连接正确,本方法执行如下命令: + MySql/SqlServer/PostgreSQL/达梦/人大金仓/神通: SELECT 1 + Oracle: SELECT 1 FROM dual + + 命令超时设置(秒) + + true: 成功, false: 失败 + + + + 查询,若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + + + + + + + + + + 查询,ExecuteReaderAsync(dr => {}, "select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteArrayAsync("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteDataSetAsync("select * from user where age > @age; select 2", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 查询 + + + + + + + + + 查询,ExecuteDataTableAsync("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 在【主库】执行 + + + + + + + + + 在【主库】执行,ExecuteNonQueryAsync("delete from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 在【主库】执行 + + + + + + + + + 在【主库】执行,ExecuteScalarAsync("select 1 from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > @age", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > @age", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + + + 执行SQL返回对象集合,Query<User>("select * from user where age > @age; select * from address", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + + + 执行SQL返回对象集合,Query<User, Address>("select * from user where age > @age; select * from address", new { age = 25 }) + 提示:parms 参数还可以传 Dictionary<string, object> + + + + + + + + 可自定义解析表达式 @@ -4614,6 +4879,12 @@ 超时 + + + 获取资源 + + + 使用完毕后,归还资源 @@ -4689,6 +4960,12 @@ 资源对象 + + + 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 + + 资源对象 + 归还对象给对象池的时候触发 @@ -5619,6 +5896,28 @@ 请使用 fsql.InsertDict(dict) 方法插入字典数据 + + + 动态构建Class Type + + + + + + 根据字典,创建 table 对应的实体对象 + + + + + + + + 根据实体对象,创建 table 对应的字典 + + + + + C#: that >= between && that <= and