diff --git a/Examples/dbcontext_01/Controllers/ValuesController.cs b/Examples/dbcontext_01/Controllers/ValuesController.cs index 8234832e..23fd09ae 100644 --- a/Examples/dbcontext_01/Controllers/ValuesController.cs +++ b/Examples/dbcontext_01/Controllers/ValuesController.cs @@ -30,19 +30,51 @@ namespace dbcontext_01.Controllers long id = 0; try { - using (var ctx = new SongContext()) { + var repos2Song = _orm.GetRepository(); + repos2Song.Where(a => a.Id > 10).ToList(); + //查询结果,进入 states + + var song = new Song { }; + repos2Song.Insert(song); + id = song.Id; + + var adds = Enumerable.Range(0, 100) + .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) + .ToList(); + //创建一堆无主键值 + + repos2Song.Insert(adds); + + for (var a = 0; a < 10; a++) + adds[a].Title = "dkdkdkdk" + a; + + repos2Song.Update(adds); + //批量修改 + + repos2Song.Delete(adds.Skip(10).Take(20).ToList()); + //批量删除,10-20 元素的主键值会被清除 + + adds.Last().Url = "skldfjlksdjglkjjcccc"; + repos2Song.Update(adds.Last()); + + adds.First().Url = "skldfjlksdjglkjjcccc"; + repos2Song.Update(adds.First()); + + + using (var ctx = new SongContext()) { + ctx.Songs.Select.Where(a => a.Id > 10).ToList(); //查询结果,进入 states - var song = new Song { }; + song = new Song { }; //可插入的 song ctx.Songs.Add(song); id = song.Id; //因有自增类型,立即开启事务执行SQL,返回自增值 - var adds = Enumerable.Range(0, 100) + adds = Enumerable.Range(0, 100) .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) .ToList(); //创建一堆无主键值 @@ -63,7 +95,10 @@ namespace dbcontext_01.Controllers adds.Last().Url = "skldfjlksdjglkjjcccc"; ctx.Songs.Update(adds.Last()); - + + adds.First().Url = "skldfjlksdjglkjjcccc"; + ctx.Songs.Update(adds.First()); + //单条修改 urls 的值,进入队列 //throw new Exception("回滚"); @@ -75,6 +110,43 @@ namespace dbcontext_01.Controllers //打包【执行队列】,提交事务 } + using (var uow = _orm.CreateUnitOfWork()) { + + var reposSong = uow.GetRepository(); + reposSong.Where(a => a.Id > 10).ToList(); + //查询结果,进入 states + + song = new Song { }; + reposSong.Insert(song); + id = song.Id; + + adds = Enumerable.Range(0, 100) + .Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" }) + .ToList(); + //创建一堆无主键值 + + reposSong.Insert(adds); + + for (var a = 0; a < 10; a++) + adds[a].Title = "dkdkdkdk" + a; + + reposSong.Update(adds); + //批量修改 + + reposSong.Delete(adds.Skip(10).Take(20).ToList()); + //批量删除,10-20 元素的主键值会被清除 + + adds.Last().Url = "skldfjlksdjglkjjcccc"; + reposSong.Update(adds.Last()); + + adds.First().Url = "skldfjlksdjglkjjcccc"; + reposSong.Update(adds.First()); + + uow.Commit(); + } + + + //using (var ctx = new SongContext()) { // var song = new Song { }; diff --git a/FreeSql.DbContext/DbContext/DbContext.cs b/FreeSql.DbContext/DbContext/DbContext.cs index 9bb6ddab..274e9e4a 100644 --- a/FreeSql.DbContext/DbContext/DbContext.cs +++ b/FreeSql.DbContext/DbContext/DbContext.cs @@ -13,8 +13,8 @@ namespace FreeSql { internal IFreeSql _orm; internal IFreeSql _fsql => _orm ?? throw new ArgumentNullException("请在 OnConfiguring 或 AddFreeDbContext 中配置 UseFreeSql"); - Object _conn; - DbTransaction _tran; + UnitOfWork _uowPriv; + internal UnitOfWork _uow => _uowPriv ?? (_uowPriv = new UnitOfWork(_fsql)); static ConcurrentDictionary _dicGetDbSetProps = new ConcurrentDictionary(); protected DbContext() { @@ -34,8 +34,6 @@ namespace FreeSql { prop.SetValue(this, set); AllSets.Add(prop.Name, set); } - - //_fsql.Aop.ToList += AopToList; } protected virtual void OnConfiguring(DbContextOptionsBuilder builder) { @@ -68,49 +66,18 @@ namespace FreeSql { _actions.Enqueue(new ExecCommandInfo { actionType = actionType, dbSet = dbSet, stateType = stateType, state = state }); } - void ReturnObject() { - _fsql.Ado.MasterPool.Return(_conn); - _tran = null; - _conn = null; - } - internal DbTransaction GetOrBeginTransaction(bool isCreate = true) { - - if (_tran != null) return _tran; - if (isCreate == false) return null; - if (_conn != null) _fsql.Ado.MasterPool.Return(_conn); - - _conn = _fsql.Ado.MasterPool.Get(); - try { - _tran = _conn.Value.BeginTransaction(); - } catch { - ReturnObject(); - throw; - } - return _tran; - } - - void Commit() { - if (_tran != null) { - try { - _tran.Commit(); - } finally { - ReturnObject(); - } - } - } - void Rollback() { - _actions.Clear(); - if (_tran != null) { - try { - _tran.Rollback(); - } finally { - ReturnObject(); - } - } + ~DbContext() { + this.Dispose(); } + bool _isdisposed = false; public void Dispose() { - //_fsql.Aop.ToList -= AopToList; - this.Rollback(); + if (_isdisposed) return; + try { + _uow.Rollback(); + } finally { + _isdisposed = true; + GC.SuppressFinalize(this); + } } } } diff --git a/FreeSql.DbContext/DbContext/DbContextAsync.cs b/FreeSql.DbContext/DbContext/DbContextAsync.cs index 5f282702..135de8db 100644 --- a/FreeSql.DbContext/DbContext/DbContextAsync.cs +++ b/FreeSql.DbContext/DbContext/DbContextAsync.cs @@ -1 +1,115 @@ - \ No newline at end of file +using SafeObjectPool; +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Data.Common; +using System.Linq; +using System.Reflection; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace FreeSql { + partial class DbContext { + + async public Task SaveChangesAsync() { + await ExecCommandAsync(); + _uow.Commit(); + return _affrows; + } + + static Dictionary>>> _dicExecCommandDbContextBetchAsync = new Dictionary>>>(); + async internal Task ExecCommandAsync() { + ExecCommandInfo oldinfo = null; + var states = new List(); + + Func> dbContextBetch = methodName => { + if (_dicExecCommandDbContextBetchAsync.TryGetValue(oldinfo.stateType, out var trydic) == false) + trydic = new Dictionary>>(); + if (trydic.TryGetValue(methodName, out var tryfunc) == false) { + var arrType = oldinfo.stateType.MakeArrayType(); + var dbsetType = oldinfo.dbSet.GetType().BaseType; + var dbsetTypeMethod = dbsetType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null); + + var returnTarget = Expression.Label(typeof(Task)); + var parm1DbSet = Expression.Parameter(typeof(object)); + var parm2Vals = Expression.Parameter(typeof(object[])); + var var1Vals = Expression.Variable(arrType); + tryfunc = Expression.Lambda>>(Expression.Block( + new[] { var1Vals }, + Expression.Assign(var1Vals, Expression.Convert(FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(arrType, parm2Vals), arrType)), + Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals)), + Expression.Label(returnTarget, Expression.Default(typeof(int))) + ), new[] { parm1DbSet, parm2Vals }).Compile(); + trydic.Add(methodName, tryfunc); + } + return tryfunc(oldinfo.dbSet, states.ToArray()); + }; + Func funcDelete = async () => { + _affrows += await dbContextBetch("DbContextBetchRemoveAsync"); + states.Clear(); + }; + Func funcInsert = async () => { + _affrows += await dbContextBetch("DbContextBetchAddAsync"); + states.Clear(); + }; + Func funcUpdate = async (isLiveUpdate) => { + var affrows = 0; + if (isLiveUpdate) affrows = await dbContextBetch("DbContextBetchUpdateNowAsync"); + else affrows = await dbContextBetch("DbContextBetchUpdateAsync"); + if (affrows == -999) { //最后一个元素已被删除 + states.RemoveAt(states.Count - 1); + return; + } + if (affrows == -998 || affrows == -997) { //没有执行更新 + var laststate = states[states.Count - 1]; + states.Clear(); + if (affrows == -997) states.Add(laststate); //保留最后一个 + } + if (affrows > 0) { + _affrows += affrows; + var islastNotUpdated = states.Count != affrows; + var laststate = states[states.Count - 1]; + states.Clear(); + if (islastNotUpdated) states.Add(laststate); //保留最后一个 + } + }; + + while (_actions.Any() || states.Any()) { + var info = _actions.Any() ? _actions.Dequeue() : null; + if (oldinfo == null) oldinfo = info; + var isLiveUpdate = false; + + if (_actions.Any() == false && states.Any() || + info != null && oldinfo.actionType != info.actionType || + info != null && oldinfo.stateType != info.stateType) { + + if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType) { + //最后一个,合起来发送 + states.Add(info.state); + info = null; + } + + switch (oldinfo.actionType) { + case ExecCommandInfoType.Insert: + await funcInsert(); + break; + case ExecCommandInfoType.Delete: + await funcDelete(); + break; + } + isLiveUpdate = true; + } + + if (isLiveUpdate || oldinfo.actionType == ExecCommandInfoType.Update) { + if (states.Any()) + await funcUpdate(isLiveUpdate); + } + + if (info != null) { + states.Add(info.state); + oldinfo = info; + } + } + } + } +} diff --git a/FreeSql.DbContext/DbContext/DbContextSync.cs b/FreeSql.DbContext/DbContext/DbContextSync.cs index b4e8dc37..e06a110e 100644 --- a/FreeSql.DbContext/DbContext/DbContextSync.cs +++ b/FreeSql.DbContext/DbContext/DbContextSync.cs @@ -12,7 +12,7 @@ namespace FreeSql { public long SaveChanges() { ExecCommand(); - Commit(); + _uow.Commit(); return _affrows; } diff --git a/FreeSql.DbContext/DbSet/DbSet.cs b/FreeSql.DbContext/DbSet/DbSet.cs index 589253d6..1415f9b9 100644 --- a/FreeSql.DbContext/DbSet/DbSet.cs +++ b/FreeSql.DbContext/DbSet/DbSet.cs @@ -10,37 +10,39 @@ namespace FreeSql { internal class BaseDbSet : DbSet where TEntity : class { public BaseDbSet(DbContext ctx) { - _ctx = ctx; - _fsql = ctx._fsql; + if (ctx != null) { + _ctx = ctx; + _uow = ctx._uow; + _fsql = ctx._fsql; + } } } public abstract partial class DbSet where TEntity : class { internal DbContext _ctx; + internal UnitOfWork _uow; internal IFreeSql _fsql; + bool IsNoneDbContext => _ctx == null; - internal ISelect OrmSelect(object dywhere) { - ExecuteCommand(); //查询前先提交,否则会出脏读 - return _fsql.Select(dywhere).WithTransaction(_ctx?.GetOrBeginTransaction(false)).TrackToList(TrackToList); + protected virtual ISelect OrmSelect(object dywhere) { + DbContextExecCommand(); //查询前先提交,否则会出脏读 + return _fsql.Select(dywhere).WithTransaction(_uow?.GetOrBeginTransaction(false)).TrackToList(TrackToList); } - internal virtual IInsert OrmInsert() => _fsql.Insert().WithTransaction(_ctx?.GetOrBeginTransaction()); - internal virtual IInsert OrmInsert(TEntity data) => _fsql.Insert(data).WithTransaction(_ctx?.GetOrBeginTransaction()); - internal virtual IInsert OrmInsert(TEntity[] data) => _fsql.Insert(data).WithTransaction(_ctx?.GetOrBeginTransaction()); - internal virtual IInsert OrmInsert(IEnumerable data) => _fsql.Insert(data).WithTransaction(_ctx?.GetOrBeginTransaction()); + protected virtual IInsert OrmInsert() => _fsql.Insert().WithTransaction(_uow?.GetOrBeginTransaction()); + protected virtual IInsert OrmInsert(TEntity data) => _fsql.Insert(data).WithTransaction(_uow?.GetOrBeginTransaction()); + protected virtual IInsert OrmInsert(IEnumerable data) => _fsql.Insert(data).WithTransaction(_uow?.GetOrBeginTransaction()); - internal virtual IUpdate OrmUpdate(object dywhere) => _fsql.Update(dywhere).WithTransaction(_ctx?.GetOrBeginTransaction()); - internal virtual IDelete OrmDelete(object dywhere) => _fsql.Delete(dywhere).WithTransaction(_ctx?.GetOrBeginTransaction()); + protected virtual IUpdate OrmUpdate(IEnumerable entitys) => _fsql.Update().SetSource(entitys).WithTransaction(_uow?.GetOrBeginTransaction()); + protected virtual IDelete OrmDelete(object dywhere) => _fsql.Delete(dywhere).WithTransaction(_uow?.GetOrBeginTransaction()); - internal void EnqueueAction(DbContext.ExecCommandInfoType actionType, object dbSet, Type stateType, object state) { - _ctx?.EnqueueAction(actionType, dbSet, stateType, state); - } - internal void ExecuteCommand() { - _ctx?.ExecCommand(); + internal void EnqueueToDbContext(DbContext.ExecCommandInfoType actionType, EntityState state) { + if (IsNoneDbContext == false) + _ctx.EnqueueAction(actionType, this, typeof(EntityState), state); } internal void IncrAffrows(long affrows) { - if (_ctx != null) + if (IsNoneDbContext == false) _ctx._affrows += affrows; } internal void TrackToList(object list) { @@ -63,12 +65,14 @@ namespace FreeSql { public ISelect Where(Expression> exp) => this.OrmSelect(null).Where(exp); public ISelect WhereIf(bool condition, Expression> exp) => this.OrmSelect(null).WhereIf(condition, exp); - Dictionary _states = new Dictionary(); + protected Dictionary _states = new Dictionary(); + internal Dictionary _statesInternal => _states; TableInfo _tablePriv; - TableInfo _table => _tablePriv ?? (_tablePriv = _fsql.CodeFirst.GetTableByEntity(_entityType)); + protected TableInfo _table => _tablePriv ?? (_tablePriv = _fsql.CodeFirst.GetTableByEntity(_entityType)); ColumnInfo[] _tableIdentitysPriv; - ColumnInfo[] _tableIdentitys => _tableIdentitysPriv ?? (_tableIdentitysPriv = _table.Primarys.Where(a => a.Attribute.IsIdentity).ToArray()); - Type _entityType = typeof(TEntity); + protected ColumnInfo[] _tableIdentitys => _tableIdentitysPriv ?? (_tableIdentitysPriv = _table.Primarys.Where(a => a.Attribute.IsIdentity).ToArray()); + protected Type _entityType = typeof(TEntity); + internal Type _entityTypeInternal => _entityType; public class EntityState { public EntityState(TEntity value, string key) { @@ -96,14 +100,7 @@ namespace FreeSql { if (string.IsNullOrEmpty(key)) return false; return _states.ContainsKey(key); } - bool CanAdd(TEntity[] data, bool isThrow) { - if (data == null) { - if (isThrow) throw new ArgumentNullException(nameof(data)); - return false; - } - foreach (var s in data) if (CanAdd(s, isThrow) == false) return false; - return true; - } + bool CanAdd(IEnumerable data, bool isThrow) { if (data == null) { if (isThrow) throw new ArgumentNullException(nameof(data)); @@ -146,14 +143,6 @@ namespace FreeSql { return true; } - bool CanUpdate(TEntity[] data, bool isThrow) { - if (data == null) { - if (isThrow) throw new ArgumentNullException(nameof(data)); - return false; - } - foreach (var s in data) if (CanUpdate(s, isThrow) == false) return false; - return true; - } bool CanUpdate(IEnumerable data, bool isThrow) { if (data == null) { if (isThrow) throw new ArgumentNullException(nameof(data)); @@ -183,14 +172,6 @@ namespace FreeSql { return true; } - bool CanRemove(TEntity[] data, bool isThrow) { - if (data == null) { - if (isThrow) throw new ArgumentNullException(nameof(data)); - return false; - } - foreach (var s in data) if (CanRemove(s, isThrow) == false) return false; - return true; - } bool CanRemove(IEnumerable data, bool isThrow) { if (data == null) { if (isThrow) throw new ArgumentNullException(nameof(data)); diff --git a/FreeSql.DbContext/DbSet/DbSetAsync.cs b/FreeSql.DbContext/DbSet/DbSetAsync.cs index 5f282702..ce271d4e 100644 --- a/FreeSql.DbContext/DbSet/DbSetAsync.cs +++ b/FreeSql.DbContext/DbSet/DbSetAsync.cs @@ -1 +1,239 @@ - \ No newline at end of file +using FreeSql.Extensions.EntityUtil; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FreeSql { + partial class DbSet { + + Task DbContextExecCommandAsync() { + if (IsNoneDbContext == false) { + _dicUpdateTimes.Clear(); + return _ctx.ExecCommandAsync(); + } + return Task.CompletedTask; + } + + async Task DbContextBetchAddAsync(EntityState[] adds) { + if (adds.Any() == false) return 0; + var affrows = await this.OrmInsert(adds.Select(a => a.Value)).ExecuteAffrowsAsync(); + return affrows; + } + + #region Add + async Task AddPrivAsync(TEntity data, bool isCheck) { + if (isCheck && CanAdd(data, true) == false) return; + if (_tableIdentitys.Length > 0) { + //有自增,马上执行 + switch (_fsql.Ado.DataType) { + case DataType.SqlServer: + case DataType.PostgreSQL: + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { + await DbContextExecCommandAsync(); + var idtval = await this.OrmInsert(data).ExecuteIdentityAsync(); + IncrAffrows(1); + _fsql.SetEntityIdentityValueWithPrimary(data, idtval); + var state = CreateEntityState(data); + _states.Add(state.Key, state); + } else { + await DbContextExecCommandAsync(); + var newval = (await this.OrmInsert(data).ExecuteInsertedAsync()).First(); + IncrAffrows(1); + _fsql.MapEntityValue(newval, data); + var state = CreateEntityState(newval); + _states.Add(state.Key, state); + } + return; + case DataType.MySql: + case DataType.Oracle: + case DataType.Sqlite: + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { + await DbContextExecCommandAsync(); + var idtval = await this.OrmInsert(data).ExecuteIdentityAsync(); + IncrAffrows(1); + _fsql.SetEntityIdentityValueWithPrimary(data, idtval); + var state = CreateEntityState(data); + _states.Add(state.Key, state); + } + return; + } + } else { + if (IsNoneDbContext) + IncrAffrows(await OrmInsert(data).ExecuteAffrowsAsync()); + else + EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(data)); + } + } + public Task AddAsync(TEntity data) => AddPrivAsync(data, true); + async public Task AddRangeAsync(IEnumerable data) { + if (CanAdd(data, true) == false) return; + if (data.ElementAtOrDefault(1) == default(TEntity)) { + await AddAsync(data.First()); + return; + } + if (_tableIdentitys.Length > 0) { + //有自增,马上执行 + switch (_fsql.Ado.DataType) { + case DataType.SqlServer: + case DataType.PostgreSQL: + await DbContextExecCommandAsync(); + var rets = await this.OrmInsert(data).ExecuteInsertedAsync(); + if (rets.Count != data.Count()) throw new Exception($"特别错误:批量添加失败,{_fsql.Ado.DataType} 的返回数据,与添加的数目不匹配"); + var idx = 0; + foreach (var s in data) + _fsql.MapEntityValue(rets[idx++], s); + IncrAffrows(rets.Count); + TrackToList(rets); + return; + case DataType.MySql: + case DataType.Oracle: + case DataType.Sqlite: + foreach (var s in data) + await AddPrivAsync(s, false); + return; + } + } else { + if (IsNoneDbContext) + IncrAffrows(await OrmInsert(data).ExecuteAffrowsAsync()); + else + //进入队列,等待 SaveChanges 时执行 + foreach (var s in data) + EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(s)); + } + } + #endregion + + #region UpdateAsync + Task DbContextBetchUpdateAsync(EntityState[] ups) => DbContextBetchUpdatePrivAsync(ups, false); + Task DbContextBetchUpdateNowAsync(EntityState[] ups) => DbContextBetchUpdatePrivAsync(ups, true); + async Task DbContextBetchUpdatePrivAsync(EntityState[] ups, bool isLiveUpdate) { + if (ups.Any() == false) return 0; + var uplst1 = ups[ups.Length - 1]; + var uplst2 = ups.Length > 1 ? ups[ups.Length - 2] : null; + + if (_states.TryGetValue(uplst1.Key, out var lstval1) == false) return -999; + var lstval2 = default(EntityState); + if (uplst2 != null && _states.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception($"特别错误:更新失败,数据未被跟踪:{_fsql.GetEntityString(uplst2.Value)}"); + + var cuig1 = _fsql.CompareEntityValueReturnColumns(uplst1.Value, lstval1.Value, true); + var cuig2 = uplst2 != null ? _fsql.CompareEntityValueReturnColumns(uplst2.Value, lstval2.Value, true) : null; + + List data = null; + string[] cuig = null; + if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0) { + //最后一个不保存 + data = ups.ToList(); + data.RemoveAt(ups.Length - 1); + cuig = cuig2; + } else if (isLiveUpdate) { + //立即保存 + data = ups.ToList(); + cuig = cuig1; + } + + if (data?.Count > 0) { + + if (cuig.Length == _table.Columns.Count) + return ups.Length == data.Count ? -998 : -997; + + var updateSource = data.Select(a => a.Value).ToArray(); + var update = this.OrmUpdate(null).SetSource(updateSource).IgnoreColumns(cuig); + + var affrows = await update.ExecuteAffrowsAsync(); + + foreach (var newval in data) { + if (_states.TryGetValue(newval.Key, out var tryold)) + _fsql.MapEntityValue(newval.Value, tryold.Value); + if (newval.OldValue != null) + _fsql.MapEntityValue(newval.Value, newval.OldValue); + } + return affrows; + } + + //等待下次对比再保存 + return 0; + } + + internal Task UpdateAffrowsAsync(TEntity data) => UpdateRangeAffrowsAsync(new[] { data }); + async internal Task UpdateRangeAffrowsAsync(IEnumerable data) { + if (CanUpdate(data, true) == false) return 0; + if (IsNoneDbContext) { + var dataarray = data.ToArray(); + var ups = new List(); + var totalAffrows = 0; + for (var a = 0; a < dataarray.Length + 1; a++) { + var item = a < dataarray.Length ? dataarray[a] : null; + if (item != null) { + var state = CreateEntityState(item); + state.Value = item; + ups.Add(state); + } + + var affrows = await DbContextBetchUpdatePrivAsync(ups.ToArray(), item == null); + if (affrows == -999) { //最后一个元素已被删除 + ups.RemoveAt(ups.Count - 1); + continue; + } + if (affrows == -998 || affrows == -997) { //没有执行更新 + var laststate = ups[ups.Count - 1]; + ups.Clear(); + if (affrows == -997) ups.Add(laststate); //保留最后一个 + } + if (affrows > 0) { + totalAffrows += affrows; + var islastNotUpdated = ups.Count != affrows; + var laststate = ups[ups.Count - 1]; + ups.Clear(); + if (islastNotUpdated) ups.Add(laststate); //保留最后一个 + } + } + IncrAffrows(totalAffrows); + return totalAffrows; + } + foreach (var item in data) { + if (_dicUpdateTimes.ContainsKey(item)) + await DbContextExecCommandAsync(); + _dicUpdateTimes.Add(item, 1); + + var state = CreateEntityState(item); + state.OldValue = item; + EnqueueToDbContext(DbContext.ExecCommandInfoType.Update, state); + } + return 0; + } + internal Task UpdateAsync(TEntity data) => UpdateAffrowsAsync(data); + internal Task UpdateRangeAsync(IEnumerable data) => UpdateRangeAffrowsAsync(data); + #endregion + + #region RemoveAsync + async Task DbContextBetchRemoveAsync(EntityState[] dels) { + if (dels.Any() == false) return 0; + var affrows = await this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrowsAsync(); + return Math.Max(dels.Length, affrows); + } + + internal Task RemoveAffrowsAsync(TEntity data) => RemoveRangeAffrowsAsync(new[] { data }); + async internal Task RemoveRangeAffrowsAsync(IEnumerable data) { + if (CanRemove(data, true) == false) return 0; + var dels = new List(); + foreach (var item in data) { + var state = CreateEntityState(item); + if (_states.ContainsKey(state.Key)) _states.Remove(state.Key); + _fsql.ClearEntityPrimaryValueWithIdentityAndGuid(item); + + if (IsNoneDbContext) dels.Add(state); + EnqueueToDbContext(DbContext.ExecCommandInfoType.Delete, state); + } + if (IsNoneDbContext) { + var affrows = await DbContextBetchRemoveAsync(dels.ToArray()); + IncrAffrows(affrows); + return affrows; + } + return 0; + } + internal Task RemoveAsync(TEntity data) => RemoveAffrowsAsync(data); + internal Task RemoveRangeAsync(IEnumerable data) => RemoveRangeAffrowsAsync(data); + #endregion + } +} diff --git a/FreeSql.DbContext/DbSet/DbSetSync.cs b/FreeSql.DbContext/DbSet/DbSetSync.cs index c423315d..f29df53e 100644 --- a/FreeSql.DbContext/DbSet/DbSetSync.cs +++ b/FreeSql.DbContext/DbSet/DbSetSync.cs @@ -6,6 +6,13 @@ using System.Linq; namespace FreeSql { partial class DbSet { + void DbContextExecCommand() { + if (IsNoneDbContext == false) { + _dicUpdateTimes.Clear(); + _ctx.ExecCommand(); + } + } + int DbContextBetchAdd(EntityState[] adds) { if (adds.Any() == false) return 0; var affrows = this.OrmInsert(adds.Select(a => a.Value)).ExecuteAffrows(); @@ -21,14 +28,14 @@ namespace FreeSql { case DataType.SqlServer: case DataType.PostgreSQL: if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { - ExecuteCommand(); + DbContextExecCommand(); var idtval = this.OrmInsert(data).ExecuteIdentity(); IncrAffrows(1); _fsql.SetEntityIdentityValueWithPrimary(data, idtval); var state = CreateEntityState(data); _states.Add(state.Key, state); } else { - ExecuteCommand(); + DbContextExecCommand(); var newval = this.OrmInsert(data).ExecuteInserted().First(); IncrAffrows(1); _fsql.MapEntityValue(newval, data); @@ -40,7 +47,7 @@ namespace FreeSql { case DataType.Oracle: case DataType.Sqlite: if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { - ExecuteCommand(); + DbContextExecCommand(); var idtval = this.OrmInsert(data).ExecuteIdentity(); IncrAffrows(1); _fsql.SetEntityIdentityValueWithPrimary(data, idtval); @@ -50,47 +57,13 @@ namespace FreeSql { return; } } else { - //进入队列,等待 SaveChanges 时执行 - EnqueueAction(DbContext.ExecCommandInfoType.Insert, this, typeof(EntityState), CreateEntityState(data)); + if (IsNoneDbContext) + IncrAffrows(OrmInsert(data).ExecuteAffrows()); + else + EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(data)); } } public void Add(TEntity data) => AddPriv(data, true); - #endregion - - #region AddRange - public void AddRange(TEntity[] data) { - if (CanAdd(data, true) == false) return; - if (data.Length == 1) { - Add(data.First()); - return; - } - if (_tableIdentitys.Length > 0) { - //有自增,马上执行 - switch (_fsql.Ado.DataType) { - case DataType.SqlServer: - case DataType.PostgreSQL: - ExecuteCommand(); - var rets = this.OrmInsert(data).ExecuteInserted(); - if (rets.Count != data.Count()) throw new Exception($"特别错误:批量添加失败,{_fsql.Ado.DataType} 的返回数据,与添加的数目不匹配"); - var idx = 0; - foreach (var s in data) - _fsql.MapEntityValue(rets[idx++], s); - IncrAffrows(rets.Count); - TrackToList(rets); - return; - case DataType.MySql: - case DataType.Oracle: - case DataType.Sqlite: - foreach (var s in data) - AddPriv(s, false); - return; - } - } else { - //进入队列,等待 SaveChanges 时执行 - foreach (var s in data) - EnqueueAction(DbContext.ExecCommandInfoType.Insert, this, typeof(EntityState), CreateEntityState(s)); - } - } public void AddRange(IEnumerable data) { if (CanAdd(data, true) == false) return; if (data.ElementAtOrDefault(1) == default(TEntity)) { @@ -102,11 +75,11 @@ namespace FreeSql { switch (_fsql.Ado.DataType) { case DataType.SqlServer: case DataType.PostgreSQL: - ExecuteCommand(); + DbContextExecCommand(); var rets = this.OrmInsert(data).ExecuteInserted(); if (rets.Count != data.Count()) throw new Exception($"特别错误:批量添加失败,{_fsql.Ado.DataType} 的返回数据,与添加的数目不匹配"); var idx = 0; - foreach(var s in data) + foreach (var s in data) _fsql.MapEntityValue(rets[idx++], s); IncrAffrows(rets.Count); TrackToList(rets); @@ -119,13 +92,17 @@ namespace FreeSql { return; } } else { - //进入队列,等待 SaveChanges 时执行 - foreach (var s in data) - EnqueueAction(DbContext.ExecCommandInfoType.Insert, this, typeof(EntityState), CreateEntityState(s)); + if (IsNoneDbContext) + IncrAffrows(OrmInsert(data).ExecuteAffrows()); + else + //进入队列,等待 SaveChanges 时执行 + foreach (var s in data) + EnqueueToDbContext(DbContext.ExecCommandInfoType.Insert, CreateEntityState(s)); } } #endregion + #region Update int DbContextBetchUpdate(EntityState[] ups) => DbContextBetchUpdatePriv(ups, false); int DbContextBetchUpdateNow(EntityState[] ups) => DbContextBetchUpdatePriv(ups, true); int DbContextBetchUpdatePriv(EntityState[] ups, bool isLiveUpdate) { @@ -176,47 +153,86 @@ namespace FreeSql { return 0; } - void UpdatePriv(TEntity data, bool isCheck) { - if (isCheck && CanUpdate(data, true) == false) return; - var state = CreateEntityState(data); - state.OldValue = data; - EnqueueAction(DbContext.ExecCommandInfoType.Update, this, typeof(EntityState), state); - } - public void Update(TEntity data) => UpdatePriv(data, true); - public void UpdateRange(TEntity[] data) { - if (CanUpdate(data, true) == false) return; - foreach (var item in data) - UpdatePriv(item, false); - } - public void UpdateRange(IEnumerable data) { - if (CanUpdate(data, true) == false) return; - foreach (var item in data) - UpdatePriv(item, false); - } + Dictionary _dicUpdateTimes = new Dictionary(); + internal int UpdateAffrows(TEntity data) => UpdateRangeAffrows(new[] { data }); + internal int UpdateRangeAffrows(IEnumerable data) { + if (CanUpdate(data, true) == false) return 0; + if (IsNoneDbContext) { + var dataarray = data.ToArray(); + var ups = new List(); + var totalAffrows = 0; + for (var a = 0; a < dataarray.Length + 1; a++) { + var item = a < dataarray.Length ? dataarray[a] : null; + if (item != null) { + var state = CreateEntityState(item); + state.Value = item; + ups.Add(state); + } + var affrows = DbContextBetchUpdatePriv(ups.ToArray(), item == null); + if (affrows == -999) { //最后一个元素已被删除 + ups.RemoveAt(ups.Count - 1); + continue; + } + if (affrows == -998 || affrows == -997) { //没有执行更新 + var laststate = ups[ups.Count - 1]; + ups.Clear(); + if (affrows == -997) ups.Add(laststate); //保留最后一个 + } + if (affrows > 0) { + totalAffrows += affrows; + var islastNotUpdated = ups.Count != affrows; + var laststate = ups[ups.Count - 1]; + ups.Clear(); + if (islastNotUpdated) ups.Add(laststate); //保留最后一个 + } + } + IncrAffrows(totalAffrows); + return totalAffrows; + } + foreach (var item in data) { + if (_dicUpdateTimes.ContainsKey(item)) + DbContextExecCommand(); + _dicUpdateTimes.Add(item, 1); + + var state = CreateEntityState(item); + state.OldValue = item; + EnqueueToDbContext(DbContext.ExecCommandInfoType.Update, state); + } + return 0; + } + public void Update(TEntity data) => UpdateAffrows(data); + public void UpdateRange(IEnumerable data) => UpdateRangeAffrows(data); + #endregion + + #region Remove int DbContextBetchRemove(EntityState[] dels) { if (dels.Any() == false) return 0; var affrows = this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrows(); return Math.Max(dels.Length, affrows); } - void RemovePriv(TEntity data, bool isCheck) { - if (isCheck && CanRemove(data, true) == false) return; - var state = CreateEntityState(data); - EnqueueAction(DbContext.ExecCommandInfoType.Delete, this, typeof(EntityState), state); - if (_states.ContainsKey(state.Key)) _states.Remove(state.Key); - _fsql.ClearEntityPrimaryValueWithIdentityAndGuid(data); - } - public void Remove(TEntity data) => RemovePriv(data, true); - public void RemoveRange(TEntity[] data) { - if (CanRemove(data, true) == false) return; - foreach (var item in data) - RemovePriv(item, false); - } - public void RemoveRange(IEnumerable data) { - if (CanRemove(data, true) == false) return; - foreach (var item in data) - RemovePriv(item, false); + internal int RemoveAffrows(TEntity data) => RemoveRangeAffrows(new[] { data }); + internal int RemoveRangeAffrows(IEnumerable data) { + if (CanRemove(data, true) == false) return 0; + var dels = new List(); + foreach (var item in data) { + var state = CreateEntityState(item); + if (_states.ContainsKey(state.Key)) _states.Remove(state.Key); + _fsql.ClearEntityPrimaryValueWithIdentityAndGuid(item); + + if (IsNoneDbContext) dels.Add(state); + EnqueueToDbContext(DbContext.ExecCommandInfoType.Delete, state); + } + if (IsNoneDbContext) { + var affrows = DbContextBetchRemove(dels.ToArray()); + IncrAffrows(affrows); + return affrows; + } + return 0; } + public void Remove(TEntity data) => RemoveAffrows(data); + public void RemoveRange(IEnumerable data) => RemoveRangeAffrows(data); + #endregion } } diff --git a/FreeSql.DbContext/Repository/DataFilter/DataFilter.cs b/FreeSql.DbContext/Repository/DataFilter/DataFilter.cs new file mode 100644 index 00000000..63c435b0 --- /dev/null +++ b/FreeSql.DbContext/Repository/DataFilter/DataFilter.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Text; +using System.Linq; + +namespace FreeSql { + public interface IDataFilter : IDisposable where TEntity : class { + + IDataFilter Apply(string filterName, Expression> filterAndValidateExp); + + /// + /// 开启过滤器,若使用 using 则使用完后,恢复为原有状态 + /// + /// 过滤器名称 + /// + IDisposable Enable(params string[] filterName); + /// + /// 开启所有过滤器,若使用 using 则使用完后,恢复为原有状态 + /// + /// + IDisposable EnableAll(); + + /// + /// 禁用过滤器,若使用 using 则使用完后,恢复为原有状态 + /// + /// + /// + IDisposable Disable(params string[] filterName); + /// + /// 禁用所有过滤器,若使用 using 则使用完后,恢复为原有状态 + /// + /// + IDisposable DisableAll(); + + bool IsEnabled(string filterName); + } + + internal class DataFilter : IDataFilter where TEntity : class { + + internal class FilterItem { + public Expression> Expression { get; set; } + Func _expressionDelegate; + public Func ExpressionDelegate => _expressionDelegate ?? (_expressionDelegate = Expression?.Compile()); + public bool IsEnabled { get; set; } + } + + internal ConcurrentDictionary _filters = new ConcurrentDictionary(StringComparer.CurrentCultureIgnoreCase); + public IDataFilter Apply(string filterName, Expression> filterAndValidateExp) { + + if (filterName == null) + throw new ArgumentNullException(nameof(filterName)); + if (filterAndValidateExp == null) return this; + + var filterItem = new FilterItem { Expression = filterAndValidateExp, IsEnabled = true }; + _filters.AddOrUpdate(filterName, filterItem, (k, v) => filterItem); + return this; + } + + public IDisposable Disable(params string[] filterName) { + if (filterName == null || filterName.Any() == false) return new UsingAny(() => { }); + + List restore = new List(); + foreach (var name in filterName) { + if (_filters.TryGetValue(name, out var tryfi)) { + if (tryfi.IsEnabled) { + restore.Add(name); + tryfi.IsEnabled = false; + } + } + } + return new UsingAny(() => this.Enable(restore.ToArray())); + } + public IDisposable DisableAll() { + List restore = new List(); + foreach (var val in _filters) { + if (val.Value.IsEnabled) { + restore.Add(val.Key); + val.Value.IsEnabled = false; + } + } + return new UsingAny(() => this.Enable(restore.ToArray())); + } + class UsingAny : IDisposable { + Action _ondis; + public UsingAny(Action ondis) { + _ondis = ondis; + } + public void Dispose() { + _ondis?.Invoke(); + } + } + + public IDisposable Enable(params string[] filterName) { + if (filterName == null || filterName.Any() == false) return new UsingAny(() => { }); + + List restore = new List(); + foreach (var name in filterName) { + if (_filters.TryGetValue(name, out var tryfi)) { + if (tryfi.IsEnabled == false) { + restore.Add(name); + tryfi.IsEnabled = true; + } + } + } + return new UsingAny(() => this.Disable(restore.ToArray())); + } + public IDisposable EnableAll() { + List restore = new List(); + foreach (var val in _filters) { + if (val.Value.IsEnabled == false) { + restore.Add(val.Key); + val.Value.IsEnabled = true; + } + } + return new UsingAny(() => this.Disable(restore.ToArray())); + } + + public bool IsEnabled(string filterName) { + if (filterName == null) return false; + return _filters.TryGetValue(filterName, out var tryfi) ? tryfi.IsEnabled : false; + } + + ~DataFilter() { + this.Dispose(); + } + public void Dispose() { + _filters.Clear(); + } + } + + public class FluentDataFilter : IDisposable { + + internal List<(Type type, string name, LambdaExpression exp)> _filters = new List<(Type type, string name, LambdaExpression exp)>(); + + public FluentDataFilter Apply(string filterName, Expression> filterAndValidateExp) where TEntity : class { + if (filterName == null) + throw new ArgumentNullException(nameof(filterName)); + if (filterAndValidateExp == null) return this; + + _filters.Add((typeof(TEntity), filterName, filterAndValidateExp)); + return this; + } + + ~FluentDataFilter() { + this.Dispose(); + } + public void Dispose() { + _filters.Clear(); + } + } +} diff --git a/FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs b/FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs new file mode 100644 index 00000000..77f41a5d --- /dev/null +++ b/FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace FreeSql { + + internal class DataFilterUtil { + + internal static Action _globalDataFilter; + + static ConcurrentDictionary _dicSetRepositoryDataFilterApplyDataFilterFunc = new ConcurrentDictionary(); + static ConcurrentDictionary> _dicSetRepositoryDataFilterConvertFilterNotExists = new ConcurrentDictionary>(); + internal static void SetRepositoryDataFilter(object repos, Action scopedDataFilter) { + if (scopedDataFilter != null) { + SetRepositoryDataFilter(repos, null); + } + if (scopedDataFilter == null) { + scopedDataFilter = _globalDataFilter; + } + if (scopedDataFilter == null) return; + using (var globalFilter = new FluentDataFilter()) { + scopedDataFilter(globalFilter); + + var type = repos.GetType(); + Type entityType = (repos as IRepository).EntityType; + if (entityType == null) throw new Exception("FreeSql.Repository 设置过滤器失败,原因是对象不属于 IRepository"); + + var notExists = _dicSetRepositoryDataFilterConvertFilterNotExists.GetOrAdd(type, t => new ConcurrentDictionary()); + var newFilter = new Dictionary(); + foreach (var gf in globalFilter._filters) { + if (notExists.ContainsKey(gf.name)) continue; + + LambdaExpression newExp = null; + var filterParameter1 = Expression.Parameter(entityType, gf.exp.Parameters[0].Name); + try { + newExp = Expression.Lambda( + typeof(Func<,>).MakeGenericType(entityType, typeof(bool)), + new ReplaceVisitor().Modify(gf.exp.Body, filterParameter1), + filterParameter1 + ); + } catch { + notExists.TryAdd(gf.name, true); //防止第二次错误 + continue; + } + newFilter.Add(gf.name, newExp); + } + if (newFilter.Any() == false) return; + + var del = _dicSetRepositoryDataFilterApplyDataFilterFunc.GetOrAdd(type, t => { + var reposParameter = Expression.Parameter(type); + var nameParameter = Expression.Parameter(typeof(string)); + var expressionParameter = Expression.Parameter( + typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(entityType, typeof(bool))) + ); + return Expression.Lambda( + Expression.Block( + Expression.Call(reposParameter, type.GetMethod("ApplyDataFilter", BindingFlags.Instance | BindingFlags.NonPublic), nameParameter, expressionParameter) + ), + new[] { + reposParameter, nameParameter, expressionParameter + } + ).Compile(); + }); + foreach (var nf in newFilter) { + del.DynamicInvoke(repos, nf.Key, nf.Value); + } + newFilter.Clear(); + } + } + } + + class ReplaceVisitor : ExpressionVisitor { + private ParameterExpression parameter; + + public Expression Modify(Expression expression, ParameterExpression parameter) { + this.parameter = parameter; + return Visit(expression); + } + + protected override Expression VisitMember(MemberExpression node) { + if (node.Expression?.NodeType == ExpressionType.Parameter) + return Expression.Property(parameter, node.Member.Name); + return base.VisitMember(node); + } + } +} diff --git a/FreeSql.DbContext/Repository/Extenssions/DependencyInjection.cs b/FreeSql.DbContext/Repository/Extenssions/DependencyInjection.cs new file mode 100644 index 00000000..e258b676 --- /dev/null +++ b/FreeSql.DbContext/Repository/Extenssions/DependencyInjection.cs @@ -0,0 +1,39 @@ +using FreeSql; +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; + +namespace FreeSql { + public static class FreeSqlRepositoryDependencyInjection { + + public static IServiceCollection AddFreeRepository(this IServiceCollection services, Action globalDataFilter = null, params Assembly[] assemblies) { + + DataFilterUtil._globalDataFilter = globalDataFilter; + + services.AddScoped(typeof(IReadOnlyRepository<>), typeof(GuidRepository<>)); + services.AddScoped(typeof(IBasicRepository<>), typeof(GuidRepository<>)); + services.AddScoped(typeof(BaseRepository<>), typeof(GuidRepository<>)); + services.AddScoped(typeof(GuidRepository<>)); + + services.AddScoped(typeof(IReadOnlyRepository<,>), typeof(DefaultRepository<,>)); + services.AddScoped(typeof(IBasicRepository<,>), typeof(DefaultRepository<,>)); + services.AddScoped(typeof(BaseRepository<,>), typeof(DefaultRepository<,>)); + services.AddScoped(typeof(DefaultRepository<,>)); + + if (assemblies?.Any() == true) { + foreach(var asse in assemblies) { + foreach (var repos in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(IRepository).IsAssignableFrom(a))) { + + services.AddScoped(repos); + } + } + } + + return services; + } + } +} \ No newline at end of file diff --git a/FreeSql.DbContext/Repository/Extenssions/FreeSqlRepositoryExtenssions.cs b/FreeSql.DbContext/Repository/Extenssions/FreeSqlRepositoryExtenssions.cs new file mode 100644 index 00000000..ef1aeca3 --- /dev/null +++ b/FreeSql.DbContext/Repository/Extenssions/FreeSqlRepositoryExtenssions.cs @@ -0,0 +1,58 @@ +using FreeSql; +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Text; +using System.Linq.Expressions; +using System.Linq; +using System.Data; + +public static class FreeSqlRepositoryExtenssions { + + /// + /// 返回默认仓库类 + /// + /// + /// + /// + /// 数据过滤 + 验证 + /// + public static DefaultRepository GetRepository(this IFreeSql that, Expression> filter = null) where TEntity : class { + return new DefaultRepository(that, filter); + } + + /// + /// 返回仓库类,适用 Insert 方法无须返回插入的数据 + /// + /// + /// + /// 数据过滤 + 验证 + /// 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository + /// + public static GuidRepository GetGuidRepository(this IFreeSql that, Expression> filter = null, Func asTable = null) where TEntity : class { + return new GuidRepository(that, filter, asTable); + } + + /// + /// 合并两个仓储的设置(过滤+分表),以便查询 + /// + /// + /// + /// + /// + /// + public static ISelect FromRepository(this ISelect that, BaseRepository repos) where TEntity : class where T2 : class { + var filters = (repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) that.Where(filter.Value.Expression); + return that.AsTable(repos.AsTableSelectInternal); + } + + /// + /// 创建基于仓储功能的工作单元,务必使用 using 包含使用 + /// + /// + /// + public static IRepositoryUnitOfWork CreateUnitOfWork(this IFreeSql that) { + return new RepositoryUnitOfWork(that); + } +} \ No newline at end of file diff --git a/FreeSql.DbContext/Repository/Repository/BaseRepository.cs b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs new file mode 100644 index 00000000..7ed0ebe0 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/BaseRepository.cs @@ -0,0 +1,99 @@ +using FreeSql.Extensions.EntityUtil; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace FreeSql { + public abstract class BaseRepository : IRepository + where TEntity : class { + + internal IFreeSql _fsql; + internal UnitOfWork _uow; + RepositoryDbSet _setPriv; + internal RepositoryDbSet _set => _setPriv ?? (_setPriv = new RepositoryDbSet(this)); + public IDataFilter DataFilter { get; } = new DataFilter(); + Func _asTableVal; + protected Func AsTable { + get => _asTableVal; + set { + _asTableVal = value; + AsTableSelect = value == null ? null : new Func((a, b) => a == EntityType ? value(b) : null); + } + } + internal Func AsTableInternal => AsTable; + protected Func AsTableSelect { get; private set; } + internal Func AsTableSelectInternal => AsTableSelect; + + protected BaseRepository(IFreeSql fsql, Expression> filter, Func asTable = null) { + _fsql = fsql; + DataFilterUtil.SetRepositoryDataFilter(this, null); + DataFilter.Apply("", filter); + AsTable = asTable; + } + + public Type EntityType => _set._entityTypeInternal; + public IUpdate UpdateDiy => _set.OrmUpdateInternal(null); + + + public ISelect Select => _set.OrmSelectInternal(null); + public ISelect Where(Expression> exp) => _set.OrmSelectInternal(null).Where(exp); + public ISelect WhereIf(bool condition, Expression> exp) => _set.OrmSelectInternal(null).WhereIf(condition, exp); + + public int Delete(Expression> predicate) => _set.OrmDeleteInternal(null).Where(predicate).ExecuteAffrows(); + public Task DeleteAsync(Expression> predicate) => _set.OrmDeleteInternal(null).Where(predicate).ExecuteAffrowsAsync(); + + public int Delete(TEntity entity) => _set.RemoveAffrows(entity); + public Task DeleteAsync(TEntity entity) => _set.RemoveAffrowsAsync(entity); + public int Delete(IEnumerable entitys) => _set.RemoveRangeAffrows(entitys); + public Task DeleteAsync(IEnumerable entitys) => _set.RemoveRangeAffrowsAsync(entitys); + + public virtual TEntity Insert(TEntity entity) { + _set.Add(entity); + return entity; + } + public virtual List Insert(IEnumerable entitys) { + _set.AddRange(entitys); + return entitys.ToList(); + } + async public virtual Task InsertAsync(TEntity entity) { + await _set.AddAsync(entity); + return entity; + } + async public virtual Task> InsertAsync(IEnumerable entitys) { + await _set.AddRangeAsync(entitys); + return entitys.ToList(); + } + + public int Update(TEntity entity) => _set.UpdateAffrows(entity); + public Task UpdateAsync(TEntity entity) => _set.UpdateAffrowsAsync(entity); + public int Update(IEnumerable entitys) => _set.UpdateRangeAffrows(entitys); + public Task UpdateAsync(IEnumerable entitys) => _set.UpdateRangeAffrowsAsync(entitys); + } + + public abstract class BaseRepository : BaseRepository, IRepository + where TEntity : class { + + public BaseRepository(IFreeSql fsql, Expression> filter, Func asTable = null) : base(fsql, filter, asTable) { + } + + public int Delete(TKey id) { + var stateKey = string.Concat(id); + if (_set._statesInternal.ContainsKey(stateKey)) _set._statesInternal.Remove(stateKey); + return _set.OrmDeleteInternal(id).ExecuteAffrows(); + } + public Task DeleteAsync(TKey id) { + var stateKey = string.Concat(id); + if (_set._statesInternal.ContainsKey(stateKey)) _set._statesInternal.Remove(stateKey); + return _set.OrmDeleteInternal(id).ExecuteAffrowsAsync(); + } + + public TEntity Find(TKey id) => _set.OrmSelectInternal(id).ToOne(); + public Task FindAsync(TKey id) => _set.OrmSelectInternal(id).ToOneAsync(); + + public TEntity Get(TKey id) => Find(id); + public Task GetAsync(TKey id) => FindAsync(id); + } +} diff --git a/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs b/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs new file mode 100644 index 00000000..3079b308 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/DefaultRepository.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql { + public class DefaultRepository : + BaseRepository + where TEntity : class { + + public DefaultRepository(IFreeSql fsql) : base(fsql, null, null) { + + } + + public DefaultRepository(IFreeSql fsql, Expression> filter) : base(fsql, filter, null) { + } + } +} diff --git a/FreeSql.DbContext/Repository/Repository/GuidRepository.cs b/FreeSql.DbContext/Repository/Repository/GuidRepository.cs new file mode 100644 index 00000000..80a9ad7e --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/GuidRepository.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace FreeSql { + public class GuidRepository : + BaseRepository + where TEntity : class { + + public GuidRepository(IFreeSql fsql) : this(fsql, null, null) { + + } + public GuidRepository(IFreeSql fsql, Expression> filter, Func asTable) : base(fsql, filter, asTable) { + } + } +} diff --git a/FreeSql.DbContext/Repository/Repository/IBasicRepository.cs b/FreeSql.DbContext/Repository/Repository/IBasicRepository.cs new file mode 100644 index 00000000..18b7db43 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/IBasicRepository.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace FreeSql { + public interface IBasicRepository : IReadOnlyRepository + where TEntity : class { + TEntity Insert(TEntity entity); + List Insert(IEnumerable entitys); + Task InsertAsync(TEntity entity); + Task> InsertAsync(IEnumerable entitys); + + int Update(TEntity entity); + int Update(IEnumerable entitys); + Task UpdateAsync(TEntity entity); + Task UpdateAsync(IEnumerable entitys); + + IUpdate UpdateDiy { get; } + + int Delete(TEntity entity); + int Delete(IEnumerable entitys); + Task DeleteAsync(TEntity entity); + Task DeleteAsync(IEnumerable entitys); + } + + public interface IBasicRepository : IBasicRepository, IReadOnlyRepository + where TEntity : class { + int Delete(TKey id); + + Task DeleteAsync(TKey id); + } +} + diff --git a/FreeSql.DbContext/Repository/Repository/IReadOnlyRepository.cs b/FreeSql.DbContext/Repository/Repository/IReadOnlyRepository.cs new file mode 100644 index 00000000..42284dc3 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/IReadOnlyRepository.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace FreeSql { + public interface IReadOnlyRepository : IRepository + where TEntity : class { + + IDataFilter DataFilter { get; } + + ISelect Select { get; } + + ISelect Where(Expression> exp); + ISelect WhereIf(bool condition, Expression> exp); + } + + public interface IReadOnlyRepository : IReadOnlyRepository + where TEntity : class { + TEntity Get(TKey id); + + Task GetAsync(TKey id); + + TEntity Find(TKey id); + + Task FindAsync(TKey id); + } +} diff --git a/FreeSql.DbContext/Repository/Repository/IRepository.cs b/FreeSql.DbContext/Repository/Repository/IRepository.cs new file mode 100644 index 00000000..d81c2080 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/IRepository.cs @@ -0,0 +1,21 @@ +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace FreeSql { + + public interface IRepository { + Type EntityType { get; } + } + + public interface IRepository : IReadOnlyRepository, IBasicRepository + where TEntity : class { + int Delete(Expression> predicate); + + Task DeleteAsync(Expression> predicate); + } + + public interface IRepository : IRepository, IReadOnlyRepository, IBasicRepository + where TEntity : class { + } +} \ No newline at end of file diff --git a/FreeSql.DbContext/Repository/Repository/RepositoryDbSet.cs b/FreeSql.DbContext/Repository/Repository/RepositoryDbSet.cs new file mode 100644 index 00000000..664e8e93 --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/RepositoryDbSet.cs @@ -0,0 +1,59 @@ +using FreeSql.Extensions.EntityUtil; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FreeSql { + internal class RepositoryDbSet : DbSet where TEntity : class { + + protected BaseRepository _repos; + public RepositoryDbSet(BaseRepository repos) { + _fsql = repos._fsql; + _uow = repos._uow; + _repos = repos; + } + + protected override ISelect OrmSelect(object dywhere) { + var select = base.OrmSelect(dywhere); + var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) select.Where(filter.Value.Expression); + return select.AsTable(_repos.AsTableSelectInternal); + } + internal ISelect OrmSelectInternal(object dywhere) => OrmSelect(dywhere); + protected override IUpdate OrmUpdate(IEnumerable entitys) { + var update = base.OrmUpdate(entitys); + var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) { + if (entitys != null) + foreach (var entity in entitys) + if (filter.Value.ExpressionDelegate?.Invoke(entity) == false) + throw new Exception($"FreeSql.Repository Update 失败,因为设置了过滤器 {filter.Key}: {filter.Value.Expression},更新的数据不符合 {_fsql.GetEntityString(entity)}"); + update.Where(filter.Value.Expression); + } + return update.AsTable(_repos.AsTableInternal); + } + internal IUpdate OrmUpdateInternal(IEnumerable entitys) => OrmUpdate(entitys); + protected override IDelete OrmDelete(object dywhere) { + var delete = base.OrmDelete(dywhere); + var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) delete.Where(filter.Value.Expression); + return delete.AsTable(_repos.AsTableInternal); + } + internal IDelete OrmDeleteInternal(object dywhere) => OrmDelete(dywhere); + protected override IInsert OrmInsert(TEntity entity) => OrmInsert(new[] { entity }); + protected override IInsert OrmInsert(IEnumerable entitys) { + var insert = base.OrmInsert(entitys); + var filters = (_repos.DataFilter as DataFilter)._filters.Where(a => a.Value.IsEnabled == true); + foreach (var filter in filters) { + if (entitys != null) + foreach (var entity in entitys) + if (filter.Value.ExpressionDelegate?.Invoke(entity) == false) + throw new Exception($"FreeSql.Repository Insert 失败,因为设置了过滤器 {filter.Key}: {filter.Value.Expression},插入的数据不符合 {_fsql.GetEntityString(entity)}"); + } + return insert.AsTable(_repos.AsTableInternal); + } + internal IInsert OrmInsertInternal(TEntity entity) => OrmInsert(entity); + internal IInsert OrmInsertInternal(IEnumerable entitys) => OrmInsert(entitys); + } +} diff --git a/FreeSql.DbContext/Repository/Repository/RepositoryUnitOfWork.cs b/FreeSql.DbContext/Repository/Repository/RepositoryUnitOfWork.cs new file mode 100644 index 00000000..4526449d --- /dev/null +++ b/FreeSql.DbContext/Repository/Repository/RepositoryUnitOfWork.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql { + class RepositoryUnitOfWork : UnitOfWork, IRepositoryUnitOfWork { + + public RepositoryUnitOfWork(IFreeSql fsql) : base(fsql) { + } + + public GuidRepository GetGuidRepository(Expression> filter = null, Func asTable = null) where TEntity : class { + var repos = new GuidRepository(_fsql, filter, asTable); + repos._uow = this; + return repos; + } + + public DefaultRepository GetRepository(Expression> filter = null) where TEntity : class { + var repos = new DefaultRepository(_fsql, filter); + repos._uow = this; + return repos; + } + } +} diff --git a/FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs b/FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs new file mode 100644 index 00000000..48782be2 --- /dev/null +++ b/FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql { + public interface IUnitOfWork : IDisposable { + + void Commit(); + + void Rollback(); + } + + public interface IRepositoryUnitOfWork : IUnitOfWork { + + /// + /// 在工作单元内创建默认仓库类,工作单元下的仓储操作具有事务特点 + /// + /// + /// + /// 数据过滤 + 验证 + /// + DefaultRepository GetRepository(Expression> filter = null) where TEntity : class; + + /// + /// 在工作单元内创建仓库类,适用 Insert 方法无须返回插入的数据,工作单元下的仓储操作具有事务特点 + /// + /// + /// 数据过滤 + 验证 + /// 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository + /// + GuidRepository GetGuidRepository(Expression> filter = null, Func asTable = null) where TEntity : class; + } +} diff --git a/FreeSql.DbContext/UnitOfWork/UnitOfWork.cs b/FreeSql.DbContext/UnitOfWork/UnitOfWork.cs new file mode 100644 index 00000000..4957400f --- /dev/null +++ b/FreeSql.DbContext/UnitOfWork/UnitOfWork.cs @@ -0,0 +1,73 @@ +using SafeObjectPool; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql { + class UnitOfWork : IUnitOfWork { + + protected IFreeSql _fsql; + protected Object _conn; + protected DbTransaction _tran; + + public UnitOfWork(IFreeSql fsql) { + _fsql = fsql; + } + + void ReturnObject() { + _fsql.Ado.MasterPool.Return(_conn); + _tran = null; + _conn = null; + } + internal DbTransaction GetOrBeginTransaction(bool isCreate = true) { + + if (_tran != null) return _tran; + if (isCreate == false) return null; + if (_conn != null) _fsql.Ado.MasterPool.Return(_conn); + + _conn = _fsql.Ado.MasterPool.Get(); + try { + _tran = _conn.Value.BeginTransaction(); + } catch { + ReturnObject(); + throw; + } + return _tran; + } + + public void Commit() { + if (_tran != null) { + try { + _tran.Commit(); + } finally { + ReturnObject(); + } + } + } + public void Rollback() { + if (_tran != null) { + try { + _tran.Rollback(); + } finally { + ReturnObject(); + } + } + } + ~UnitOfWork() { + this.Dispose(); + } + bool _isdisposed = false; + public void Dispose() { + if (_isdisposed) return; + try { + this.Rollback(); + } finally { + _isdisposed = true; + GC.SuppressFinalize(this); + } + } + + } +} diff --git a/FreeSql.Tests/FreeSql.Tests.csproj b/FreeSql.Tests/FreeSql.Tests.csproj index 64c7754f..ad94b75b 100644 --- a/FreeSql.Tests/FreeSql.Tests.csproj +++ b/FreeSql.Tests/FreeSql.Tests.csproj @@ -14,7 +14,6 @@ -