mirror of
				https://github.com/nsnail/FreeSql.git
				synced 2025-11-04 09:15:27 +08:00 
			
		
		
		
	initial commit
This commit is contained in:
		
							
								
								
									
										335
									
								
								FreeSql.DbContext/DbContext/DbContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										335
									
								
								FreeSql.DbContext/DbContext/DbContext.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,335 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    public abstract partial class DbContext : IDisposable
 | 
			
		||||
    {
 | 
			
		||||
        internal DbContextScopedFreeSql _ormScoped;
 | 
			
		||||
        internal IFreeSql OrmOriginal => _ormScoped?._originalFsql ?? throw new ArgumentNullException(DbContextStrings.ConfigureUseFreeSql);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 该对象 Select/Delete/Insert/Update/InsertOrUpdate 与 DbContext 事务保持一致,可省略传递 WithTransaction
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public IFreeSql Orm => _ormScoped ?? throw new ArgumentNullException(DbContextStrings.ConfigureUseFreeSql);
 | 
			
		||||
 | 
			
		||||
        #region Property UnitOfWork
 | 
			
		||||
        internal bool _isUseUnitOfWork = true; //是否创建工作单元事务
 | 
			
		||||
        IUnitOfWork _uowPriv;
 | 
			
		||||
        public IUnitOfWork UnitOfWork
 | 
			
		||||
        {
 | 
			
		||||
            set => _uowPriv = value;
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                if (_uowPriv != null) return _uowPriv;
 | 
			
		||||
                if (_isUseUnitOfWork == false) return null;
 | 
			
		||||
                return _uowPriv = new UnitOfWork(OrmOriginal);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Property Options
 | 
			
		||||
        internal DbContextOptions _optionsPriv;
 | 
			
		||||
        public DbContextOptions Options
 | 
			
		||||
        {
 | 
			
		||||
            set => _optionsPriv = value;
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                if (_optionsPriv == null)
 | 
			
		||||
                {
 | 
			
		||||
                    _optionsPriv = new DbContextOptions();
 | 
			
		||||
                    if (FreeSqlDbContextExtensions._dicSetDbContextOptions.TryGetValue(OrmOriginal.Ado.Identifier, out var opt))
 | 
			
		||||
                    {
 | 
			
		||||
                        _optionsPriv.EnableCascadeSave = opt.EnableCascadeSave;
 | 
			
		||||
                        _optionsPriv.EnableGlobalFilter = opt.EnableGlobalFilter;
 | 
			
		||||
                        _optionsPriv.NoneParameter = opt.NoneParameter;
 | 
			
		||||
                        _optionsPriv.OnEntityChange = opt.OnEntityChange;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return _optionsPriv;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        internal void EmitOnEntityChange(List<EntityChangeReport.ChangeInfo> report)
 | 
			
		||||
        {
 | 
			
		||||
            var oec = UnitOfWork?.EntityChangeReport?.OnChange ?? Options.OnEntityChange;
 | 
			
		||||
            if (oec == null || report == null || report.Any() == false) return;
 | 
			
		||||
            oec(report);
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        protected DbContext() : this(null, null) { }
 | 
			
		||||
        protected DbContext(IFreeSql fsql, DbContextOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            _ormScoped = DbContextScopedFreeSql.Create(fsql, () => this, () => UnitOfWork);
 | 
			
		||||
            _optionsPriv = options;
 | 
			
		||||
 | 
			
		||||
            if (_ormScoped == null)
 | 
			
		||||
            {
 | 
			
		||||
                var builder = new DbContextOptionsBuilder();
 | 
			
		||||
                OnConfiguring(builder);
 | 
			
		||||
                _ormScoped = DbContextScopedFreeSql.Create(builder._fsql, () => this, () => UnitOfWork);
 | 
			
		||||
                _optionsPriv = builder._options;
 | 
			
		||||
            }
 | 
			
		||||
            if (_ormScoped != null) InitPropSets();
 | 
			
		||||
        }
 | 
			
		||||
        protected virtual void OnConfiguring(DbContextOptionsBuilder options) { }
 | 
			
		||||
        protected virtual void OnModelCreating(ICodeFirst codefirst) { }
 | 
			
		||||
 | 
			
		||||
        #region Set
 | 
			
		||||
        static ConcurrentDictionary<Type, NativeTuple<PropertyInfo[], bool>> _dicGetDbSetProps = new ConcurrentDictionary<Type, NativeTuple<PropertyInfo[], bool>>();
 | 
			
		||||
        static object _lockOnModelCreating = new object();
 | 
			
		||||
        internal void InitPropSets()
 | 
			
		||||
        {
 | 
			
		||||
            var thisType = this.GetType();
 | 
			
		||||
            var isOnModelCreating = false;
 | 
			
		||||
            if (_dicGetDbSetProps.TryGetValue(thisType, out var dicval) == false)
 | 
			
		||||
            {
 | 
			
		||||
                lock (_lockOnModelCreating)
 | 
			
		||||
                {
 | 
			
		||||
                    if (_dicGetDbSetProps.TryGetValue(thisType, out dicval) == false)
 | 
			
		||||
                    {
 | 
			
		||||
                        dicval = NativeTuple.Create(
 | 
			
		||||
                            thisType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
 | 
			
		||||
                                .Where(a => a.PropertyType.IsGenericType && 
 | 
			
		||||
                                    a.PropertyType == typeof(DbSet<>).MakeGenericType(a.PropertyType.GetGenericArguments()[0])).ToArray(),
 | 
			
		||||
                            false);
 | 
			
		||||
                        _dicGetDbSetProps.TryAdd(thisType, dicval);
 | 
			
		||||
                        isOnModelCreating = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (isOnModelCreating)
 | 
			
		||||
                OnModelCreating(OrmOriginal.CodeFirst);
 | 
			
		||||
 | 
			
		||||
            foreach (var prop in dicval.Item1)
 | 
			
		||||
            {
 | 
			
		||||
                var set = this.Set(prop.PropertyType.GetGenericArguments()[0]);
 | 
			
		||||
 | 
			
		||||
                prop.SetValue(this, set, null);
 | 
			
		||||
                AllSets.Add(prop.Name, set);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected List<IDbSet> _listSet = new List<IDbSet>();
 | 
			
		||||
        protected Dictionary<Type, IDbSet> _dicSet = new Dictionary<Type, IDbSet>();
 | 
			
		||||
        internal Dictionary<Type, IDbSet> InternalDicSet => _dicSet;
 | 
			
		||||
        public DbSet<TEntity> Set<TEntity>() where TEntity : class => this.Set(typeof(TEntity)) as DbSet<TEntity>;
 | 
			
		||||
        public virtual IDbSet Set(Type entityType)
 | 
			
		||||
        {
 | 
			
		||||
            if (_dicSet.ContainsKey(entityType)) return _dicSet[entityType];
 | 
			
		||||
            var sd = Activator.CreateInstance(typeof(DbContextDbSet<>).MakeGenericType(entityType), this) as IDbSet;
 | 
			
		||||
            _listSet.Add(sd);
 | 
			
		||||
            if (entityType != typeof(object)) _dicSet.Add(entityType, sd);
 | 
			
		||||
            return sd;
 | 
			
		||||
        }
 | 
			
		||||
        protected Dictionary<string, IDbSet> AllSets { get; } = new Dictionary<string, IDbSet>();
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region DbSet 快速代理
 | 
			
		||||
        void CheckEntityTypeOrThrow(Type entityType)
 | 
			
		||||
        {
 | 
			
		||||
            if (OrmOriginal.CodeFirst.GetTableByEntity(entityType) == null)
 | 
			
		||||
                throw new ArgumentException(DbContextStrings.ParameterDataTypeError(entityType.FullName));
 | 
			
		||||
        }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 添加
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="TEntity"></typeparam>
 | 
			
		||||
        /// <param name="data"></param>
 | 
			
		||||
        public void Add<TEntity>(TEntity data) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            this.Set<TEntity>().Add(data);
 | 
			
		||||
        }
 | 
			
		||||
        public void AddRange<TEntity>(IEnumerable<TEntity> data) where TEntity : class => this.Set<TEntity>().AddRange(data);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 更新
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="TEntity"></typeparam>
 | 
			
		||||
        /// <param name="data"></param>
 | 
			
		||||
        public void Update<TEntity>(TEntity data) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            this.Set<TEntity>().Update(data);
 | 
			
		||||
        }
 | 
			
		||||
        public void UpdateRange<TEntity>(IEnumerable<TEntity> data) where TEntity : class => this.Set<TEntity>().UpdateRange(data);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 删除
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="TEntity"></typeparam>
 | 
			
		||||
        /// <param name="data"></param>
 | 
			
		||||
        public void Remove<TEntity>(TEntity data) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            this.Set<TEntity>().Remove(data);
 | 
			
		||||
        }
 | 
			
		||||
        public void RemoveRange<TEntity>(IEnumerable<TEntity> data) where TEntity : class => this.Set<TEntity>().RemoveRange(data);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 添加或更新
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="TEntity"></typeparam>
 | 
			
		||||
        /// <param name="data"></param>
 | 
			
		||||
        public void AddOrUpdate<TEntity>(TEntity data) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            this.Set<TEntity>().AddOrUpdate(data);
 | 
			
		||||
        }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
 | 
			
		||||
        /// 场景:在关闭级联保存功能之后,手工使用本方法<para></para>
 | 
			
		||||
        /// 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus")<para></para>
 | 
			
		||||
        /// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
 | 
			
		||||
        /// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="data">实体对象</param>
 | 
			
		||||
        /// <param name="propertyName">属性名</param>
 | 
			
		||||
        public void SaveMany<TEntity>(TEntity data, string propertyName) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            this.Set<TEntity>().SaveMany(data, propertyName);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 附加实体,可用于不查询就更新或删除
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="TEntity"></typeparam>
 | 
			
		||||
        /// <param name="data"></param>
 | 
			
		||||
        public void Attach<TEntity>(TEntity data) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            this.Set<TEntity>().Attach(data);
 | 
			
		||||
        }
 | 
			
		||||
        public void AttachRange<TEntity>(IEnumerable<TEntity> data) where TEntity : class => this.Set<TEntity>().AttachRange(data);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 附加实体,并且只附加主键值,可用于不更新属性值为null或默认值的字段
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <typeparam name="TEntity"></typeparam>
 | 
			
		||||
        /// <param name="data"></param>
 | 
			
		||||
        public DbContext AttachOnlyPrimary<TEntity>(TEntity data) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            this.Set<TEntity>().AttachOnlyPrimary(data);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 比较实体,计算出值发生变化的属性,以及属性变化的前后值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="newdata">最新的实体对象,它将与附加实体的状态对比</param>
 | 
			
		||||
        /// <returns>key: 属性名, value: [旧值, 新值]</returns>
 | 
			
		||||
        public Dictionary<string, object[]> CompareState<TEntity>(TEntity newdata) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            return this.Set<TEntity>().CompareState(newdata);
 | 
			
		||||
        }
 | 
			
		||||
#if net40
 | 
			
		||||
#else
 | 
			
		||||
        public Task AddAsync<TEntity>(TEntity data, CancellationToken cancellationToken = default) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            return this.Set<TEntity>().AddAsync(data, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        public Task AddRangeAsync<TEntity>(IEnumerable<TEntity> data, CancellationToken cancellationToken = default) where TEntity : class => this.Set<TEntity>().AddRangeAsync(data, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        public Task UpdateAsync<TEntity>(TEntity data, CancellationToken cancellationToken = default) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            return this.Set<TEntity>().UpdateAsync(data, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        public Task UpdateRangeAsync<TEntity>(IEnumerable<TEntity> data, CancellationToken cancellationToken = default) where TEntity : class => this.Set<TEntity>().UpdateRangeAsync(data, cancellationToken);
 | 
			
		||||
 | 
			
		||||
        public Task AddOrUpdateAsync<TEntity>(TEntity data, CancellationToken cancellationToken = default) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            return this.Set<TEntity>().AddOrUpdateAsync(data, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
        public Task SaveManyAsync<TEntity>(TEntity data, string propertyName, CancellationToken cancellationToken = default) where TEntity : class
 | 
			
		||||
        {
 | 
			
		||||
            CheckEntityTypeOrThrow(typeof(TEntity));
 | 
			
		||||
            return this.Set<TEntity>().SaveManyAsync(data, propertyName, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Queue PreCommand
 | 
			
		||||
        public class EntityChangeReport
 | 
			
		||||
        {
 | 
			
		||||
            public class ChangeInfo
 | 
			
		||||
            {
 | 
			
		||||
                public object Object { get; set; }
 | 
			
		||||
                /// <summary>
 | 
			
		||||
                /// Type = Update 的时候,获取更新之前的对象
 | 
			
		||||
                /// </summary>
 | 
			
		||||
                public object BeforeObject { get; set; }
 | 
			
		||||
                public EntityChangeType Type { get; set; }
 | 
			
		||||
                /// <summary>
 | 
			
		||||
                /// 实体类型
 | 
			
		||||
                /// </summary>
 | 
			
		||||
                public Type EntityType { get; set; }
 | 
			
		||||
            }
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            /// 实体变化记录
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            public List<ChangeInfo> Report { get; } = new List<ChangeInfo>();
 | 
			
		||||
            /// <summary>
 | 
			
		||||
            /// 实体变化事件
 | 
			
		||||
            /// </summary>
 | 
			
		||||
            public Action<List<ChangeInfo>> OnChange { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
        internal List<EntityChangeReport.ChangeInfo> _entityChangeReport = new List<EntityChangeReport.ChangeInfo>();
 | 
			
		||||
        public enum EntityChangeType { Insert, Update, Delete, SqlRaw }
 | 
			
		||||
        internal class PrevCommandInfo
 | 
			
		||||
        {
 | 
			
		||||
            public EntityChangeType changeType { get; set; }
 | 
			
		||||
            public IDbSet dbSet { get; set; }
 | 
			
		||||
            public Type stateType { get; set; }
 | 
			
		||||
            public Type entityType { get; set; }
 | 
			
		||||
            public object state { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
        Queue<PrevCommandInfo> _prevCommands = new Queue<PrevCommandInfo>();
 | 
			
		||||
        internal int _affrows = 0;
 | 
			
		||||
 | 
			
		||||
        internal void EnqueuePreCommand(EntityChangeType changeType, IDbSet dbSet, Type stateType, Type entityType, object state) =>
 | 
			
		||||
            _prevCommands.Enqueue(new PrevCommandInfo { changeType = changeType, dbSet = dbSet, stateType = stateType, entityType = entityType, state = state });
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        ~DbContext() => this.Dispose();
 | 
			
		||||
        int _disposeCounter;
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            if (Interlocked.Increment(ref _disposeCounter) != 1) return;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                _prevCommands.Clear();
 | 
			
		||||
 | 
			
		||||
                foreach (var set in _listSet)
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        set.Dispose();
 | 
			
		||||
                    }
 | 
			
		||||
                    catch { }
 | 
			
		||||
 | 
			
		||||
                _listSet.Clear();
 | 
			
		||||
                _dicSet.Clear();
 | 
			
		||||
                AllSets.Clear();
 | 
			
		||||
 | 
			
		||||
                if (_isUseUnitOfWork)
 | 
			
		||||
                    UnitOfWork?.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                GC.SuppressFinalize(this);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										162
									
								
								FreeSql.DbContext/DbContext/DbContextAsync.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								FreeSql.DbContext/DbContext/DbContextAsync.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
#if net40
 | 
			
		||||
#else
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    partial class DbContext
 | 
			
		||||
    {
 | 
			
		||||
        public virtual async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            await FlushCommandAsync(cancellationToken);
 | 
			
		||||
            return SaveChangesSuccess();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static ConcurrentDictionary<Type, ConcurrentDictionary<string, Func<object, object[], CancellationToken, Task<int>>>> _dicFlushCommandDbSetBatchAsync = new ConcurrentDictionary<Type, ConcurrentDictionary<string, Func<object, object[], CancellationToken, Task<int>>>>();
 | 
			
		||||
        internal async Task FlushCommandAsync(CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            if (isFlushCommanding) return;
 | 
			
		||||
            if (_prevCommands.Any() == false) return;
 | 
			
		||||
            isFlushCommanding = true;
 | 
			
		||||
 | 
			
		||||
            PrevCommandInfo oldinfo = null;
 | 
			
		||||
            var states = new List<object>();
 | 
			
		||||
            var flagFuncUpdateLaststate = false;
 | 
			
		||||
 | 
			
		||||
            Task<int> dbsetBatch(string method)
 | 
			
		||||
            {
 | 
			
		||||
                var tryfunc = _dicFlushCommandDbSetBatchAsync
 | 
			
		||||
                    .GetOrAdd(oldinfo.stateType, stateType => new ConcurrentDictionary<string, Func<object, object[], CancellationToken, Task<int>>>())
 | 
			
		||||
                    .GetOrAdd(method, methodName =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var arrType = oldinfo.stateType.MakeArrayType();
 | 
			
		||||
                        var dbsetType = oldinfo.dbSet.GetType().BaseType;
 | 
			
		||||
                        var dbsetTypeMethod = dbsetType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType, typeof(CancellationToken) }, null);
 | 
			
		||||
 | 
			
		||||
                        var returnTarget = Expression.Label(typeof(Task<int>));
 | 
			
		||||
                        var parm1DbSet = Expression.Parameter(typeof(object));
 | 
			
		||||
                        var parm2Vals = Expression.Parameter(typeof(object[]));
 | 
			
		||||
                        var parm3CancelToken = Expression.Parameter(typeof(CancellationToken));
 | 
			
		||||
                        var var1Vals = Expression.Variable(arrType);
 | 
			
		||||
                        return Expression.Lambda<Func<object, object[], CancellationToken, Task<int>>>(Expression.Block(
 | 
			
		||||
                            new[] { var1Vals },
 | 
			
		||||
                            Expression.Assign(var1Vals, Expression.Convert(global::FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(arrType, parm2Vals), arrType)),
 | 
			
		||||
                            Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals, parm3CancelToken)),
 | 
			
		||||
                            Expression.Label(returnTarget, Expression.Default(typeof(Task<int>)))
 | 
			
		||||
                        ), new[] { parm1DbSet, parm2Vals, parm3CancelToken }).Compile();
 | 
			
		||||
                    });
 | 
			
		||||
                return tryfunc(oldinfo.dbSet, states.ToArray(), cancellationToken);
 | 
			
		||||
            }
 | 
			
		||||
            async Task funcDelete()
 | 
			
		||||
            {
 | 
			
		||||
                _affrows += await dbsetBatch("DbContextBatchRemoveAsync");
 | 
			
		||||
                states.Clear();
 | 
			
		||||
            }
 | 
			
		||||
            async Task funcInsert()
 | 
			
		||||
            {
 | 
			
		||||
                _affrows += await dbsetBatch("DbContextBatchAddAsync");
 | 
			
		||||
                states.Clear();
 | 
			
		||||
            };
 | 
			
		||||
            async Task funcUpdate(bool isLiveUpdate)
 | 
			
		||||
            {
 | 
			
		||||
                var affrows = 0;
 | 
			
		||||
                if (isLiveUpdate) affrows = await dbsetBatch("DbContextBatchUpdateNowAsync");
 | 
			
		||||
                else affrows = await dbsetBatch("DbContextBatchUpdateAsync");
 | 
			
		||||
                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)
 | 
			
		||||
                    {
 | 
			
		||||
                        flagFuncUpdateLaststate = true;
 | 
			
		||||
                        states.Add(laststate); //保留最后一个
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (affrows > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    _affrows += affrows;
 | 
			
		||||
                    var islastNotUpdated = states.Count != affrows;
 | 
			
		||||
                    var laststate = states[states.Count - 1];
 | 
			
		||||
                    states.Clear();
 | 
			
		||||
                    if (islastNotUpdated)
 | 
			
		||||
                    {
 | 
			
		||||
                        flagFuncUpdateLaststate = true;
 | 
			
		||||
                        states.Add(laststate); //保留最后一个
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                while (_prevCommands.Any() || states.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    var info = _prevCommands.Any() ? _prevCommands.Dequeue() : null;
 | 
			
		||||
                    if (oldinfo == null) oldinfo = info;
 | 
			
		||||
                    var isLiveUpdate = false;
 | 
			
		||||
                    flagFuncUpdateLaststate = false;
 | 
			
		||||
 | 
			
		||||
                    if (_prevCommands.Any() == false && states.Any() ||
 | 
			
		||||
                        info != null && oldinfo.changeType != info.changeType ||
 | 
			
		||||
                        info != null && oldinfo.stateType != info.stateType ||
 | 
			
		||||
                        info != null && oldinfo.entityType != info.entityType)
 | 
			
		||||
                    {
 | 
			
		||||
 | 
			
		||||
                        if (info != null && oldinfo.changeType == info.changeType && oldinfo.stateType == info.stateType && oldinfo.entityType == info.entityType)
 | 
			
		||||
                        {
 | 
			
		||||
                            //最后一个,合起来发送
 | 
			
		||||
                            states.Add(info.state);
 | 
			
		||||
                            info = null;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        switch (oldinfo.changeType)
 | 
			
		||||
                        {
 | 
			
		||||
                            case EntityChangeType.Insert:
 | 
			
		||||
                                await funcInsert();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case EntityChangeType.Delete:
 | 
			
		||||
                                await funcDelete();
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
                        isLiveUpdate = true;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (isLiveUpdate || oldinfo.changeType == EntityChangeType.Update)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (states.Any())
 | 
			
		||||
                        {
 | 
			
		||||
                            await funcUpdate(isLiveUpdate);
 | 
			
		||||
                            if (info?.changeType == EntityChangeType.Update)
 | 
			
		||||
                                flagFuncUpdateLaststate = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (info != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        states.Add(info.state);
 | 
			
		||||
                        oldinfo = info;
 | 
			
		||||
 | 
			
		||||
                        if (flagFuncUpdateLaststate && oldinfo.changeType == EntityChangeType.Update) //马上与上个元素比较
 | 
			
		||||
                            await funcUpdate(isLiveUpdate);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                isFlushCommanding = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										80
									
								
								FreeSql.DbContext/DbContext/DbContextOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								FreeSql.DbContext/DbContext/DbContextOptions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    public class DbContextOptions
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 是否开启 一对一(OneToOne)、一对多(OneToMany)、多对多(ManyToMany) 级联保存功能<para></para>
 | 
			
		||||
        /// <para></para>
 | 
			
		||||
        /// 【一对一】模型下,保存时级联保存 OneToOne 属性。
 | 
			
		||||
        /// <para></para>
 | 
			
		||||
        /// 【一对多】模型下,保存时级联保存 OneToMany 集合属性。出于安全考虑我们没做完整对比,只针对实体属性集合的添加或更新操作,因此不会删除数据库表已有的数据。<para></para>
 | 
			
		||||
        /// 完整对比的功能使用起来太危险,试想下面的场景:<para></para>
 | 
			
		||||
        /// - 保存的时候,实体的属性集合为空时(!=null),表记录全部删除?<para></para>
 | 
			
		||||
        /// - 保存的时候,由于数据库子表的记录很多,只想保存子表的部分数据,又或者只需要添加,如何操作?
 | 
			
		||||
        /// <para></para>
 | 
			
		||||
        /// 【多对多】模型下,对中间表的保存是完整对比操作,对外部实体的只作新增操作(*注意不会更新)<para></para>
 | 
			
		||||
        /// - 属性集合为空时(!=null),删除他们的所有关联数据(中间表)<para></para>
 | 
			
		||||
        /// - 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool EnableCascadeSave { get; set; } = false;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 因增加支持 OneToOne 级联保存,和基于内存的级联删除,已改名为 EnableCascadeSave
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [Obsolete("因增加支持 OneToOne 级联保存,和基于内存的级联删除,已改名为 EnableCascadeSave")]
 | 
			
		||||
        public bool EnableAddOrUpdateNavigateList
 | 
			
		||||
        {
 | 
			
		||||
            get => EnableCascadeSave;
 | 
			
		||||
            set => EnableCascadeSave = value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 使用无参数化设置(对应 IInsert/IUpdate)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool? NoneParameter { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 是否开启 IFreeSql GlobalFilter 功能(默认:true)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool EnableGlobalFilter { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 实体变化事件
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Action<List<DbContext.EntityChangeReport.ChangeInfo>> OnEntityChange { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// DbContext/Repository 审计值,适合 Scoped IOC 中获取登陆信息
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Action<DbContextAuditValueEventArgs> AuditValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class DbContextAuditValueEventArgs : EventArgs
 | 
			
		||||
    {
 | 
			
		||||
        public DbContextAuditValueEventArgs(Aop.AuditValueType auditValueType, Type entityType, object obj)
 | 
			
		||||
        {
 | 
			
		||||
            this.AuditValueType = auditValueType;
 | 
			
		||||
            this.EntityType = entityType;
 | 
			
		||||
            this.Object = obj;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 类型
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Aop.AuditValueType AuditValueType { get; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 类型
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Type EntityType { get; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 实体对象
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public object Object { get; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    public class DbContextOptionsBuilder
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        internal IFreeSql _fsql;
 | 
			
		||||
        internal DbContextOptions _options;
 | 
			
		||||
 | 
			
		||||
        public DbContextOptionsBuilder UseFreeSql(IFreeSql orm)
 | 
			
		||||
        {
 | 
			
		||||
            _fsql = orm;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        public DbContextOptionsBuilder UseOptions(DbContextOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            _options = options;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								FreeSql.DbContext/DbContext/DbContextScopedFreeSql.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								FreeSql.DbContext/DbContext/DbContextScopedFreeSql.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
using FreeSql;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.CommonProvider;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using FreeSql.Internal.ObjectPool;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Data;
 | 
			
		||||
using System.Data.Common;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    class DbContextScopedFreeSql : IFreeSql
 | 
			
		||||
    {
 | 
			
		||||
        public IFreeSql _originalFsql;
 | 
			
		||||
        Func<DbContext> _resolveDbContext;
 | 
			
		||||
        Func<IUnitOfWork> _resolveUnitOfWork;
 | 
			
		||||
        DbContextScopedFreeSql() { }
 | 
			
		||||
 | 
			
		||||
        public static DbContextScopedFreeSql Create(IFreeSql fsql, Func<DbContext> resolveDbContext, Func<IUnitOfWork> resolveUnitOfWork)
 | 
			
		||||
        {
 | 
			
		||||
            if (fsql == null) return null;
 | 
			
		||||
            var scopedfsql = fsql as DbContextScopedFreeSql;
 | 
			
		||||
            if (scopedfsql == null)
 | 
			
		||||
                return new DbContextScopedFreeSql
 | 
			
		||||
                {
 | 
			
		||||
                    _originalFsql = fsql,
 | 
			
		||||
                    _resolveDbContext = resolveDbContext,
 | 
			
		||||
                    _resolveUnitOfWork = resolveUnitOfWork,
 | 
			
		||||
                    Ado = new ScopeTransactionAdo(fsql.Ado as AdoProvider, () =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var db = resolveDbContext?.Invoke();
 | 
			
		||||
                        db?.FlushCommand();
 | 
			
		||||
                        return resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction();
 | 
			
		||||
                    })
 | 
			
		||||
                };
 | 
			
		||||
            return Create(scopedfsql._originalFsql, resolveDbContext, resolveUnitOfWork);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        class ScopeTransactionAdo : AdoProvider
 | 
			
		||||
        {
 | 
			
		||||
            AdoProvider _ado;
 | 
			
		||||
            public ScopeTransactionAdo(AdoProvider ado, Func<DbTransaction> resolveTran) : base(ado.DataType, null, null)
 | 
			
		||||
            {
 | 
			
		||||
                _ado = ado;
 | 
			
		||||
                base.ResolveTransaction = resolveTran;
 | 
			
		||||
                base.ConnectionString = ado.ConnectionString;
 | 
			
		||||
                base.SlaveConnectionStrings = ado.SlaveConnectionStrings;
 | 
			
		||||
                base.Identifier = ado.Identifier;
 | 
			
		||||
                base.MasterPool = ado.MasterPool;
 | 
			
		||||
                base._util = ado._util;
 | 
			
		||||
            }
 | 
			
		||||
            public override object AddslashesProcessParam(object param, Type mapType, ColumnInfo mapColumn) => _ado.AddslashesProcessParam(param, mapType, mapColumn);
 | 
			
		||||
            public override DbCommand CreateCommand() => _ado.CreateCommand();
 | 
			
		||||
            public override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _ado.GetDbParamtersByObject(sql, obj);
 | 
			
		||||
            public override void ReturnConnection(IObjectPool<DbConnection> pool, Object<DbConnection> conn, Exception ex) => _ado.ReturnConnection(pool, conn, ex);
 | 
			
		||||
        }
 | 
			
		||||
        public IAdo Ado { get; private set; }
 | 
			
		||||
        public IAop Aop => _originalFsql.Aop;
 | 
			
		||||
        public ICodeFirst CodeFirst => _originalFsql.CodeFirst;
 | 
			
		||||
        public IDbFirst DbFirst => _originalFsql.DbFirst;
 | 
			
		||||
        public GlobalFilter GlobalFilter => _originalFsql.GlobalFilter;
 | 
			
		||||
        public void Dispose() { }
 | 
			
		||||
 | 
			
		||||
        public void Transaction(Action handler) => _originalFsql.Transaction(handler);
 | 
			
		||||
        public void Transaction(IsolationLevel isolationLevel, Action handler) => _originalFsql.Transaction(isolationLevel, handler);
 | 
			
		||||
 | 
			
		||||
        public ISelect<T1> Select<T1>() where T1 : class
 | 
			
		||||
        {
 | 
			
		||||
            var db = _resolveDbContext?.Invoke();
 | 
			
		||||
            db?.FlushCommand();
 | 
			
		||||
            var uow = _resolveUnitOfWork?.Invoke();
 | 
			
		||||
            var uowIsolationLevel = uow?.IsolationLevel ?? IsolationLevel.Unspecified;
 | 
			
		||||
            var select = _originalFsql.Select<T1>().WithTransaction(uow?.GetOrBeginTransaction(uowIsolationLevel != IsolationLevel.Unspecified));
 | 
			
		||||
            (select as Select0Provider)._resolveHookTransaction = () => uow?.GetOrBeginTransaction();
 | 
			
		||||
            if (db?.Options.EnableGlobalFilter == false) select.DisableGlobalFilter();
 | 
			
		||||
            return select;
 | 
			
		||||
        }
 | 
			
		||||
        public ISelect<T1> Select<T1>(object dywhere) where T1 : class => Select<T1>().WhereDynamic(dywhere);
 | 
			
		||||
 | 
			
		||||
        public IDelete<T1> Delete<T1>() where T1 : class
 | 
			
		||||
        {
 | 
			
		||||
            var db = _resolveDbContext?.Invoke();
 | 
			
		||||
            db?.FlushCommand();
 | 
			
		||||
            var delete = _originalFsql.Delete<T1>().WithTransaction(_resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction());
 | 
			
		||||
            if (db?.Options.EnableGlobalFilter == false) delete.DisableGlobalFilter();
 | 
			
		||||
            return delete;
 | 
			
		||||
        }
 | 
			
		||||
        public IDelete<T1> Delete<T1>(object dywhere) where T1 : class => Delete<T1>().WhereDynamic(dywhere);
 | 
			
		||||
 | 
			
		||||
        public IUpdate<T1> Update<T1>() where T1 : class
 | 
			
		||||
        {
 | 
			
		||||
            var db = _resolveDbContext?.Invoke();
 | 
			
		||||
            db?.FlushCommand();
 | 
			
		||||
            var update = _originalFsql.Update<T1>().WithTransaction(_resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction());
 | 
			
		||||
            if (db?.Options.NoneParameter != null) update.NoneParameter(db.Options.NoneParameter.Value);
 | 
			
		||||
            if (db?.Options.EnableGlobalFilter == false) update.DisableGlobalFilter();
 | 
			
		||||
            return update;
 | 
			
		||||
        }
 | 
			
		||||
        public IUpdate<T1> Update<T1>(object dywhere) where T1 : class => Update<T1>().WhereDynamic(dywhere);
 | 
			
		||||
 | 
			
		||||
        public IInsert<T1> Insert<T1>() where T1 : class
 | 
			
		||||
        {
 | 
			
		||||
            var db = _resolveDbContext?.Invoke();
 | 
			
		||||
            db?.FlushCommand();
 | 
			
		||||
            var insert = _originalFsql.Insert<T1>().WithTransaction(_resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction());
 | 
			
		||||
            if (db?.Options.NoneParameter != null) insert.NoneParameter(db.Options.NoneParameter.Value);
 | 
			
		||||
            return insert;
 | 
			
		||||
        }
 | 
			
		||||
        public IInsert<T1> Insert<T1>(T1 source) where T1 : class => Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(T1[] source) where T1 : class => Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(List<T1> source) where T1 : class => Insert<T1>().AppendData(source);
 | 
			
		||||
        public IInsert<T1> Insert<T1>(IEnumerable<T1> source) where T1 : class => Insert<T1>().AppendData(source);
 | 
			
		||||
 | 
			
		||||
        public IInsertOrUpdate<T1> InsertOrUpdate<T1>() where T1 : class
 | 
			
		||||
        {
 | 
			
		||||
            var db = _resolveDbContext?.Invoke();
 | 
			
		||||
            db?.FlushCommand();
 | 
			
		||||
            return _originalFsql.InsertOrUpdate<T1>().WithTransaction(_resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										176
									
								
								FreeSql.DbContext/DbContext/DbContextSync.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								FreeSql.DbContext/DbContext/DbContextSync.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    partial class DbContext
 | 
			
		||||
    {
 | 
			
		||||
        int SaveChangesSuccess()
 | 
			
		||||
        {
 | 
			
		||||
            UnitOfWork?.Commit();
 | 
			
		||||
            int ret;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                EmitOnEntityChange(_entityChangeReport);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                _entityChangeReport.Clear();
 | 
			
		||||
                ret = _affrows;
 | 
			
		||||
                _affrows = 0;
 | 
			
		||||
            }
 | 
			
		||||
            return ret;
 | 
			
		||||
        }
 | 
			
		||||
        public virtual int SaveChanges()
 | 
			
		||||
        {
 | 
			
		||||
            FlushCommand();
 | 
			
		||||
            return SaveChangesSuccess();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static ConcurrentDictionary<Type, ConcurrentDictionary<string, Func<object, object[], int>>> _dicFlushCommandDbSetBatch = new ConcurrentDictionary<Type, ConcurrentDictionary<string, Func<object, object[], int>>>();
 | 
			
		||||
        bool isFlushCommanding = false;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 刷新队列中的命令
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        internal void FlushCommand()
 | 
			
		||||
        {
 | 
			
		||||
            if (isFlushCommanding) return;
 | 
			
		||||
            if (_prevCommands.Any() == false) return;
 | 
			
		||||
            isFlushCommanding = true;
 | 
			
		||||
 | 
			
		||||
            PrevCommandInfo oldinfo = null;
 | 
			
		||||
            var states = new List<object>();
 | 
			
		||||
            var flagFuncUpdateLaststate = false;
 | 
			
		||||
 | 
			
		||||
            int dbsetBatch(string method)
 | 
			
		||||
            {
 | 
			
		||||
                var tryfunc = _dicFlushCommandDbSetBatch
 | 
			
		||||
                    .GetOrAdd(oldinfo.stateType, stateType => new ConcurrentDictionary<string, Func<object, object[], int>>())
 | 
			
		||||
                    .GetOrAdd(method, methodName =>
 | 
			
		||||
                    {
 | 
			
		||||
                        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<Func<object, object[], int>>(Expression.Block(
 | 
			
		||||
                            new[] { var1Vals },
 | 
			
		||||
                            Expression.Assign(var1Vals, Expression.Convert(global::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();
 | 
			
		||||
                    });
 | 
			
		||||
                return tryfunc(oldinfo.dbSet, states.ToArray());
 | 
			
		||||
            }
 | 
			
		||||
            void funcDelete()
 | 
			
		||||
            {
 | 
			
		||||
                _affrows += dbsetBatch("DbContextBatchRemove");
 | 
			
		||||
                states.Clear();
 | 
			
		||||
            }
 | 
			
		||||
            void funcInsert()
 | 
			
		||||
            {
 | 
			
		||||
                _affrows += dbsetBatch("DbContextBatchAdd");
 | 
			
		||||
                states.Clear();
 | 
			
		||||
            };
 | 
			
		||||
            void funcUpdate(bool isLiveUpdate)
 | 
			
		||||
            {
 | 
			
		||||
                var affrows = 0;
 | 
			
		||||
                if (isLiveUpdate) affrows = dbsetBatch("DbContextBatchUpdateNow");
 | 
			
		||||
                else affrows = dbsetBatch("DbContextBatchUpdate");
 | 
			
		||||
                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)
 | 
			
		||||
                    {
 | 
			
		||||
                        flagFuncUpdateLaststate = true;
 | 
			
		||||
                        states.Add(laststate); //保留最后一个
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (affrows > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    _affrows += affrows;
 | 
			
		||||
                    var islastNotUpdated = states.Count != affrows;
 | 
			
		||||
                    var laststate = states[states.Count - 1];
 | 
			
		||||
                    states.Clear();
 | 
			
		||||
                    if (islastNotUpdated)
 | 
			
		||||
                    {
 | 
			
		||||
                        flagFuncUpdateLaststate = true;
 | 
			
		||||
                        states.Add(laststate); //保留最后一个
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                while (_prevCommands.Any() || states.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    var info = _prevCommands.Any() ? _prevCommands.Dequeue() : null;
 | 
			
		||||
                    if (oldinfo == null) oldinfo = info;
 | 
			
		||||
                    var isLiveUpdate = false;
 | 
			
		||||
                    flagFuncUpdateLaststate = false;
 | 
			
		||||
 | 
			
		||||
                    if (_prevCommands.Any() == false && states.Any() ||
 | 
			
		||||
                        info != null && oldinfo.changeType != info.changeType ||
 | 
			
		||||
                        info != null && oldinfo.stateType != info.stateType ||
 | 
			
		||||
                        info != null && oldinfo.entityType != info.entityType)
 | 
			
		||||
                    {
 | 
			
		||||
 | 
			
		||||
                        if (info != null && oldinfo.changeType == info.changeType && oldinfo.stateType == info.stateType && oldinfo.entityType == info.entityType)
 | 
			
		||||
                        {
 | 
			
		||||
                            //最后一个,合起来发送
 | 
			
		||||
                            states.Add(info.state);
 | 
			
		||||
                            info = null;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        switch (oldinfo.changeType)
 | 
			
		||||
                        {
 | 
			
		||||
                            case EntityChangeType.Insert:
 | 
			
		||||
                                funcInsert();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case EntityChangeType.Delete:
 | 
			
		||||
                                funcDelete();
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
                        isLiveUpdate = true;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (isLiveUpdate || oldinfo.changeType == EntityChangeType.Update)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (states.Any())
 | 
			
		||||
                        {
 | 
			
		||||
                            funcUpdate(isLiveUpdate);
 | 
			
		||||
                            if (info?.changeType == EntityChangeType.Update)
 | 
			
		||||
                                flagFuncUpdateLaststate = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (info != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        states.Add(info.state);
 | 
			
		||||
                        oldinfo = info;
 | 
			
		||||
 | 
			
		||||
                        if (flagFuncUpdateLaststate && oldinfo.changeType == EntityChangeType.Update) //马上与上个元素比较
 | 
			
		||||
                            funcUpdate(isLiveUpdate);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                isFlushCommanding = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								FreeSql.DbContext/DbContext/FreeContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								FreeSql.DbContext/DbContext/FreeContext.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    public class FreeContext : DbContext
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public FreeContext(IFreeSql orm)
 | 
			
		||||
        {
 | 
			
		||||
            _ormScoped = DbContextScopedFreeSql.Create(orm, () => this, () => UnitOfWork);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user