From 7f3aa84ffe61a33376cf6a55bacc57f83b5081ac Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Fri, 22 Mar 2019 00:26:08 +0800 Subject: [PATCH] ## v0.3.22 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化 导航属性 ManyToOne 名称查找规则; - 增加 IFreeSql.Aop 属性,未来所有拦截方法都在这里,第一期支持如下: * 监控 ToList 返回的的数据,用于拦截重新装饰; * 监视 Where,包括 select/update/delete,返回值 true 时可使上层不被执行; * 可自定义解析表达式; - 增加 ISelect.TractToList,用于单次跟踪或审核实体; - 优化 FreeSql.DbContext SaveChanges; --- .../Controllers/ValuesController.cs | 4 +- .../dbcontext_01/DbContexts/SongContext.cs | 2 +- FreeSql.DbContext/DbContext.cs | 99 +++--- FreeSql.DbContext/DbContextAsync.cs | 88 ++---- FreeSql.DbContext/DbSet.cs | 293 ++++++------------ FreeSql.DbContext/DbSetAsync.cs | 102 +++--- FreeSql.DbContext/EntityUtil.cs | 278 +++++++++++++++++ FreeSql.DbContext/FreeSql.DbContext.csproj | 2 +- FreeSql.Repository/FreeSql.Repository.csproj | 2 +- .../PostgreSQLAdo/PostgreSQLAdoTest.cs | 8 +- FreeSql/FreeSql.csproj | 2 +- FreeSql/Interface/Curd/ISelect/ISelect0.cs | 7 + FreeSql/Interface/IAop.cs | 78 ++++- FreeSql/Interface/IFreeSql.cs | 4 + FreeSql/Internal/CommonExpression.cs | 7 +- .../Internal/CommonProvider/AopProvider.cs | 13 + .../Internal/CommonProvider/DeleteProvider.cs | 4 + .../SelectProvider/Select0Provider.cs | 23 ++ .../SelectProvider/SelectGroupingProvider.cs | 2 - .../Internal/CommonProvider/UpdateProvider.cs | 4 + FreeSql/Internal/UtilsExpressionTree.cs | 5 +- FreeSql/MySql/MySqlProvider.cs | 2 + FreeSql/Oracle/OracleProvider.cs | 2 + FreeSql/PostgreSQL/PostgreSQLProvider.cs | 2 + FreeSql/SqlServer/SqlServerProvider.cs | 2 + FreeSql/Sqlite/SqliteProvider.cs | 2 + readme.md | 206 ++++++------ 27 files changed, 720 insertions(+), 523 deletions(-) create mode 100644 FreeSql.DbContext/EntityUtil.cs create mode 100644 FreeSql/Internal/CommonProvider/AopProvider.cs diff --git a/Examples/dbcontext_01/Controllers/ValuesController.cs b/Examples/dbcontext_01/Controllers/ValuesController.cs index 8e58cfbf..4762103f 100644 --- a/Examples/dbcontext_01/Controllers/ValuesController.cs +++ b/Examples/dbcontext_01/Controllers/ValuesController.cs @@ -28,6 +28,8 @@ namespace dbcontext_01.Controllers try { using (var ctx = new SongContext()) { + ctx.Songs.Select.Where(a => a.Id > 10).ToList(); + var song = new Song { }; ctx.Songs.Add(song); id = song.Id; @@ -90,7 +92,7 @@ namespace dbcontext_01.Controllers var item22 = await _orm.Select().Where(a => a.Id == id).FirstAsync(); var item33 = await _orm.Select().Where(a => a.Id > id).ToListAsync(); - return item22.Title; + return item22.Id.ToString(); } // GET api/values/5 diff --git a/Examples/dbcontext_01/DbContexts/SongContext.cs b/Examples/dbcontext_01/DbContexts/SongContext.cs index d1a01cd3..2a04d969 100644 --- a/Examples/dbcontext_01/DbContexts/SongContext.cs +++ b/Examples/dbcontext_01/DbContexts/SongContext.cs @@ -8,7 +8,7 @@ namespace dbcontext_01 { public class SongContext : DbContext { public DbSet Songs { get; set; } - public DbSet Tags { get; set; } + public DbSet Tags { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder builder) { builder.UseFreeSql(dbcontext_01.Startup.Fsql); diff --git a/FreeSql.DbContext/DbContext.cs b/FreeSql.DbContext/DbContext.cs index 137e644a..61b84324 100644 --- a/FreeSql.DbContext/DbContext.cs +++ b/FreeSql.DbContext/DbContext.cs @@ -32,8 +32,10 @@ namespace FreeSql { var set = this.Set(prop.PropertyType.GenericTypeArguments[0]); prop.SetValue(this, set); - AllSets.Add(prop, set); + AllSets.Add(prop.Name, set); } + + //_fsql.Aop.ToList += AopToList; } protected virtual void OnConfiguring(DbContextOptionsBuilder builder) { @@ -43,7 +45,7 @@ namespace FreeSql { public DbSet Set() where TEntity : class => this.Set(typeof(TEntity)) as DbSet; public object Set(Type entityType) => Activator.CreateInstance(typeof(BaseDbSet<>).MakeGenericType(entityType), this); - protected Dictionary AllSets => new Dictionary(); + protected Dictionary AllSets { get; } = new Dictionary(); public long SaveChanges() { ExecCommand(); @@ -53,91 +55,61 @@ namespace FreeSql { internal class ExecCommandInfo { public ExecCommandInfoType actionType { get; set; } - public Type entityType { get; set; } public object dbSet { get; set; } + public Type stateType { get; set; } public object state { get; set; } } internal enum ExecCommandInfoType { Insert, Update, Delete } Queue _actions = new Queue(); internal long _affrows = 0; - internal void EnqueueAction(ExecCommandInfoType actionType, Type entityType, object dbSet, object state) { - _actions.Enqueue(new ExecCommandInfo { actionType = actionType, entityType = entityType, dbSet = dbSet, state = state }); + internal void EnqueueAction(ExecCommandInfoType actionType, object dbSet, Type stateType, object state) { + _actions.Enqueue(new ExecCommandInfo { actionType = actionType, dbSet = dbSet, stateType = stateType, state = state }); } - static ConcurrentDictionary> _dicExecCommandInsert = new ConcurrentDictionary>(); - static ConcurrentDictionary> _dicExecCommandDelete = new ConcurrentDictionary>(); - static ConcurrentDictionary> _dicExecCommandUpdate = new ConcurrentDictionary>(); + static Dictionary>> _dicExecCommandDbContextBetch = new Dictionary>>(); internal void ExecCommand() { ExecCommandInfo oldinfo = null; var states = new List(); - Action funcInsert = () => { - var insertFunc = _dicExecCommandInsert.GetOrAdd(oldinfo.entityType, t => { - var arrType = t.MakeArrayType(); - var dbsetType = typeof(DbSet<>).MakeGenericType(t); - var dbsetTypeInsert = dbsetType.GetMethod("OrmInsert", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null); - var insertBuilder = typeof(IInsert<>).MakeGenericType(t); - var insertExecuteAffrows = insertBuilder.GetMethod("ExecuteAffrows", new Type[0]); + Func dbContextBetch = methodName => { + if (_dicExecCommandDbContextBetch.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(int)); var parm1DbSet = Expression.Parameter(typeof(object)); var parm2Vals = Expression.Parameter(typeof(object[])); var var1Vals = Expression.Variable(arrType); - return Expression.Lambda>(Expression.Block( + 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.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeInsert, var1Vals), - insertExecuteAffrows - ) - ), + Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals)), Expression.Label(returnTarget, Expression.Default(typeof(int))) ), new[] { parm1DbSet, parm2Vals }).Compile(); - }); - _affrows += insertFunc(oldinfo.dbSet, states.ToArray()); - states.Clear(); + trydic.Add(methodName, tryfunc); + } + return tryfunc(oldinfo.dbSet, states.ToArray()); }; Action funcDelete = () => { - var deleteFunc = _dicExecCommandDelete.GetOrAdd(oldinfo.entityType, t => { - var arrType = t.MakeArrayType(); - var dbsetType = typeof(DbSet<>).MakeGenericType(t); - var dbsetTypeDelete = dbsetType.GetMethod("DbContextBetchRemove", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null); - - var returnTarget = Expression.Label(typeof(int)); - var parm1DbSet = Expression.Parameter(typeof(object)); - var parm2Vals = Expression.Parameter(typeof(object[])); - var var1Vals = Expression.Variable(arrType); - return 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), dbsetTypeDelete, var1Vals)), - Expression.Label(returnTarget, Expression.Default(typeof(int))) - ), new[] { parm1DbSet, parm2Vals }).Compile(); - }); - _affrows += deleteFunc(oldinfo.dbSet, states.ToArray()); + _affrows += dbContextBetch("DbContextBetchRemove"); + states.Clear(); + }; + Action funcInsert = () => { + _affrows += dbContextBetch("DbContextBetchAdd"); states.Clear(); }; Action funcUpdate = isLiveUpdate => { - var updateFunc = _dicExecCommandUpdate.GetOrAdd(oldinfo.entityType, t => { - var arrType = t.MakeArrayType(); - var dbsetType = typeof(DbSet<>).MakeGenericType(t); - var dbsetTypeUpdate = dbsetType.GetMethod("DbContextBetchUpdate", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType, typeof(bool) }, null); - - var returnTarget = Expression.Label(typeof(int)); - var parm1DbSet = Expression.Parameter(typeof(object)); - var parm2Vals = Expression.Parameter(typeof(object[])); - var parm3IsLiveUpdate = Expression.Parameter(typeof(bool)); - var var1Vals = Expression.Variable(arrType); - return 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), dbsetTypeUpdate, var1Vals, parm3IsLiveUpdate)), - Expression.Label(returnTarget, Expression.Default(typeof(int))) - ), new[] { parm1DbSet, parm2Vals, parm3IsLiveUpdate }).Compile(); - }); - var affrows = updateFunc(oldinfo.dbSet, states.ToArray(), isLiveUpdate); + var affrows = 0; + if (isLiveUpdate) affrows = dbContextBetch("DbContextBetchUpdateNow"); + else affrows = dbContextBetch("DbContextBetchUpdate"); + if (affrows == -999) { //最后一个元素已被删除 + states.RemoveAt(states.Count - 1); + return; + } if (affrows > 0) { _affrows += affrows; var islastNotUpdated = states.Count != affrows; @@ -146,16 +118,16 @@ namespace FreeSql { } }; - while(_actions.Any() || states.Any()) { + 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.entityType != info.entityType) { + info != null && oldinfo.stateType != info.stateType) { - if (info != null && oldinfo.actionType == info.actionType && oldinfo.entityType == info.entityType) { + if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType) { //最后一个,合起来发送 states.Add(info.state); info = null; @@ -224,6 +196,7 @@ namespace FreeSql { } } public void Dispose() { + //_fsql.Aop.ToList -= AopToList; this.Rollback(); } } diff --git a/FreeSql.DbContext/DbContextAsync.cs b/FreeSql.DbContext/DbContextAsync.cs index 0aa52a80..9394eaaa 100644 --- a/FreeSql.DbContext/DbContextAsync.cs +++ b/FreeSql.DbContext/DbContextAsync.cs @@ -17,79 +17,49 @@ namespace FreeSql { return _affrows; } - static ConcurrentDictionary>> _dicExecCommandAsyncInsert = new ConcurrentDictionary>>(); - static ConcurrentDictionary>> _dicExecCommandAsyncDelete = new ConcurrentDictionary>>(); - static ConcurrentDictionary>> _dicExecCommandAsyncUpdate = new ConcurrentDictionary>>(); + static Dictionary>>> _dicExecCommandAsyncDbContextBetch = new Dictionary>>>(); async internal Task ExecCommandAsync() { ExecCommandInfo oldinfo = null; var states = new List(); - Func funcInsert = async () => { - var insertFunc = _dicExecCommandAsyncInsert.GetOrAdd(oldinfo.entityType, t => { - var arrType = t.MakeArrayType(); - var dbsetType = typeof(DbSet<>).MakeGenericType(t); - var dbsetTypeInsert = dbsetType.GetMethod("OrmInsert", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null); - var insertBuilder = typeof(IInsert<>).MakeGenericType(t); - var insertExecuteAffrows = insertBuilder.GetMethod("ExecuteAffrowsAsync", new Type[0]); + Func> dbContextBetch = methodName => { + if (_dicExecCommandAsyncDbContextBetch.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); - return Expression.Lambda>>(Expression.Block( + 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.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeInsert, var1Vals), - insertExecuteAffrows - ) - ), + Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals)), Expression.Label(returnTarget, Expression.Default(typeof(Task))) ), new[] { parm1DbSet, parm2Vals }).Compile(); - }); - _affrows += await insertFunc(oldinfo.dbSet, states.ToArray()); - states.Clear(); + trydic.Add(methodName, tryfunc); + } + return tryfunc(oldinfo.dbSet, states.ToArray()); }; Func funcDelete = async () => { - var deleteFunc = _dicExecCommandAsyncDelete.GetOrAdd(oldinfo.entityType, t => { - var arrType = t.MakeArrayType(); - var dbsetType = typeof(DbSet<>).MakeGenericType(t); - var dbsetTypeDelete = dbsetType.GetMethod("DbContextBetchRemoveAsync", 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); - return 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), dbsetTypeDelete, var1Vals)), - Expression.Label(returnTarget, Expression.Default(typeof(Task))) - ), new[] { parm1DbSet, parm2Vals }).Compile(); - }); - _affrows += await deleteFunc(oldinfo.dbSet, states.ToArray()); + _affrows += await dbContextBetch("DbContextBetchRemoveAsync"); states.Clear(); }; - Func funcUpdate = async (isLiveUpdate) => { - var updateFunc = _dicExecCommandAsyncUpdate.GetOrAdd(oldinfo.entityType, t => { - var arrType = t.MakeArrayType(); - var dbsetType = typeof(DbSet<>).MakeGenericType(t); - var dbsetTypeUpdate = dbsetType.GetMethod("DbContextBetchUpdateAsync", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType, typeof(bool) }, null); - - var returnTarget = Expression.Label(typeof(Task)); - var parm1DbSet = Expression.Parameter(typeof(object)); - var parm2Vals = Expression.Parameter(typeof(object[])); - var parm3IsLiveUpdate = Expression.Parameter(typeof(bool)); - var var1Vals = Expression.Variable(arrType); - return 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), dbsetTypeUpdate, var1Vals, parm3IsLiveUpdate)), - Expression.Label(returnTarget, Expression.Default(typeof(Task))) - ), new[] { parm1DbSet, parm2Vals, parm3IsLiveUpdate }).Compile(); - }); - var affrows = await updateFunc(oldinfo.dbSet, states.ToArray(), isLiveUpdate); + 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 > 0) { _affrows += affrows; var islastNotUpdated = states.Count != affrows; @@ -98,16 +68,16 @@ namespace FreeSql { } }; - while(_actions.Any() || states.Any()) { + 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.entityType != info.entityType) { + info != null && oldinfo.stateType != info.stateType) { - if (info != null && oldinfo.actionType == info.actionType && oldinfo.entityType == info.entityType) { + if (info != null && oldinfo.actionType == info.actionType && oldinfo.stateType == info.stateType) { //最后一个,合起来发送 states.Add(info.state); info = null; diff --git a/FreeSql.DbContext/DbSet.cs b/FreeSql.DbContext/DbSet.cs index 0123d9b0..e03f1e03 100644 --- a/FreeSql.DbContext/DbSet.cs +++ b/FreeSql.DbContext/DbSet.cs @@ -10,172 +10,92 @@ using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using System.Reflection; +using FreeSql.Extensions; namespace FreeSql { public abstract partial class DbSet where TEntity : class { protected DbContext _ctx; + IFreeSql _fsql => _ctx._fsql; - protected ISelect OrmSelect(object dywhere) => _ctx._fsql.Select(dywhere).WithTransaction(_ctx.GetOrBeginTransaction(false)); + protected ISelect OrmSelect(object dywhere) => _fsql.Select(dywhere).WithTransaction(_ctx.GetOrBeginTransaction(false)).TrackToList(TrackToList); - protected IInsert OrmInsert() => _ctx._fsql.Insert().WithTransaction(_ctx.GetOrBeginTransaction()); - protected IInsert OrmInsert(TEntity source) => _ctx._fsql.Insert(source).WithTransaction(_ctx.GetOrBeginTransaction()); - protected IInsert OrmInsert(TEntity[] source) => _ctx._fsql.Insert(source).WithTransaction(_ctx.GetOrBeginTransaction()); - protected IInsert OrmInsert(IEnumerable source) => _ctx._fsql.Insert(source).WithTransaction(_ctx.GetOrBeginTransaction()); + protected IInsert OrmInsert() => _fsql.Insert().WithTransaction(_ctx.GetOrBeginTransaction()); + protected IInsert OrmInsert(TEntity source) => _fsql.Insert(source).WithTransaction(_ctx.GetOrBeginTransaction()); + protected IInsert OrmInsert(TEntity[] source) => _fsql.Insert(source).WithTransaction(_ctx.GetOrBeginTransaction()); + protected IInsert OrmInsert(IEnumerable source) => _fsql.Insert(source).WithTransaction(_ctx.GetOrBeginTransaction()); - protected IUpdate OrmUpdate(object dywhere) => _ctx._fsql.Update(dywhere).WithTransaction(_ctx.GetOrBeginTransaction()); - protected IDelete OrmDelete(object dywhere) => _ctx._fsql.Delete(dywhere).WithTransaction(_ctx.GetOrBeginTransaction()); + protected IUpdate OrmUpdate(object dywhere) => _fsql.Update(dywhere).WithTransaction(_ctx.GetOrBeginTransaction()); + protected IDelete OrmDelete(object dywhere) => _fsql.Delete(dywhere).WithTransaction(_ctx.GetOrBeginTransaction()); public ISelect Select => this.OrmSelect(null); public ISelect Where(Expression> exp) => this.OrmSelect(null).Where(exp); public ISelect WhereIf(bool condition, Expression> exp) => this.OrmSelect(null).WhereIf(condition, exp); - protected Dictionary _vals = new Dictionary(); + protected Dictionary _vals = new Dictionary(); TableInfo _tablePriv; - protected TableInfo _table => _tablePriv ?? (_tablePriv = _ctx._orm.CodeFirst.GetTableByEntity(_entityType)); + protected TableInfo _table => _tablePriv ?? (_tablePriv = _fsql.CodeFirst.GetTableByEntity(_entityType)); + ColumnInfo[] _tableIdentitysPriv; + protected ColumnInfo[] _tableIdentitys => _tableIdentitysPriv ?? (_tableIdentitysPriv = _table.Primarys.Where(a => a.Attribute.IsIdentity).ToArray()); protected Type _entityType = typeof(TEntity); - static ConcurrentDictionary> _dicGetEntityKeyString = new ConcurrentDictionary>(); - static MethodInfo MethodStringBuilderAppend = typeof(StringBuilder).GetMethod("Append", new Type[] { typeof(object) }); - static MethodInfo MethodStringBuilderToString = typeof(StringBuilder).GetMethod("ToString", new Type[0]); - static PropertyInfo MethodStringBuilderLength = typeof(StringBuilder).GetProperty("Length"); - static MethodInfo MethodStringConcat = typeof(string).GetMethod("Concat", new Type[]{ typeof(object) }); - string GetEntityKeyString(TEntity item) { - var func = _dicGetEntityKeyString.GetOrAdd(_entityType, t => { - var pks = _table.Primarys; - var returnTarget = Expression.Label(typeof(string)); - var parm1 = Expression.Parameter(_entityType); - var var1Sb = Expression.Variable(typeof(StringBuilder)); - var var3IsNull = Expression.Variable(typeof(bool)); - var exps = new List(); - - exps.AddRange(new Expression[] { - Expression.Assign(var1Sb, Expression.New(typeof(StringBuilder))), - Expression.Assign(var3IsNull, Expression.Constant(false)) - }); - for (var a = 0; a < pks.Length; a++) { - exps.Add( - Expression.IfThen( - Expression.Equal(var3IsNull, Expression.Constant(false)), - Expression.IfThenElse( - Expression.Equal(Expression.MakeMemberAccess(parm1, _table.Properties[pks[a].CsName]), Expression.Default(pks[a].CsType)), - Expression.Assign(var3IsNull, Expression.Constant(true)), - Expression.Block( - new Expression[]{ - a > 0 ? Expression.Call(var1Sb, MethodStringBuilderAppend, Expression.Constant("*|_,,_|*" )) : null, - Expression.Call(var1Sb, MethodStringBuilderAppend, - Expression.Convert(Expression.MakeMemberAccess(parm1, _table.Properties[pks[a].CsName]), typeof(object)) - ) - }.Where(c => c != null).ToArray() - ) - ) - ) - ); - } - exps.Add( - Expression.IfThen( - Expression.Equal(var3IsNull, Expression.Constant(false)), - Expression.Return(returnTarget, Expression.Call(var1Sb, MethodStringBuilderToString)) - ) - ); - exps.Add(Expression.Label(returnTarget, Expression.Default(typeof(string)))); - return Expression.Lambda>(Expression.Block(new[] { var1Sb, var3IsNull }, exps), new[] { parm1 }).Compile(); - }); - return func(item); + public class EntityState { + public TEntity Value { get; set; } + public string Key { get; set; } + public DateTime Time { get; set; } } - static ConcurrentDictionary> _dicCopyNewValueToEntity = new ConcurrentDictionary>(); - void CopyNewValueToEntity(TEntity old, TEntity newvalue) { - var func = _dicCopyNewValueToEntity.GetOrAdd(_entityType, t => { - var parm1 = Expression.Parameter(_entityType); - var parm2 = Expression.Parameter(_entityType); - var exps = new List(); - foreach (var prop in _table.Properties.Values) { - if (_table.ColumnsByCs.ContainsKey(prop.Name)) { - exps.Add( - Expression.Assign( - Expression.MakeMemberAccess(parm1, prop), - Expression.MakeMemberAccess(parm2, prop) - ) - ); - } else { - exps.Add( - Expression.Assign( - Expression.MakeMemberAccess(parm1, prop), - Expression.Default(prop.PropertyType) - ) - ); - } - } - return Expression.Lambda>(Expression.Block(exps), new[] { parm1, parm2 }).Compile(); - }); - func(old, newvalue); + int DbContextBetcAdd(EntityState[] dels) { + if (dels.Any() == false) return 0; + var affrows = this.OrmInsert(dels.Select(a => a.Value)).ExecuteAffrows(); + return affrows; } - - static ConcurrentDictionary> _dicSetEntityIdentityValue = new ConcurrentDictionary>(); - void SetEntityIdentityValue(TEntity old, long idtval) { - var func = _dicSetEntityIdentityValue.GetOrAdd(_entityType, t => { - var parm1 = Expression.Parameter(_entityType); - var parm2 = Expression.Parameter(typeof(long)); - var exps = new List(); - exps.Add( - Expression.Assign( - Expression.MakeMemberAccess(parm1, _table.Properties[_table.Primarys[0].CsName]), - Expression.Convert(FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(_table.Primarys[0].CsType, Expression.Convert(parm2, typeof(object))), _table.Primarys[0].CsType) - ) - ); - return Expression.Lambda>(Expression.Block(exps), new[] { parm1, parm2 }).Compile(); - }); - func(old, idtval); - } - public void Add(TEntity source) { if (source == null) throw new ArgumentNullException(nameof(source)); - var key = GetEntityKeyString(source); - TEntity newval = null; + var key = _fsql.GetEntityKeyString(source); + EntityState state = new EntityState(); if (string.IsNullOrEmpty(key)) { - var ids = _table.Primarys.Where(a => a.Attribute.IsIdentity).ToArray(); - - switch(_ctx._orm.Ado.DataType) { + switch(_fsql.Ado.DataType) { case DataType.SqlServer: case DataType.PostgreSQL: - if (ids.Length == 1 && _table.Primarys.Length == 1) { + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { _ctx.ExecCommand(); var idtval = this.OrmInsert(source).ExecuteIdentity(); _ctx._affrows++; - SetEntityIdentityValue(source, idtval); + _fsql.SetEntityIdentityValue(source, idtval); } else { _ctx.ExecCommand(); - newval = this.OrmInsert(source).ExecuteInserted().First(); + state.Value = this.OrmInsert(source).ExecuteInserted().First(); _ctx._affrows++; - CopyNewValueToEntity(source, newval); + _fsql.CopyEntityValue(source, state.Value); } break; case DataType.MySql: case DataType.Oracle: case DataType.Sqlite: - if (ids.Length == 1 && _table.Primarys.Length == 1) { + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { _ctx.ExecCommand(); var idtval = this.OrmInsert(source).ExecuteIdentity(); _ctx._affrows++; - SetEntityIdentityValue(source, idtval); + _fsql.SetEntityIdentityValue(source, idtval); } else { - throw new Exception("DbSet.Add 失败,由于实体没有主键值,或者没有配置自增,或者自增列数不为1。"); + throw new Exception($"DbSet.Add 失败,未设置主键的值,或者没有配置自增,或者自增列数不为1:{_fsql.GetEntityString(source)}"); } break; } - key = GetEntityKeyString(source); + state.Key = key = _fsql.GetEntityKeyString(source); + state.Time = DateTime.Now; } else { if (_vals.ContainsKey(key)) - throw new Exception("DbSet.Add 失败,实体数据已存在,请勿重复添加。"); - _ctx.EnqueueAction(DbContext.ExecCommandInfoType.Insert, _entityType, this, source); + throw new Exception($"DbSet.Add 失败,实体数据已存在,请勿重复添加:{_fsql.GetEntityString(source)}"); + _ctx.EnqueueAction(DbContext.ExecCommandInfoType.Insert, this, typeof(EntityState), state); } - if (newval == null) { - newval = Activator.CreateInstance(); - CopyNewValueToEntity(newval, source); + if (state.Value == null) { + state.Value = Activator.CreateInstance(); + _fsql.CopyEntityValue(state.Value, source); //copy, 记录旧值版本 } - _vals.Add(key, newval); + _vals.Add(key, state); } public void AddRange(TEntity[] source) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -188,96 +108,52 @@ namespace FreeSql { Add(item); } - static ConcurrentDictionary> _dicCompareUpdateIngoreColumns = new ConcurrentDictionary>(); - string CompareUpdateIngoreColumns(TEntity up, TEntity oldval) { - var func = _dicCompareUpdateIngoreColumns.GetOrAdd(_entityType, t => { - var returnTarget = Expression.Label(typeof(string)); - var parm1 = Expression.Parameter(_entityType); - var parm2 = Expression.Parameter(_entityType); - var var1Sb = Expression.Variable(typeof(StringBuilder)); - var exps = new List(); - - exps.AddRange(new Expression[] { - Expression.Assign(var1Sb, Expression.New(typeof(StringBuilder))) - }); - var a = 0; - foreach (var prop in _table.Properties.Values) { - if (_table.ColumnsByCs.TryGetValue(prop.Name, out var trycol) == false) continue; - exps.Add( - Expression.IfThen( - Expression.Equal( - Expression.MakeMemberAccess(parm1, prop), - Expression.MakeMemberAccess(parm2, prop) - ), - Expression.Block( - new Expression[]{ - a > 0 ? Expression.Call(var1Sb, MethodStringBuilderAppend, Expression.Constant(", " )) : null, - Expression.Call(var1Sb, MethodStringBuilderAppend, Expression.Constant(trycol.Attribute.Name)) - }.Where(c => c != null).ToArray() - ) - ) - ); - a++; - } - exps.Add(Expression.Return(returnTarget, Expression.Call(var1Sb, MethodStringBuilderToString))); - exps.Add(Expression.Label(returnTarget, Expression.Default(typeof(string)))); - return Expression.Lambda>(Expression.Block(new[] { var1Sb }, exps), new[] { parm1, parm2 }).Compile(); - }); - return func(up, oldval); - } - int DbContextBetchUpdate(TEntity[] ups, bool isLiveUpdate) { + int DbContextBetchUpdate(EntityState[] ups) => DbContextBetchUpdatePriv(ups, false); + int DbContextBetchUpdateNow(EntityState[] ups) => DbContextBetchUpdatePriv(ups, true); + int DbContextBetchUpdatePriv(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; - var lstkey1 = GetEntityKeyString(uplst1); - if (_vals.TryGetValue(lstkey1, out var lstval1) == false) throw new Exception("DbSet.Update 失败,实体应该先查询再修改。"); - TEntity lstval2 = default(TEntity); - if (uplst2 != null) { - var lstkey2 = GetEntityKeyString(uplst2); - if (_vals.TryGetValue(lstkey2, out lstval2) == false) throw new Exception("DbSet.Update 失败,实体应该先查询再修改。"); - } + if (_vals.TryGetValue(uplst1.Key, out var lstval1) == false) return -999; + var lstval2 = default(EntityState); + if (uplst2 != null && _vals.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception($"DbSet.Update 失败,实体应该先查询再修改:{_fsql.GetEntityString(uplst2.Value)}"); - var cuig1 = CompareUpdateIngoreColumns(uplst1, lstval1); - var cuig2 = uplst2 != null ? CompareUpdateIngoreColumns(uplst2, lstval2) : null; - if (uplst2 != null && string.Compare(cuig1, cuig2, true) != 0) { + var cuig1 = _fsql.CompareEntityValueReturnColumns(uplst1.Value, lstval1.Value, true); + var cuig2 = uplst2 != null ? _fsql.CompareEntityValueReturnColumns(uplst2.Value, lstval2.Value, true) : null; + if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0) { //最后一个不保存 - var ignores = cuig2.Split(new[] { ", " }, StringSplitOptions.None); var source = ups.ToList(); source.RemoveAt(ups.Length - 1); - var affrows = this.OrmUpdate(null).SetSource(source).IgnoreColumns(ignores).ExecuteAffrows(); - foreach(var newval in source) { - var newkey = GetEntityKeyString(newval); - if (_vals.TryGetValue(newkey, out var tryold)) - CopyNewValueToEntity(tryold, newval); + var affrows = this.OrmUpdate(null).SetSource(source.Select(a => a.Value)).IgnoreColumns(cuig2).ExecuteAffrows(); + foreach (var newval in source) { + if (_vals.TryGetValue(newval.Key, out var tryold)) + _fsql.CopyEntityValue(tryold.Value, newval.Value); } return affrows; } else if (isLiveUpdate) { //立即保存 - var ignores = cuig1.Split(new[] { ", " }, StringSplitOptions.None); - var affrows = this.OrmUpdate(null).SetSource(ups).IgnoreColumns(ignores).ExecuteAffrows(); - foreach (var newval in ups) { - var newkey = GetEntityKeyString(newval); - if (_vals.TryGetValue(newkey, out var tryold)) - CopyNewValueToEntity(tryold, newval); + var source = ups; + var affrows = this.OrmUpdate(null).SetSource(source.Select(a => a.Value)).IgnoreColumns(cuig1).ExecuteAffrows(); + foreach (var newval in source) { + if (_vals.TryGetValue(newval.Key, out var tryold)) + _fsql.CopyEntityValue(tryold.Value, newval.Value); } return Math.Min(ups.Length, affrows); } //等待下次对比再保存 return 0; } - public void Update(TEntity source) { if (source == null) throw new ArgumentNullException(nameof(source)); - if (_table.Primarys.Any() == false) throw new Exception("DbSet.Update 失败,实体没有主键。"); - var key = GetEntityKeyString(source); - if (string.IsNullOrEmpty(key)) throw new Exception("DbSet.Update 失败,实体没有设置主键值。"); + if (_table.Primarys.Any() == false) throw new Exception($"DbSet.Update 失败,实体没有主键:{_fsql.GetEntityString(source)}"); + var key = _fsql.GetEntityKeyString(source); + if (string.IsNullOrEmpty(key)) throw new Exception($"DbSet.Update 失败,未设置主键的值:{_fsql.GetEntityString(source)}"); + if (_vals.TryGetValue(key, out var tryval) == false) throw new Exception($"DbSet.Update 失败,实体未被跟踪,更新前应该先做查询:{_fsql.GetEntityString(source)}"); var snap = Activator.CreateInstance(); - CopyNewValueToEntity(snap, source); - if (_vals.TryGetValue(key, out var val) == false) _vals.Add(key, snap); - - _ctx.EnqueueAction(DbContext.ExecCommandInfoType.Update, _entityType, this, snap); + _fsql.CopyEntityValue(snap, source); //copy,避免SaveChanges前对象再次被修改 + _ctx.EnqueueAction(DbContext.ExecCommandInfoType.Update, this, typeof(EntityState), new EntityState { Value = snap, Key = key, Time = DateTime.Now }); } public void UpdateRange(TEntity[] source) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -290,28 +166,26 @@ namespace FreeSql { Update(item); } - int DbContextBetchRemove(TEntity[] dels) { + int DbContextBetchRemove(EntityState[] dels) { if (dels.Any() == false) return 0; - - var affrows = this.OrmDelete(dels).ExecuteAffrows(); - foreach(var del in dels) { - var key = GetEntityKeyString(del); - _vals.Remove(key); - } + var affrows = this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrows(); + //foreach (var del in dels) + // _vals.Remove(del.Key); return affrows; } - public void Remove(TEntity source) { if (source == null) throw new ArgumentNullException(nameof(source)); - if (_table.Primarys.Any() == false) throw new Exception("DbSet.Remove 失败,实体没有主键。"); - var key = GetEntityKeyString(source); - if (string.IsNullOrEmpty(key)) throw new Exception("DbSet.Remove 失败,实体没有设置主键值。"); + if (_table.Primarys.Any() == false) throw new Exception($"DbSet.Remove 失败,实体没有主键:{_fsql.GetEntityString(source)}"); + var key = _fsql.GetEntityKeyString(source); + if (string.IsNullOrEmpty(key)) throw new Exception($"DbSet.Remove 失败,未设置主键的值:{_fsql.GetEntityString(source)}"); + if (_vals.TryGetValue(key, out var tryval) == false) throw new Exception($"DbSet.Remove 失败,实体未被跟踪,删除前应该先做查询:{_fsql.GetEntityString(source)}"); var snap = Activator.CreateInstance(); - CopyNewValueToEntity(snap, source); - if (_vals.TryGetValue(key, out var val) == false) _vals.Add(key, snap); + _fsql.CopyEntityValue(snap, source); //copy,避免SaveChanges前对象再次被修改 + _ctx.EnqueueAction(DbContext.ExecCommandInfoType.Delete, this, typeof(EntityState), new EntityState { Value = snap, Key = key, Time = DateTime.Now }); - _ctx.EnqueueAction(DbContext.ExecCommandInfoType.Delete, _entityType, this, snap); + _vals.Remove(key); + _fsql.ClearEntityPrimaryValueWithIdentityAndGuid(source); } public void RemoveRange(TEntity[] source) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -323,6 +197,23 @@ namespace FreeSql { foreach (var item in source) Remove(item); } + + void TrackToList(object list) { + if (list == null) return; + var ls = list as IList; + + foreach (var item in ls) { + var key = _fsql.GetEntityKeyString(item); + if (_vals.ContainsKey(key)) { + _fsql.CopyEntityValue(_vals[key].Value, item); + _vals[key].Time = DateTime.Now; + } else { + var snap = Activator.CreateInstance(); + _fsql.CopyEntityValue(snap, item); + _vals.Add(key, new EntityState { Value = snap, Key = key, Time = DateTime.Now }); + } + } + } } internal class BaseDbSet : DbSet where TEntity : class { diff --git a/FreeSql.DbContext/DbSetAsync.cs b/FreeSql.DbContext/DbSetAsync.cs index 20954d7b..cb2b6f7c 100644 --- a/FreeSql.DbContext/DbSetAsync.cs +++ b/FreeSql.DbContext/DbSetAsync.cs @@ -10,57 +10,62 @@ using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using System.Reflection; +using FreeSql.Extensions; namespace FreeSql { - partial class DbSet { + abstract partial class DbSet { + async Task DbContextBetcAddAsync(EntityState[] dels) { + if (dels.Any() == false) return 0; + var affrows = await this.OrmInsert(dels.Select(a => a.Value)).ExecuteAffrowsAsync(); + return affrows; + } async public Task AddAsync(TEntity source) { if (source == null) throw new ArgumentNullException(nameof(source)); - var key = GetEntityKeyString(source); - TEntity newval = null; + var key = _fsql.GetEntityKeyString(source); + EntityState state = new EntityState(); if (string.IsNullOrEmpty(key)) { - var ids = _table.Primarys.Where(a => a.Attribute.IsIdentity).ToArray(); - - switch (_ctx._orm.Ado.DataType) { + switch (_fsql.Ado.DataType) { case DataType.SqlServer: case DataType.PostgreSQL: - if (ids.Length == 1 && _table.Primarys.Length == 1) { - await _ctx.ExecCommandAsync(); + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { + _ctx.ExecCommand(); var idtval = await this.OrmInsert(source).ExecuteIdentityAsync(); _ctx._affrows++; - SetEntityIdentityValue(source, idtval); + _fsql.SetEntityIdentityValue(source, idtval); } else { - await _ctx.ExecCommandAsync(); - newval = (await this.OrmInsert(source).ExecuteInsertedAsync()).First(); + _ctx.ExecCommand(); + state.Value = (await this.OrmInsert(source).ExecuteInsertedAsync()).First(); _ctx._affrows++; - CopyNewValueToEntity(source, newval); + _fsql.CopyEntityValue(source, state.Value); } break; case DataType.MySql: case DataType.Oracle: case DataType.Sqlite: - if (ids.Length == 1 && _table.Primarys.Length == 1) { - await _ctx.ExecCommandAsync(); + if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1) { + _ctx.ExecCommand(); var idtval = await this.OrmInsert(source).ExecuteIdentityAsync(); _ctx._affrows++; - SetEntityIdentityValue(source, idtval); + _fsql.SetEntityIdentityValue(source, idtval); } else { - throw new Exception("DbSet.Add 失败,由于实体没有主键值,或者没有配置自增,或者自增列数不为1。"); + throw new Exception($"DbSet.Add 失败,未设置主键的值,或者没有配置自增,或者自增列数不为1:{_fsql.GetEntityString(source)}"); } break; } - key = GetEntityKeyString(source); + state.Key = key = _fsql.GetEntityKeyString(source); + state.Time = DateTime.Now; } else { if (_vals.ContainsKey(key)) - throw new Exception("DbSet.Add 失败,实体数据已存在,请勿重复添加。"); - _ctx.EnqueueAction(DbContext.ExecCommandInfoType.Insert, _entityType, this, source); + throw new Exception($"DbSet.Add 失败,实体数据已存在,请勿重复添加:{_fsql.GetEntityString(source)}"); + _ctx.EnqueueAction(DbContext.ExecCommandInfoType.Insert, this, typeof(EntityState), state); } - if (newval == null) { - newval = Activator.CreateInstance(); - CopyNewValueToEntity(newval, source); + if (state.Value == null) { + state.Value = Activator.CreateInstance(); + _fsql.CopyEntityValue(state.Value, source); //copy, 记录旧值版本 } - _vals.Add(key, newval); + _vals.Add(key, state); } async public Task AddRangeAsync(TEntity[] source) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -73,56 +78,47 @@ namespace FreeSql { await AddAsync(item); } - async Task DbContextBetchUpdateAsync(TEntity[] ups, bool isLiveUpdate) { + 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; - var lstkey1 = GetEntityKeyString(uplst1); - if (_vals.TryGetValue(lstkey1, out var lstval1) == false) throw new Exception("DbSet.Update 失败,实体应该先查询再修改。"); - TEntity lstval2 = default(TEntity); - if (uplst2 != null) { - var lstkey2 = GetEntityKeyString(uplst2); - if (_vals.TryGetValue(lstkey2, out lstval2) == false) throw new Exception("DbSet.Update 失败,实体应该先查询再修改。"); - } + if (_vals.TryGetValue(uplst1.Key, out var lstval1) == false) return -999; + var lstval2 = default(EntityState); + if (uplst2 != null && _vals.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception($"DbSet.Update 失败,实体应该先查询再修改:{_fsql.GetEntityString(uplst2.Value)}"); - var cuig1 = CompareUpdateIngoreColumns(uplst1, lstval1); - var cuig2 = uplst2 != null ? CompareUpdateIngoreColumns(uplst2, lstval2) : null; - if (uplst2 != null && string.Compare(cuig1, cuig2, true) != 0) { + var cuig1 = _fsql.CompareEntityValueReturnColumns(uplst1.Value, lstval1.Value, true); + var cuig2 = uplst2 != null ? _fsql.CompareEntityValueReturnColumns(uplst2.Value, lstval2.Value, true) : null; + if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0) { //最后一个不保存 - var ignores = cuig2.Split(new[] { ", " }, StringSplitOptions.None); var source = ups.ToList(); source.RemoveAt(ups.Length - 1); - var affrows = await this.OrmUpdate(null).SetSource(source).IgnoreColumns(ignores).ExecuteAffrowsAsync(); + var affrows = await this.OrmUpdate(null).SetSource(source.Select(a => a.Value)).IgnoreColumns(cuig2).ExecuteAffrowsAsync(); foreach (var newval in source) { - var newkey = GetEntityKeyString(newval); - if (_vals.TryGetValue(newkey, out var tryold)) - CopyNewValueToEntity(tryold, newval); + if (_vals.TryGetValue(newval.Key, out var tryold)) + _fsql.CopyEntityValue(tryold.Value, newval.Value); } return affrows; } else if (isLiveUpdate) { //立即保存 - var ignores = cuig1.Split(new[] { ", " }, StringSplitOptions.None); - var affrows = await this.OrmUpdate(null).SetSource(ups).IgnoreColumns(ignores).ExecuteAffrowsAsync(); - foreach (var newval in ups) { - var newkey = GetEntityKeyString(newval); - if (_vals.TryGetValue(newkey, out var tryold)) - CopyNewValueToEntity(tryold, newval); + var source = ups; + var affrows = await this.OrmUpdate(null).SetSource(source.Select(a => a.Value)).IgnoreColumns(cuig1).ExecuteAffrowsAsync(); + foreach (var newval in source) { + if (_vals.TryGetValue(newval.Key, out var tryold)) + _fsql.CopyEntityValue(tryold.Value, newval.Value); } return Math.Min(ups.Length, affrows); } //等待下次对比再保存 return 0; } - - async Task DbContextBetchRemoveAsync(TEntity[] dels) { + async Task DbContextBetchRemoveAsync(EntityState[] dels) { if (dels.Any() == false) return 0; - - var affrows = await this.OrmDelete(dels).ExecuteAffrowsAsync(); - foreach (var del in dels) { - var key = GetEntityKeyString(del); - _vals.Remove(key); - } + var affrows = await this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrowsAsync(); + //foreach (var del in dels) + // _vals.Remove(del.Key); return affrows; } } diff --git a/FreeSql.DbContext/EntityUtil.cs b/FreeSql.DbContext/EntityUtil.cs new file mode 100644 index 00000000..fc4c2931 --- /dev/null +++ b/FreeSql.DbContext/EntityUtil.cs @@ -0,0 +1,278 @@ +using FreeSql.Internal.Model; +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.Extensions { + public static class EntityUtilFreeSqlExtensions { + + static MethodInfo MethodStringBuilderAppend = typeof(StringBuilder).GetMethod("Append", new Type[] { typeof(object) }); + static MethodInfo MethodStringBuilderToString = typeof(StringBuilder).GetMethod("ToString", new Type[0]); + static PropertyInfo MethodStringBuilderLength = typeof(StringBuilder).GetProperty("Length"); + static MethodInfo MethodStringConcat = typeof(string).GetMethod("Concat", new Type[] { typeof(object) }); + + static ConcurrentDictionary>> _dicGetEntityKeyString = new ConcurrentDictionary>>(); + /// + /// 获取实体的主键值,以 "*|_,[,_|*" 分割 + /// + /// + /// + /// + /// + public static string GetEntityKeyString(this IFreeSql orm, TEntity item, string splitString = "*|_,[,_|*") { + var func = _dicGetEntityKeyString.GetOrAdd(orm.Ado.DataType, dt => new ConcurrentDictionary>()).GetOrAdd(typeof(TEntity), t => { + var _table = orm.CodeFirst.GetTableByEntity(t); + var pks = _table.Primarys; + var returnTarget = Expression.Label(typeof(string)); + var parm1 = Expression.Parameter(typeof(object)); + var var1Parm = Expression.Variable(t); + var var2Sb = Expression.Variable(typeof(StringBuilder)); + var var3IsNull = Expression.Variable(typeof(bool)); + var exps = new List(new Expression[] { + Expression.Assign(var1Parm, Expression.TypeAs(parm1, t)), + Expression.Assign(var2Sb, Expression.New(typeof(StringBuilder))), + Expression.Assign(var3IsNull, Expression.Constant(false)) + }); + for (var a = 0; a < pks.Length; a++) { + exps.Add( + Expression.IfThen( + Expression.Equal(var3IsNull, Expression.Constant(false)), + Expression.IfThenElse( + Expression.Equal(Expression.MakeMemberAccess(var1Parm, _table.Properties[pks[a].CsName]), Expression.Default(pks[a].CsType)), + Expression.Assign(var3IsNull, Expression.Constant(true)), + Expression.Block( + new Expression[]{ + a > 0 ? Expression.Call(var2Sb, MethodStringBuilderAppend, Expression.Constant(splitString)) : null, + Expression.Call(var2Sb, MethodStringBuilderAppend, + Expression.Convert(Expression.MakeMemberAccess(var1Parm, _table.Properties[pks[a].CsName]), typeof(object)) + ) + }.Where(c => c != null).ToArray() + ) + ) + ) + ); + } + exps.Add( + Expression.IfThen( + Expression.Equal(var3IsNull, Expression.Constant(false)), + Expression.Return(returnTarget, Expression.Call(var2Sb, MethodStringBuilderToString)) + ) + ); + exps.Add(Expression.Label(returnTarget, Expression.Default(typeof(string)))); + return Expression.Lambda>(Expression.Block(new[] { var1Parm, var2Sb, var3IsNull }, exps), new[] { parm1 }).Compile(); + }); + return func(item); + } + static ConcurrentDictionary>> _dicGetEntityString = new ConcurrentDictionary>>(); + /// + /// 获取实体的所有数据,以 (1, 2, xxx) 的形式 + /// + /// + /// + /// + /// + public static string GetEntityString(this IFreeSql orm, TEntity item) { + var func = _dicGetEntityString.GetOrAdd(orm.Ado.DataType, dt => new ConcurrentDictionary>()).GetOrAdd(typeof(TEntity), t => { + var _table = orm.CodeFirst.GetTableByEntity(t); + var cols = _table.Columns; + var returnTarget = Expression.Label(typeof(string)); + var parm1 = Expression.Parameter(typeof(object)); + var var1Parm = Expression.Variable(t); + var var2Sb = Expression.Variable(typeof(StringBuilder)); + var var3IsNull = Expression.Variable(typeof(bool)); + var exps = new List(new Expression[] { + Expression.Assign(var1Parm, Expression.TypeAs(parm1, t)), + Expression.Assign(var2Sb, Expression.New(typeof(StringBuilder))), + Expression.Assign(var3IsNull, Expression.Constant(false)), + Expression.Call(var2Sb, MethodStringBuilderAppend, Expression.Constant("(" )) + }); + var a = 0; + foreach (var col in cols.Values) { + exps.Add( + Expression.IfThen( + Expression.Equal(var3IsNull, Expression.Constant(false)), + Expression.IfThenElse( + Expression.Equal(Expression.MakeMemberAccess(var1Parm, _table.Properties[col.CsName]), Expression.Default(col.CsType)), + Expression.Assign(var3IsNull, Expression.Constant(true)), + Expression.Block( + new Expression[]{ + a > 0 ? Expression.Call(var2Sb, MethodStringBuilderAppend, Expression.Constant(", " )) : null, + Expression.Call(var2Sb, MethodStringBuilderAppend, + Expression.Convert(Expression.MakeMemberAccess(var1Parm, _table.Properties[col.CsName]), typeof(object)) + ) + }.Where(c => c != null).ToArray() + ) + ) + ) + ); + a++; + } + exps.AddRange(new Expression[] { + Expression.Call(var2Sb, MethodStringBuilderAppend, Expression.Constant(")" )), + Expression.IfThen( + Expression.Equal(var3IsNull, Expression.Constant(false)), + Expression.Return(returnTarget, Expression.Call(var2Sb, MethodStringBuilderToString)) + ) + }); + exps.Add(Expression.Label(returnTarget, Expression.Default(typeof(string)))); + return Expression.Lambda>(Expression.Block(new[] { var1Parm, var2Sb, var3IsNull }, exps), new[] { parm1 }).Compile(); + }); + return func(item); + } + + /// + /// 使用新实体的值,复盖旧实体的值 + /// + static ConcurrentDictionary>> _dicCopyNewValueToEntity = new ConcurrentDictionary>>(); + public static void CopyEntityValue(this IFreeSql orm, TEntity oldValue, TEntity newValue) { + var func = _dicCopyNewValueToEntity.GetOrAdd(orm.Ado.DataType, dt => new ConcurrentDictionary>()).GetOrAdd(typeof(TEntity), t => { + var _table = orm.CodeFirst.GetTableByEntity(t); + var parm1 = Expression.Parameter(typeof(object)); + var parm2 = Expression.Parameter(typeof(object)); + var var1Parm = Expression.Variable(t); + var var2Parm = Expression.Variable(t); + var exps = new List(new Expression[] { + Expression.Assign(var1Parm, Expression.TypeAs(parm1, t)), + Expression.Assign(var2Parm, Expression.TypeAs(parm2, t)) + }); + foreach (var prop in _table.Properties.Values) { + if (_table.ColumnsByCs.ContainsKey(prop.Name)) { + exps.Add( + Expression.Assign( + Expression.MakeMemberAccess(var1Parm, prop), + Expression.MakeMemberAccess(var2Parm, prop) + ) + ); + } else { + exps.Add( + Expression.Assign( + Expression.MakeMemberAccess(var1Parm, prop), + Expression.Default(prop.PropertyType) + ) + ); + } + } + return Expression.Lambda>(Expression.Block(new[] { var1Parm, var2Parm }, exps), new[] { parm1, parm2 }).Compile(); + }); + func(oldValue, newValue); + } + + static ConcurrentDictionary>> _dicSetEntityIdentityValue = new ConcurrentDictionary>>(); + /// + /// 设置实体的自增字段值(若存在) + /// + /// + /// + /// + /// + public static void SetEntityIdentityValue(this IFreeSql orm, TEntity item, long idtval) { + var func = _dicSetEntityIdentityValue.GetOrAdd(orm.Ado.DataType, dt => new ConcurrentDictionary>()).GetOrAdd(typeof(TEntity), t => { + var _table = orm.CodeFirst.GetTableByEntity(t); + var identitys = _table.Primarys.Where(a => a.Attribute.IsIdentity); + var parm1 = Expression.Parameter(typeof(object)); + var parm2 = Expression.Parameter(typeof(long)); + var var1Parm = Expression.Variable(t); + var exps = new List(new Expression[] { + Expression.Assign(var1Parm, Expression.TypeAs(parm1, t)) + }); + foreach (var pk in identitys) { + exps.Add( + Expression.Assign( + Expression.MakeMemberAccess(var1Parm, _table.Properties[pk.CsName]), + Expression.Convert(FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(pk.CsType, Expression.Convert(parm2, typeof(object))), pk.CsType) + ) + ); + } + return Expression.Lambda>(Expression.Block(new[] { var1Parm }, exps), new[] { parm1, parm2 }).Compile(); + }); + func(item, idtval); + } + static ConcurrentDictionary>> _dicClearEntityPrimaryValueWithIdentityAndGuid = new ConcurrentDictionary>>(); + /// + /// 清除实体的主键值,将自增、Guid类型的主键值清除 + /// + /// + /// + /// + public static void ClearEntityPrimaryValueWithIdentityAndGuid(this IFreeSql orm, TEntity item) { + var func = _dicClearEntityPrimaryValueWithIdentityAndGuid.GetOrAdd(orm.Ado.DataType, dt => new ConcurrentDictionary>()).GetOrAdd(typeof(TEntity), t => { + var _table = orm.CodeFirst.GetTableByEntity(t); + var identitys = _table.Primarys.Where(a => a.Attribute.IsIdentity); + var parm1 = Expression.Parameter(typeof(object)); + var var1Parm = Expression.Variable(t); + var exps = new List(new Expression[] { + Expression.Assign(var1Parm, Expression.TypeAs(parm1, t)) + }); + foreach (var pk in _table.Primarys) { + if (pk.CsType == typeof(Guid) || pk.CsType == typeof(Guid?) || + pk.Attribute.IsIdentity) { + exps.Add( + Expression.Assign( + Expression.MakeMemberAccess(var1Parm, _table.Properties[pk.CsName]), + Expression.Default(pk.CsType) + ) + ); + } + } + return Expression.Lambda>(Expression.Block(new[] { var1Parm }, exps), new[] { parm1 }).Compile(); + }); + func(item); + } + + static ConcurrentDictionary>> _dicCompareEntityValueReturnColumns = new ConcurrentDictionary>>(); + /// + /// 对比两个实体值,返回相同/或不相同的列名 + /// + /// + /// + /// + /// + /// + public static string[] CompareEntityValueReturnColumns(this IFreeSql orm, TEntity up, TEntity oldval, bool isEqual) { + var func = _dicCompareEntityValueReturnColumns.GetOrAdd(orm.Ado.DataType, dt => new ConcurrentDictionary>()).GetOrAdd(typeof(TEntity), t => { + var _table = orm.CodeFirst.GetTableByEntity(t); + var returnTarget = Expression.Label(typeof(string[])); + var parm1 = Expression.Parameter(typeof(object)); + var parm2 = Expression.Parameter(typeof(object)); + var parm3 = Expression.Parameter(typeof(bool)); + var var1Ret = Expression.Variable(typeof(List)); + var var1Parm = Expression.Variable(t); + var var2Parm = Expression.Variable(t); + var exps = new List(new Expression[] { + Expression.Assign(var1Parm, Expression.TypeAs(parm1, t)), + Expression.Assign(var2Parm, Expression.TypeAs(parm2, t)), + Expression.Assign(var1Ret, Expression.New(typeof(List))) + }); + var a = 0; + foreach (var prop in _table.Properties.Values) { + if (_table.ColumnsByCs.TryGetValue(prop.Name, out var trycol) == false) continue; + exps.Add( + Expression.IfThenElse( + Expression.Equal( + Expression.MakeMemberAccess(var1Parm, prop), + Expression.MakeMemberAccess(var2Parm, prop) + ), + Expression.IfThen( + Expression.IsTrue(parm3), + Expression.Call(var1Ret, typeof(List).GetMethod("Add", new Type[] { typeof(string) }), Expression.Constant(trycol.Attribute.Name)) + ), + Expression.IfThen( + Expression.IsFalse(parm3), + Expression.Call(var1Ret, typeof(List).GetMethod("Add", new Type[] { typeof(string) }), Expression.Constant(trycol.Attribute.Name)) + ) + ) + ); + a++; + } + exps.Add(Expression.Return(returnTarget, Expression.Call(var1Ret, typeof(List).GetMethod("ToArray", new Type[0])))); + exps.Add(Expression.Label(returnTarget, Expression.Constant(new string[0]))); + return Expression.Lambda>(Expression.Block(new[] { var1Ret, var1Parm, var2Parm }, exps), new[] { parm1, parm2, parm3 }).Compile(); + }); + return func(up, oldval, isEqual); + } + } +} diff --git a/FreeSql.DbContext/FreeSql.DbContext.csproj b/FreeSql.DbContext/FreeSql.DbContext.csproj index d82dbf06..251ffe10 100644 --- a/FreeSql.DbContext/FreeSql.DbContext.csproj +++ b/FreeSql.DbContext/FreeSql.DbContext.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 0.3.21 + 0.3.22 true YeXiangQin FreeSql is the most convenient ORM in dotnet. It supports Mysql, Postgresql, SqlServer, Oracle and Sqlite. diff --git a/FreeSql.Repository/FreeSql.Repository.csproj b/FreeSql.Repository/FreeSql.Repository.csproj index 61cf0244..a9e5a740 100644 --- a/FreeSql.Repository/FreeSql.Repository.csproj +++ b/FreeSql.Repository/FreeSql.Repository.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 0.3.21 + 0.3.22 YeXiangQin FreeSql Implementation of General Repository, Support MySql/SqlServer/PostgreSQL/Oracle/Sqlite, and read/write separation、and split table. https://github.com/2881099/FreeSql/wiki/Repository diff --git a/FreeSql.Tests/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs b/FreeSql.Tests/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs index 73354609..599cc076 100644 --- a/FreeSql.Tests/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs +++ b/FreeSql.Tests/PostgreSQL/PostgreSQLAdo/PostgreSQLAdoTest.cs @@ -33,11 +33,13 @@ namespace FreeSql.Tests.PostgreSQL { [Fact] public void Query() { - var t3 = g.pgsql.Ado.Query("select * from song"); - var t4 = g.pgsql.Ado.Query<(int, string, string)>("select * from song"); + g.pgsql.CodeFirst.SyncStructure(); + var t3 = g.pgsql.Ado.Query("select * from xxx"); - var t5 = g.pgsql.Ado.Query("select * from song"); + var t4 = g.pgsql.Ado.Query<(int, string, string)>("select * from xxx"); + + var t5 = g.pgsql.Ado.Query("select * from xxx"); } class xxx { diff --git a/FreeSql/FreeSql.csproj b/FreeSql/FreeSql.csproj index 9fab5623..01299f5d 100644 --- a/FreeSql/FreeSql.csproj +++ b/FreeSql/FreeSql.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 0.3.21 + 0.3.22 true YeXiangQin FreeSql is the most convenient ORM in dotnet. It supports Mysql, Postgresql, SqlServer, Oracle and Sqlite. diff --git a/FreeSql/Interface/Curd/ISelect/ISelect0.cs b/FreeSql/Interface/Curd/ISelect/ISelect0.cs index 472e5d52..a3906cbb 100644 --- a/FreeSql/Interface/Curd/ISelect/ISelect0.cs +++ b/FreeSql/Interface/Curd/ISelect/ISelect0.cs @@ -16,6 +16,13 @@ namespace FreeSql { /// TSelect WithTransaction(DbTransaction transaction); + /// + /// 审核或跟踪 ToList 即将返回的数据 + /// + /// + /// + TSelect TrackToList(Action action); + /// /// 执行SQL查询,返回 DataTable /// diff --git a/FreeSql/Interface/IAop.cs b/FreeSql/Interface/IAop.cs index 1b6d0756..06fbc895 100644 --- a/FreeSql/Interface/IAop.cs +++ b/FreeSql/Interface/IAop.cs @@ -1,20 +1,64 @@ -//using FreeSql.DatabaseModel; -//using System; -//using System.Collections.Generic; +using FreeSql.DatabaseModel; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; -//namespace FreeSql { -// public interface IAop { +namespace FreeSql { + public interface IAop { -// ISelect SelectFitler(ISelect select) where T1 : class; -// //ISelect SelectFitler(ISelect select) where T1 : class where T2 : class where T3 : class; -// //ISelect SelectFitler(ISelect select) where T1 : class where T2 : class where T3 : class where T4 : class; -// //ISelect SelectFitler(ISelect select) where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class; -// //ISelect SelectFitler(ISelect select) where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class; -// //ISelect SelectFitler(ISelect select) where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class; -// //ISelect SelectFitler(ISelect select) where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class; -// //ISelect SelectFitler(ISelect select) where T1 : class where T2 : class where T3 : class where T4 : class where T5 : class where T6 : class where T7 : class where T8 : class where T9 : class where T10 : class; + /// + /// 监控 ToList 返回的的数据,用于拦截重新装饰 + /// + EventHandler ToList { get; set; } -// IUpdate UpdateFitler(IUpdate update) where T1 : class; -// IDelete DeleteFitler(IUpdate delete) where T1 : class; -// } -//} + /// + /// 监视 Where,包括 select/update/delete,可控制使上层不被执行。 + /// + EventHandler Where { get; set; } + + /// + /// 可自定义解析表达式 + /// + EventHandler ParseExpression { get; set; } + } + + public class AopToListEventArgs : EventArgs { + public AopToListEventArgs(object list) { + this.List = list; + } + /// + /// 可重新装饰的引用数据 + /// + public object List { get; } + } + public class AopWhereEventArgs : EventArgs { + public AopWhereEventArgs(params object[] parameters) { + this.Parameters = parameters; + } + public object[] Parameters { get; } + /// + /// 可使上层不被执行这个条件 + /// + public bool IsCancel { get; set; } + } + public class AopParseExpressionEventArgs : EventArgs { + public AopParseExpressionEventArgs(Expression expression, Func freeParse) { + this.Expression = expression; + this.FreeParse = freeParse; + } + + /// + /// 内置解析功能,可辅助您进行解析 + /// + public Func FreeParse { get; } + + /// + /// 需要您解析的表达式 + /// + public Expression Expression { get; } + /// + /// 解析后的内容 + /// + public string Result { get; set; } + } +} diff --git a/FreeSql/Interface/IFreeSql.cs b/FreeSql/Interface/IFreeSql.cs index cb24c141..8c157ac7 100644 --- a/FreeSql/Interface/IFreeSql.cs +++ b/FreeSql/Interface/IFreeSql.cs @@ -95,6 +95,10 @@ public interface IFreeSql { /// 数据库访问对象 /// IAdo Ado { get; } + /// + /// 所有拦截方法都在这里 + /// + IAop Aop { get; } /// /// CodeFirst 模式开发相关方法 diff --git a/FreeSql/Internal/CommonExpression.cs b/FreeSql/Internal/CommonExpression.cs index 3c538f94..2e1e2403 100644 --- a/FreeSql/Internal/CommonExpression.cs +++ b/FreeSql/Internal/CommonExpression.cs @@ -227,8 +227,13 @@ namespace FreeSql.Internal { static ConcurrentDictionary _dicExpressionLambdaToSqlAsSelectAnyMethodInfo = new ConcurrentDictionary(); static ConcurrentDictionary _dicNullableValueProperty = new ConcurrentDictionary(); static ConcurrentDictionary _dicFreeSqlGlobalExtensionsAsSelectExpression = new ConcurrentDictionary(); - internal string ExpressionLambdaToSql(Expression exp, List _tables, List _selectColumnMap, Func getSelectGroupingMapString, SelectTableInfoType tbtype, bool isQuoteName) { + internal string ExpressionLambdaToSql(Expression exp, List _tables, List _selectColumnMap, Func getSelectGroupingMapString, SelectTableInfoType tbtype, bool isQuoteName, bool isDiyParse = true) { if (exp == null) return ""; + if (isDiyParse) { + var args = new AopParseExpressionEventArgs(exp, ukexp => ExpressionLambdaToSql(exp, _tables, _selectColumnMap, getSelectGroupingMapString, tbtype, isQuoteName, false)); + _common._orm.Aop.ParseExpression?.Invoke(this, args); + if (string.IsNullOrEmpty(args.Result) == false) return args.Result; + } switch (exp.NodeType) { case ExpressionType.Not: return $"not({ExpressionLambdaToSql((exp as UnaryExpression)?.Operand, _tables, _selectColumnMap, getSelectGroupingMapString, tbtype, isQuoteName)})"; case ExpressionType.Quote: return ExpressionLambdaToSql((exp as UnaryExpression)?.Operand, _tables, _selectColumnMap, getSelectGroupingMapString, tbtype, isQuoteName); diff --git a/FreeSql/Internal/CommonProvider/AopProvider.cs b/FreeSql/Internal/CommonProvider/AopProvider.cs new file mode 100644 index 00000000..c7375c21 --- /dev/null +++ b/FreeSql/Internal/CommonProvider/AopProvider.cs @@ -0,0 +1,13 @@ + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Text; + +namespace FreeSql.Internal.CommonProvider { + class AopProvider : IAop { + public EventHandler ToList { get; set; } + public EventHandler Where { get; set; } + public EventHandler ParseExpression { get; set; } + } +} diff --git a/FreeSql/Internal/CommonProvider/DeleteProvider.cs b/FreeSql/Internal/CommonProvider/DeleteProvider.cs index 0038ad26..1171a532 100644 --- a/FreeSql/Internal/CommonProvider/DeleteProvider.cs +++ b/FreeSql/Internal/CommonProvider/DeleteProvider.cs @@ -51,6 +51,10 @@ namespace FreeSql.Internal.CommonProvider { public IDelete Where(Expression> exp) => this.Where(_commonExpression.ExpressionWhereLambdaNoneForeignObject(null, null, exp?.Body, null)); public IDelete Where(string sql, object parms = null) { if (string.IsNullOrEmpty(sql)) return this; + var args = new AopWhereEventArgs(sql, parms); + _orm.Aop.Where?.Invoke(this, new AopWhereEventArgs(sql, parms)); + if (args.IsCancel == true) return this; + if (++_whereTimes > 1) _where.Append(" AND "); _where.Append("(").Append(sql).Append(")"); if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs index 6930bee3..46aedbca 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs @@ -28,6 +28,7 @@ namespace FreeSql.Internal.CommonProvider { protected CommonUtils _commonUtils; protected CommonExpression _commonExpression; protected DbTransaction _transaction; + protected Action _trackToList; internal static void CopyData(Select0Provider from, object to, ReadOnlyCollection lambParms) { var toType = to?.GetType(); @@ -60,6 +61,7 @@ namespace FreeSql.Internal.CommonProvider { //toType.GetField("_commonUtils", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._commonUtils); //toType.GetField("_commonExpression", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._commonExpression); toType.GetField("_transaction", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._transaction); + toType.GetField("_trackToList", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(to, from._trackToList); } public Select0Provider(IFreeSql orm, CommonUtils commonUtils, CommonExpression commonExpression, object dywhere) { @@ -71,6 +73,11 @@ namespace FreeSql.Internal.CommonProvider { if (_orm.CodeFirst.IsAutoSyncStructure) _orm.CodeFirst.SyncStructure(); } + public TSelect TrackToList(Action track) { + _trackToList = track; + return this as TSelect; + } + public TSelect WithTransaction(DbTransaction transaction) { _transaction = transaction; return this as TSelect; @@ -215,6 +222,8 @@ namespace FreeSql.Internal.CommonProvider { var read = Utils.ExecuteArrayRowReadClassOrTuple(type, null, dr, 0, _commonUtils); ret.Add((TTuple)read.Value); }, CommandType.Text, sql, _params.ToArray()); + _orm.Aop.ToList?.Invoke(this, new AopToListEventArgs(ret)); + _trackToList?.Invoke(ret); return ret; }); } @@ -230,6 +239,8 @@ namespace FreeSql.Internal.CommonProvider { ret.Add((TTuple)read.Value); return Task.CompletedTask; }, CommandType.Text, sql, _params.ToArray()); + _orm.Aop.ToList?.Invoke(this, new AopToListEventArgs(ret)); + _trackToList?.Invoke(ret); return ret; }); } @@ -243,6 +254,8 @@ namespace FreeSql.Internal.CommonProvider { _orm.Ado.ExecuteReader(_transaction, dr => { ret.Add(af.Read(dr)); }, CommandType.Text, sql, _params.ToArray()); + _orm.Aop.ToList?.Invoke(this, new AopToListEventArgs(ret)); + _trackToList?.Invoke(ret); return ret; }); } @@ -257,6 +270,8 @@ namespace FreeSql.Internal.CommonProvider { ret.Add(af.Read(dr)); return Task.CompletedTask; }, CommandType.Text, sql, _params.ToArray()); + _orm.Aop.ToList?.Invoke(this, new AopToListEventArgs(ret)); + _trackToList?.Invoke(ret); return ret; }); } @@ -283,6 +298,8 @@ namespace FreeSql.Internal.CommonProvider { var index = -1; ret.Add((TReturn)_commonExpression.ReadAnonymous(af.map, dr, ref index, false)); }, CommandType.Text, sql, _params.ToArray()); + _orm.Aop.ToList?.Invoke(this, new AopToListEventArgs(ret)); + _trackToList?.Invoke(ret); return ret; }); } @@ -298,6 +315,8 @@ namespace FreeSql.Internal.CommonProvider { ret.Add((TReturn)_commonExpression.ReadAnonymous(af.map, dr, ref index, false)); return Task.CompletedTask; }, CommandType.Text, sql, _params.ToArray()); + _orm.Aop.ToList?.Invoke(this, new AopToListEventArgs(ret)); + _trackToList?.Invoke(ret); return ret; }); } @@ -480,6 +499,10 @@ namespace FreeSql.Internal.CommonProvider { public TSelect Where(string sql, object parms = null) => this.WhereIf(true, sql, parms); public TSelect WhereIf(bool condition, string sql, object parms = null) { if (condition == false || string.IsNullOrEmpty(sql)) return this as TSelect; + var args = new AopWhereEventArgs(sql, parms); + _orm.Aop.Where?.Invoke(this, new AopWhereEventArgs(sql, parms)); + if (args.IsCancel == true) return this as TSelect; + _where.Append(" AND (").Append(sql).Append(")"); if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); return this as TSelect; diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/SelectGroupingProvider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/SelectGroupingProvider.cs index a205f595..a1fecc2d 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/SelectGroupingProvider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/SelectGroupingProvider.cs @@ -79,7 +79,5 @@ namespace FreeSql.Internal.CommonProvider { var method = _select.GetType().GetMethod("ToSql", new[] { typeof(string) }); return method.Invoke(_select, new object[] { field.Length > 0 ? field.Remove(0, 2).ToString() : null }) as string; } - - } } diff --git a/FreeSql/Internal/CommonProvider/UpdateProvider.cs b/FreeSql/Internal/CommonProvider/UpdateProvider.cs index ea6516bb..ad372f53 100644 --- a/FreeSql/Internal/CommonProvider/UpdateProvider.cs +++ b/FreeSql/Internal/CommonProvider/UpdateProvider.cs @@ -117,6 +117,10 @@ namespace FreeSql.Internal.CommonProvider { public IUpdate Where(Expression> expression) => this.Where(_commonExpression.ExpressionWhereLambdaNoneForeignObject(null, null, expression?.Body, null)); public IUpdate Where(string sql, object parms = null) { if (string.IsNullOrEmpty(sql)) return this; + var args = new AopWhereEventArgs(sql, parms); + _orm.Aop.Where?.Invoke(this, new AopWhereEventArgs(sql, parms)); + if (args.IsCancel == true) return this; + _where.Append(" AND (").Append(sql).Append(")"); if (parms != null) _params.AddRange(_commonUtils.GetDbParamtersByObject(sql, parms)); return this; diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs index de941cb0..65a0b6b6 100644 --- a/FreeSql/Internal/UtilsExpressionTree.cs +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -494,7 +494,10 @@ namespace FreeSql.Internal { var findtbrefPkCsName = tbref.Primarys[a].CsName.TrimStart('_'); if (findtbrefPkCsName.StartsWith(tbref.Type.Name, StringComparison.CurrentCultureIgnoreCase)) findtbrefPkCsName = findtbrefPkCsName.Substring(tbref.Type.Name.Length).TrimStart('_'); if (trytb.ColumnsByCs.TryGetValue($"{pnv.Name}{findtbrefPkCsName}", out var trycol) == false && //骆峰命名 - trytb.ColumnsByCs.TryGetValue($"{pnv.Name}_{findtbrefPkCsName}", out trycol) == false //下划线命名 + trytb.ColumnsByCs.TryGetValue($"{pnv.Name}_{findtbrefPkCsName}", out trycol) == false && //下划线命名 + tbref.Primarys.Length == 1 && + trytb.ColumnsByCs.TryGetValue($"{pnv.Name}_Id", out trycol) == false && + trytb.ColumnsByCs.TryGetValue($"{pnv.Name}Id", out trycol) == false ) { //一对一,主键与主键查找 if (isOnoToOne) { diff --git a/FreeSql/MySql/MySqlProvider.cs b/FreeSql/MySql/MySqlProvider.cs index ac98e30d..c1c87bd9 100644 --- a/FreeSql/MySql/MySqlProvider.cs +++ b/FreeSql/MySql/MySqlProvider.cs @@ -33,6 +33,7 @@ namespace FreeSql.MySql { public IDelete Delete(object dywhere) where T1 : class => new MySqlDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); public IAdo Ado { get; } + public IAop Aop { get; } public ICache Cache { get; } public ICodeFirst CodeFirst { get; } public IDbFirst DbFirst { get; } @@ -44,6 +45,7 @@ namespace FreeSql.MySql { this.Cache = new CacheProvider(cache, log); this.Ado = new MySqlAdo(this.InternalCommonUtils, this.Cache, log, masterConnectionString, slaveConnectionString); + this.Aop = new AopProvider(); this.DbFirst = new MySqlDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); this.CodeFirst = new MySqlCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); diff --git a/FreeSql/Oracle/OracleProvider.cs b/FreeSql/Oracle/OracleProvider.cs index da9317ee..e8fba743 100644 --- a/FreeSql/Oracle/OracleProvider.cs +++ b/FreeSql/Oracle/OracleProvider.cs @@ -24,6 +24,7 @@ namespace FreeSql.Oracle { public IDelete Delete(object dywhere) where T1 : class => new OracleDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); public IAdo Ado { get; } + public IAop Aop { get; } public ICache Cache { get; } public ICodeFirst CodeFirst { get; } public IDbFirst DbFirst => null; @@ -35,6 +36,7 @@ namespace FreeSql.Oracle { this.Cache = new CacheProvider(cache, log); this.Ado = new OracleAdo(this.InternalCommonUtils, this.Cache, log, masterConnectionString, slaveConnectionString); + this.Aop = new AopProvider(); this.CodeFirst = new OracleCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); } diff --git a/FreeSql/PostgreSQL/PostgreSQLProvider.cs b/FreeSql/PostgreSQL/PostgreSQLProvider.cs index 6cd7e645..61b5fbc8 100644 --- a/FreeSql/PostgreSQL/PostgreSQLProvider.cs +++ b/FreeSql/PostgreSQL/PostgreSQLProvider.cs @@ -60,6 +60,7 @@ namespace FreeSql.PostgreSQL { public IDelete Delete(object dywhere) where T1 : class => new PostgreSQLDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); public IAdo Ado { get; } + public IAop Aop { get; } public ICache Cache { get; } public ICodeFirst CodeFirst { get; } public IDbFirst DbFirst { get; } @@ -71,6 +72,7 @@ namespace FreeSql.PostgreSQL { this.Cache = new CacheProvider(cache, log); this.Ado = new PostgreSQLAdo(this.InternalCommonUtils, this.Cache, log, masterConnectionString, slaveConnectionString); + this.Aop = new AopProvider(); this.DbFirst = new PostgreSQLDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); this.CodeFirst = new PostgreSQLCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); diff --git a/FreeSql/SqlServer/SqlServerProvider.cs b/FreeSql/SqlServer/SqlServerProvider.cs index 2356e299..42fe5168 100644 --- a/FreeSql/SqlServer/SqlServerProvider.cs +++ b/FreeSql/SqlServer/SqlServerProvider.cs @@ -23,6 +23,7 @@ namespace FreeSql.SqlServer { public IDelete Delete(object dywhere) where T1 : class => new SqlServerDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); public IAdo Ado { get; } + public IAop Aop { get; } public ICache Cache { get; } public ICodeFirst CodeFirst { get; } public IDbFirst DbFirst { get; } @@ -34,6 +35,7 @@ namespace FreeSql.SqlServer { this.Cache = new CacheProvider(cache, log); this.Ado = new SqlServerAdo(this.InternalCommonUtils, this.Cache, log, masterConnectionString, slaveConnectionString); + this.Aop = new AopProvider(); this.DbFirst = new SqlServerDbFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); this.CodeFirst = new SqlServerCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); diff --git a/FreeSql/Sqlite/SqliteProvider.cs b/FreeSql/Sqlite/SqliteProvider.cs index 7ebee7f6..0c83dbb8 100644 --- a/FreeSql/Sqlite/SqliteProvider.cs +++ b/FreeSql/Sqlite/SqliteProvider.cs @@ -24,6 +24,7 @@ namespace FreeSql.Sqlite { public IDelete Delete(object dywhere) where T1 : class => new SqliteDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); public IAdo Ado { get; } + public IAop Aop { get; } public ICache Cache { get; } public ICodeFirst CodeFirst { get; } public IDbFirst DbFirst => null; @@ -35,6 +36,7 @@ namespace FreeSql.Sqlite { this.Cache = new CacheProvider(cache, log); this.Ado = new SqliteAdo(this.InternalCommonUtils, this.Cache, log, masterConnectionString, slaveConnectionString); + this.Aop = new AopProvider(); this.CodeFirst = new SqliteCodeFirst(this, this.InternalCommonUtils, this.InternalCommonExpression); } diff --git a/readme.md b/readme.md index 4c31cba5..94ca62f0 100644 --- a/readme.md +++ b/readme.md @@ -25,59 +25,20 @@ FreeSql 是一个功能强大的 .NETStandard 库,用于对象关系映射程 | 高手 | [《Repository》](https://github.com/2881099/FreeSql/wiki/Repository) \| [《UnitOfWork》](https://github.com/2881099/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83) \| [《过滤器》](https://github.com/2881099/FreeSql/wiki/%e8%bf%87%e6%bb%a4%e5%99%a8) \| [《DbContext》](https://github.com/2881099/FreeSql/wiki/DbContext) | | 不朽 | [《读写分离》](https://github.com/2881099/FreeSql/wiki/%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb) \| [《分区分表》](https://github.com/2881099/FreeSql/wiki/%e5%88%86%e5%8c%ba%e5%88%86%e8%a1%a8) \| [《租户》](https://github.com/2881099/FreeSql/wiki/%e7%a7%9f%e6%88%b7) \| [更新日志](https://github.com/2881099/FreeSql/wiki/%e6%9b%b4%e6%96%b0%e6%97%a5%e5%bf%97) | -# 快速开始 +# Quick start +> dotnet add package FreeSql ```csharp -var connstr = "Data Source=127.0.0.1;User ID=root;Password=root;" + - "Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10"; - IFreeSql fsql = new FreeSql.FreeSqlBuilder() - .UseConnectionString(FreeSql.DataType.MySql, connstr) - .UseSlave("connectionString1", "connectionString2") - //读写分离,使用从数据库,支持多个 - - .UseMonitorCommand( - cmd => Console.WriteLine(cmd.CommandText), - //监听SQL命令对象,在执行前 - (cmd, traceLog) => Console.WriteLine(traceLog)) - //监听SQL命令对象,在执行后 - - .UseLogger(null) - //使用日志,不指定默认输出控制台 ILogger - .UseCache(null) - //使用缓存,不指定默认使用内存 IDistributedCache - - .UseAutoSyncStructure(true) - //自动同步实体结构到数据库 - .UseSyncStructureToLower(true) - //转小写同步结构 - .UseSyncStructureToUpper(true) - //转大写同步结构 - .UseConfigEntityFromDbFirst(true) - //若无配置实体类主键、自增,可从数据库导入 - .UseNoneCommandParameter(true) - //不使用命令参数化执行,针对 Insert/Update,也可临时使用 IInsert/IUpdate.NoneParameter() - - .UseLazyLoading(true) - //延时加载导航属性对象,导航属性需要声明 virtual + .UseConnectionString(FreeSql.DataType.MySql, "connectionString") + .UseAutoSyncStructure(true) //自动同步实体结构到数据库 .Build(); -``` -# 实体 - -FreeSql 使用模型执行数据访问,模型由实体类表示数据库表或视图,用于查询和保存数据。 - -可从现有数据库生成实体模型,提供 IDbFirst 生成实体模型。 - -或者手动创建模型,基于模型创建或修改数据库,提供 ICodeFirst 同步结构的 API(甚至可以做到开发阶段自动同步)。 - -```csharp class Song { [Column(IsIdentity = true)] public int Id { get; set; } - public DateTime? Create_time { get; set; } - public bool? Is_deleted { get; set; } public string Title { get; set; } public string Url { get; set; } + public DateTime CreateTime { get; set; } public virtual ICollection Tags { get; set; } } @@ -91,113 +52,124 @@ class Song_tag { class Tag { [Column(IsIdentity = true)] public int Id { get; set; } + public string Name { get; set; } + public int? Parent_id { get; set; } public virtual Tag Parent { get; set; } - public decimal? Ddd { get; set; } - public string Name { get; set; } - public virtual ICollection Songs { get; set; } public virtual ICollection Tags { get; set; } } ``` -# 查询 - +# Query ```csharp //OneToOne、ManyToOne -var t0 = fsql.Select().Where(a => a.Parent.Parent.Name == "粤语").ToSql(); -//SELECT a.`Id`, a.`Parent_id`, a__Parent.`Id` as3, a__Parent.`Parent_id` as4, a__Parent.`Ddd`, a__Parent.`Name`, a.`Ddd` as7, a.`Name` as8 -//FROM `Tag` a -//LEFT JOIN `Tag` a__Parent ON a__Parent.`Id` = a.`Parent_id` -//LEFT JOIN `Tag` a__Parent__Parent ON a__Parent__Parent.`Id` = a__Parent.`Parent_id` -//WHERE (a__Parent__Parent.`Name` = '粤语') +var t0 = fsql.Select().Where(a => a.Parent.Parent.Name == "粤语").ToList(); //OneToMany -var t1 = fsql.Select().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToSql(); -//SELECT a.`Id`, a.`Parent_id`, a.`Ddd`, a.`Name` -//FROM `Tag` a -//WHERE (exists(SELECT 1 -// FROM `Tag` t -// LEFT JOIN `Tag` t__Parent ON t__Parent.`Id` = t.`Parent_id` -// WHERE (t__Parent.`Id` = 10) AND (t.`Parent_id` = a.`Id`) -// limit 0,1)) +var t1 = fsql.Select().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToList(); //ManyToMany -var t2 = fsql.Select().Where(s => s.Tags.AsSelect().Any(t => t.Name == "国语")).ToSql(); -//SELECT a.`Id`, a.`Create_time`, a.`Is_deleted`, a.`Title`, a.`Url` -//FROM `Song` a -//WHERE(exists(SELECT 1 -// FROM `Song_tag` Mt_Ms -// WHERE(Mt_Ms.`Song_id` = a.`Id`) AND(exists(SELECT 1 -// FROM `Tag` t -// WHERE(t.`Name` = '国语') AND(t.`Id` = Mt_Ms.`Tag_id`) -// limit 0, 1)) -// limit 0, 1)) +var t2 = fsql.Select().Where(s => s.Tags.AsSelect().Any(t => t.Name == "国语")).ToList(); ``` -更多前往wiki:[《Select查询数据文档》](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2) +更多前往Wiki:[《Select 查询数据文档》](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2) -# 表达式函数 +# Lambda +```csharp +var t3 = f.Select.Where(a => new[] { 1, 2, 3 }.Contains(a.Id)).ToList(); +``` ```csharp -var t1 = select.Where(a => new[] { 1, 2, 3 }.Contains(a.testFieldInt)).ToSql(); -//SELECT a.`Id`, a.`Clicks`, a.`TestTypeInfoGuid`, a.`Title`, a.`CreateTime` -//FROM `Song` a -//WHERE (a.`Id` in (1,2,3)) +var t4 = select.Where(a => a.CreateTime.Date == DateTime.Now.Date).ToList(); ``` -查找今天创建的数据 - ```csharp -var t2 = select.Where(a => a.CreateTime.Date == DateTime.Now.Date).ToSql(); +var t5 = select.OrderBy(a => Guid.NewGuid()).Limit(1).ToList(); ``` +更多前往Wiki:[《表达式函数》](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0) -SqlServer 下随机获取记录 +# Repository & UnitOfWork +> dotnet add package FreeSql.Repository ```csharp -var t3 = select.OrderBy(a => Guid.NewGuid()).Limit(1).ToSql(); -//SELECT top 1 ... -//FROM [Song] a -//ORDER BY newid() +using (var unitOfWork = fsql.CreateUnitOfWork()) { + + var songRepository = uow.GetRepository(); + var tagRepository = uow.GetRepository(); + + await songRepository.InsertAsync(new Song()); + await tagRepository.InsertAsync(new Tag()); + + uow.Commit(); +} ``` -更多前往wiki:[《Expression 表达式函数文档》](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0) - -# 返回数据 +# DbContext & DbSet +> dotnet add package FreeSql.DbContext ```csharp -List t1 = fsql.Select().Where(a => a.Id > 0).ToList(); +public class SongContext : DbContext { -//返回普通字段 + 导航对象 Type 的数据 -List t2 = fsql.Select().LeftJoin(a => a.Type.Id == a.TypeId).ToList(); + public DbSet Songs { get; set; } + public DbSet Tags { get; set; } -//返回一个字段 -List t3 = fsql.Select().Where(a => a.Id > 0).ToList(a => a.Id); + protected override void OnConfiguring(DbContextOptionsBuilder builder) { + builder.UseFreeSql(fsql); + } +} -//返回匿名类型 -List<匿名类型> t4 = fsql.Select().Where(a => a.Id > 0).ToList(a => new { a.Id, a.Title }); +long id = 0; +using (var ctx = new SongContext()) { + var song = new Song { }; + await ctx.Songs.AddAsync(song); + id = song.Id; -//返回元组 -List<(int, string)> t5 = fsql.Select().Where(a => a.Id > 0).ToList<(int, string)>("id, title"); + var adds = Enumerable.Range(0, 100).Select(a => new Song { Title = "xxxx" + a, Url = "url222" }).ToList(); + await ctx.Songs.AddRangeAsync(adds); -//返回SQL字段 -List<匿名类> t4 = select.Where(a => a.Id > 0).Skip(100).Limit(200) - .ToList(a => new { - a.Id, a.Title, - cstitle = "substr(a.title, 0, 2)", //将 substr(a.title, 0, 2) 作为查询字段 - csnow = Convert.ToDateTime("now()"), //将 now() 作为查询字段 - //奇思妙想:怎么查询开窗函数的结果 - }); + for (var a = 0; a < adds.Count; a++) + adds[a].Title = "dkdkdkdk" + a; + + ctx.Songs.UpdateRange(adds); + ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList()); + ctx.Songs.Update(adds.Last()); + + await ctx.SaveChangesAsync(); +} ``` -执行SQL返回数据 + +# DataFilter & Tenant + ```csharp -List t6 = fsql.Ado.Query("select * from song"); -List<(int, string ,string)> t7 = fsql.Ado.Query<(int, string, string)>("select id,title,url from song"); -List t8 = fsql.Ado.Query("select * from song"); -``` -更多前往wiki:[《Select查询数据》](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2) +public IServiceProvider ConfigureServices(IServiceCollection services) { + services.AddSingleton(fsql); + services.AddMvc(); -# 性能测试 + var builder = new ContainerBuilder(); + + builder.RegisterFreeRepository(filter => filter + .Apply("SoftDelete", a => a.IsDeleted == false) + .Apply("Tenant", a => a.TenantId == 1) + ); + + builder.Populate(services); + var container = builder.Build(); + return new AutofacServiceProvider(container); +} +``` + +Temporary disable: +```csharp +var songRepository = fsql.GetRepository(); + +using (songRepository.DataFilter.Disable("Tenant")) { + //Tenant Invalid +} +//Tenant restore +``` + +# Performance FreeSql Query & Dapper Query ```shell @@ -223,11 +195,9 @@ Elapsed: 00:00:00.6707125; ToList Entity Counts: 131072; ORM: FreeSql* Elapsed: 00:00:00.6495301; Query Entity Counts: 131072; ORM: Dapper ``` -测试方法:运行两次,以第二次性能报告,避免了首个运行慢不公平的情况。[查看测试代码](FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs) +[Test code](FreeSql.Tests.PerformanceTests/MySqlAdoTest.cs) -FreeSql 目前使用的ExpressionTree+缓存,因为支持更为复杂的数据类型,所以比 Dapper Emit 慢少许。 - -# 贡献者名单 +# Contributors [systemhejiyong](https://github.com/systemhejiyong) [LambertW](https://github.com/LambertW)