mirror of
				https://github.com/nsnail/FreeSql.git
				synced 2025-11-04 09:15:27 +08:00 
			
		
		
		
	v3.2.667 #1237
This commit is contained in:
		@@ -0,0 +1,34 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.DataAnnotations
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置 AggregateRootRepository 边界范围<para></para>
 | 
			
		||||
    /// 在边界范围之内的规则 :<para></para>
 | 
			
		||||
    /// 1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改<para></para>
 | 
			
		||||
    /// 2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略)<para></para>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
 | 
			
		||||
    public class AggregateRootBoundaryAttribute : Attribute
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 边界是否终止
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool Break { get; set; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 边界是否终止向下探测
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool BreakThen { get; set; }
 | 
			
		||||
 | 
			
		||||
        public AggregateRootBoundaryAttribute(string name)
 | 
			
		||||
        {
 | 
			
		||||
            this.Name = name;
 | 
			
		||||
        }
 | 
			
		||||
        public AggregateRootBoundaryAttribute()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql.Internal.Model
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public class AggregateRootTrackingChangeInfo
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        public List<NativeTuple<Type, object>> InsertLog { get; } = new List<NativeTuple<Type, object>>();
 | 
			
		||||
 | 
			
		||||
        public List<NativeTuple<Type, object, object, List<string>>> UpdateLog { get; } = new List<NativeTuple<Type, object, object, List<string>>>();
 | 
			
		||||
 | 
			
		||||
        public List<NativeTuple<Type, object[]>> DeleteLog { get; } = new List<NativeTuple<Type, object[]>>();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,261 @@
 | 
			
		||||
using FreeSql.Extensions.EntityUtil;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    public interface IAggregateRootRepository<TEntity>: IBaseRepository<TEntity> where TEntity : class
 | 
			
		||||
    {
 | 
			
		||||
        IBaseRepository<TEntity> ChangeBoundary(string name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public partial class AggregateRootRepository<TEntity> : IAggregateRootRepository<TEntity> where TEntity : class
 | 
			
		||||
    {
 | 
			
		||||
        readonly IBaseRepository<TEntity> _repository;
 | 
			
		||||
        public AggregateRootRepository(IFreeSql fsql)
 | 
			
		||||
        {
 | 
			
		||||
            if (fsql == null) throw new ArgumentNullException(nameof(fsql));
 | 
			
		||||
            _repository = fsql.GetRepository<TEntity>();
 | 
			
		||||
            _repository.DbContextOptions.EnableCascadeSave = false;
 | 
			
		||||
        }
 | 
			
		||||
        public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager) : this(uowManager?.Orm ?? fsql)
 | 
			
		||||
        {
 | 
			
		||||
            uowManager?.Binding(_repository);
 | 
			
		||||
        }
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            DisposeChildRepositorys();
 | 
			
		||||
            _repository.FlushState();
 | 
			
		||||
            _repository.Dispose();
 | 
			
		||||
            FlushState();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        string _boundaryName = "";
 | 
			
		||||
        public IBaseRepository<TEntity> ChangeBoundary(string name)
 | 
			
		||||
        {
 | 
			
		||||
            DisposeChildRepositorys();
 | 
			
		||||
            _repository.FlushState();
 | 
			
		||||
            FlushState();
 | 
			
		||||
            _boundaryName = string.Concat(name).Trim();
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IFreeSql Orm => _repository.Orm;
 | 
			
		||||
        public IUnitOfWork UnitOfWork { get => _repository.UnitOfWork; set => _repository.UnitOfWork = value; }
 | 
			
		||||
        public DbContextOptions DbContextOptions
 | 
			
		||||
        {
 | 
			
		||||
            get => _repository.DbContextOptions;
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                if (value == null) throw new ArgumentNullException(nameof(DbContextOptions));
 | 
			
		||||
                _repository.DbContextOptions = value;
 | 
			
		||||
                _repository.DbContextOptions.EnableCascadeSave = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        public void AsType(Type entityType) => _repository.AsType(entityType); 
 | 
			
		||||
        Func<string, string> _asTableRule;
 | 
			
		||||
        public void AsTable(Func<string, string> rule)
 | 
			
		||||
        {
 | 
			
		||||
            _repository.AsTable(rule);
 | 
			
		||||
            _asTableRule = rule;
 | 
			
		||||
        }
 | 
			
		||||
        public Type EntityType => _repository.EntityType;
 | 
			
		||||
        public IDataFilter<TEntity> DataFilter => _repository.DataFilter;
 | 
			
		||||
 | 
			
		||||
        public void Attach(TEntity entity)
 | 
			
		||||
        {
 | 
			
		||||
            var state = CreateEntityState(entity);
 | 
			
		||||
            if (_states.ContainsKey(state.Key)) _states[state.Key] = state;
 | 
			
		||||
            else _states.Add(state.Key, state);
 | 
			
		||||
        }
 | 
			
		||||
        public void Attach(IEnumerable<TEntity> entity)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var item in entity)
 | 
			
		||||
                Attach(item);
 | 
			
		||||
        }
 | 
			
		||||
        public IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data) => _repository.AttachOnlyPrimary(data);
 | 
			
		||||
        public Dictionary<string, object[]> CompareState(TEntity newdata)
 | 
			
		||||
        {
 | 
			
		||||
            if (newdata == null) return null;
 | 
			
		||||
            var _table = Orm.CodeFirst.GetTableByEntity(EntityType);
 | 
			
		||||
            if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.Incomparable_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, newdata)));
 | 
			
		||||
            var key = Orm.GetEntityKeyString(EntityType, newdata, false);
 | 
			
		||||
            if (string.IsNullOrEmpty(key)) throw new Exception(DbContextStrings.Incomparable_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, newdata)));
 | 
			
		||||
            if (_states.TryGetValue(key, out var oldState) == false || oldState == null) throw new Exception($"不可对比,数据未被跟踪:{Orm.GetEntityString(EntityType, newdata)}");
 | 
			
		||||
            AggregateRootTrackingChangeInfo tracking = new AggregateRootTrackingChangeInfo();
 | 
			
		||||
            AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, oldState, newdata, null, tracking);
 | 
			
		||||
            return new Dictionary<string, object[]>
 | 
			
		||||
            {
 | 
			
		||||
                ["Insert"] = tracking.InsertLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray(),
 | 
			
		||||
                ["Delete"] = tracking.DeleteLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray(),
 | 
			
		||||
                ["Update"] = tracking.UpdateLog.Select(a => new object[] { a.Item1, a.Item2, a.Item3, a.Item4 }).ToArray(),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        public void FlushState()
 | 
			
		||||
        {
 | 
			
		||||
            DisposeChildRepositorys();
 | 
			
		||||
            _repository.FlushState();
 | 
			
		||||
            _states.Clear();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IUpdate<TEntity> UpdateDiy => _repository.UpdateDiy;
 | 
			
		||||
        public ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => Select.Where(exp);
 | 
			
		||||
        public ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => Select.WhereIf(condition, exp);
 | 
			
		||||
 | 
			
		||||
        readonly Dictionary<Type, IBaseRepository<object>> _childRepositorys = new Dictionary<Type, IBaseRepository<object>>();
 | 
			
		||||
        IBaseRepository<object> GetChildRepository(Type type)
 | 
			
		||||
        {
 | 
			
		||||
            if (_childRepositorys.TryGetValue(type, out var repo) == false)
 | 
			
		||||
            {
 | 
			
		||||
                repo = Orm.GetRepository<object>();
 | 
			
		||||
                repo.AsType(type);
 | 
			
		||||
                _childRepositorys.Add(type, repo);
 | 
			
		||||
            }
 | 
			
		||||
            repo.UnitOfWork = UnitOfWork;
 | 
			
		||||
            repo.DbContextOptions = DbContextOptions;
 | 
			
		||||
            repo.DbContextOptions.EnableCascadeSave = false;
 | 
			
		||||
            repo.AsTable(_asTableRule);
 | 
			
		||||
            return repo;
 | 
			
		||||
        }
 | 
			
		||||
        void DisposeChildRepositorys()
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var repo in _childRepositorys.Values)
 | 
			
		||||
            {
 | 
			
		||||
                repo.FlushState();
 | 
			
		||||
                repo.Dispose();
 | 
			
		||||
            }
 | 
			
		||||
            _childRepositorys.Clear();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #region 状态管理
 | 
			
		||||
        protected Dictionary<string, EntityState> _states = new Dictionary<string, EntityState>();
 | 
			
		||||
        protected class EntityState
 | 
			
		||||
        {
 | 
			
		||||
            public EntityState(TEntity value, string key)
 | 
			
		||||
            {
 | 
			
		||||
                this.Value = value;
 | 
			
		||||
                this.Key = key;
 | 
			
		||||
                this.Time = DateTime.Now;
 | 
			
		||||
            }
 | 
			
		||||
            public TEntity OldValue { get; set; }
 | 
			
		||||
            public TEntity Value { get; set; }
 | 
			
		||||
            public string Key { get; set; }
 | 
			
		||||
            public DateTime Time { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
        EntityState CreateEntityState(TEntity data)
 | 
			
		||||
        {
 | 
			
		||||
            if (data == null) throw new ArgumentNullException(nameof(data));
 | 
			
		||||
            var key = Orm.GetEntityKeyString(EntityType, data, false);
 | 
			
		||||
            var state = new EntityState((TEntity)EntityType.CreateInstanceGetDefaultValue(), key);
 | 
			
		||||
            AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, data, state.Value);
 | 
			
		||||
            return state;
 | 
			
		||||
        }
 | 
			
		||||
        bool? ExistsInStates(object data)
 | 
			
		||||
        {
 | 
			
		||||
            if (data == null) throw new ArgumentNullException(nameof(data));
 | 
			
		||||
            var key = Orm.GetEntityKeyString(EntityType, data, false);
 | 
			
		||||
            if (string.IsNullOrEmpty(key)) return null;
 | 
			
		||||
            return _states.ContainsKey(key);
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region 查询数据
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)<para></para>
 | 
			
		||||
        /// 重写:使用
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public virtual ISelect<TEntity> Select => SelectAggregateRoot;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 创建查询对象(纯净)<para></para>
 | 
			
		||||
        /// _<para></para>
 | 
			
		||||
        /// 聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发<para></para>
 | 
			
		||||
        /// string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order))
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        protected ISelect<TEntity> SelectDiy => _repository.Select.TrackToList(SelectAggregateRootTracking);
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns></returns>
 | 
			
		||||
        protected ISelect<TEntity> SelectAggregateRoot
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                var query = _repository.Select.TrackToList(SelectAggregateRootTracking);
 | 
			
		||||
                query = AggregateRootUtils.GetAutoIncludeQuery(_boundaryName, query);
 | 
			
		||||
                return query;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// ISelect.TrackToList 委托,数据返回后自动 Attach
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="list"></param>
 | 
			
		||||
        protected void SelectAggregateRootTracking(object list)
 | 
			
		||||
        {
 | 
			
		||||
            if (list == null) return;
 | 
			
		||||
            var ls = list as IEnumerable<TEntity>;
 | 
			
		||||
            if (ls == null)
 | 
			
		||||
            {
 | 
			
		||||
                var ie = list as IEnumerable;
 | 
			
		||||
                if (ie == null) return;
 | 
			
		||||
                var isfirst = true;
 | 
			
		||||
                foreach (var item in ie)
 | 
			
		||||
                {
 | 
			
		||||
                    if (item == null) continue;
 | 
			
		||||
                    if (isfirst)
 | 
			
		||||
                    {
 | 
			
		||||
                        isfirst = false;
 | 
			
		||||
                        var itemType = item.GetType();
 | 
			
		||||
                        if (itemType == typeof(object)) return;
 | 
			
		||||
                        if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType;
 | 
			
		||||
                        if (Orm.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return;
 | 
			
		||||
                        if (itemType.GetConstructor(Type.EmptyTypes) == null) return;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (item is TEntity item2) Attach(item2);
 | 
			
		||||
                    else return;
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        //void SelectAggregateRootNavigateReader<T1>(ISelect<T1> currentQuery, Type entityType, string navigatePath, Stack<Type> ignores)
 | 
			
		||||
        //{
 | 
			
		||||
        //    if (ignores.Any(a => a == entityType)) return;
 | 
			
		||||
        //    ignores.Push(entityType);
 | 
			
		||||
        //    var table = Orm.CodeFirst.GetTableByEntity(entityType);
 | 
			
		||||
        //    if (table == null) return;
 | 
			
		||||
        //    if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}.";
 | 
			
		||||
        //    foreach (var tr in table.GetAllTableRef())
 | 
			
		||||
        //    {
 | 
			
		||||
        //        var tbref = tr.Value;
 | 
			
		||||
        //        if (tbref.Exception != null) continue;
 | 
			
		||||
        //        var navigateExpression = $"{navigatePath}{tr.Key}";
 | 
			
		||||
        //        switch (tbref.RefType)
 | 
			
		||||
        //        {
 | 
			
		||||
        //            case TableRefType.OneToOne:
 | 
			
		||||
        //                if (ignores.Any(a => a == tbref.RefEntityType)) break;
 | 
			
		||||
        //                currentQuery.IncludeByPropertyName(navigateExpression);
 | 
			
		||||
        //                SelectAggregateRootNavigateReader(currentQuery, tbref.RefEntityType, navigateExpression, ignores);
 | 
			
		||||
        //                break;
 | 
			
		||||
        //            case TableRefType.OneToMany:
 | 
			
		||||
        //                var ignoresCopy = new Stack<Type>(ignores.ToArray());
 | 
			
		||||
        //                currentQuery.IncludeByPropertyName(navigateExpression, then =>
 | 
			
		||||
        //                    SelectAggregateRootNavigateReader(then, tbref.RefEntityType, "", ignoresCopy)); //variable 'then' of type 'FreeSql.ISelect`1[System.Object]' referenced from scope '', but it is not defined
 | 
			
		||||
        //                break;
 | 
			
		||||
        //            case TableRefType.ManyToMany:
 | 
			
		||||
        //                currentQuery.IncludeByPropertyName(navigateExpression);
 | 
			
		||||
        //                break;
 | 
			
		||||
        //            case TableRefType.PgArrayToMany:
 | 
			
		||||
        //                break;
 | 
			
		||||
        //            case TableRefType.ManyToOne:
 | 
			
		||||
        //                break;
 | 
			
		||||
        //        }
 | 
			
		||||
        //    }
 | 
			
		||||
        //    ignores.Pop();
 | 
			
		||||
        //}
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,297 @@
 | 
			
		||||
#if net40
 | 
			
		||||
#else
 | 
			
		||||
using FreeSql.Extensions.EntityUtil;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    partial class AggregateRootRepository<TEntity>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        #region InsertAsync
 | 
			
		||||
        async public virtual Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => (await InsertAsync(new[] { entity }, cancellationToken)).FirstOrDefault();
 | 
			
		||||
        async public virtual Task<List<TEntity>> InsertAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            var repos = new Dictionary<Type, object>();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var ret = await InsertWithinBoundaryStaticAsync(_boundaryName, _repository, GetChildRepository, entitys, null, cancellationToken);
 | 
			
		||||
                Attach(ret);
 | 
			
		||||
                return ret;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                DisposeChildRepositorys();
 | 
			
		||||
                _repository.FlushState();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        async Task<List<T1>> InsertWithinBoundaryStaticAsync<T1>(string boundaryName, IBaseRepository<T1> rootRepository, Func<Type, IBaseRepository<object>> getChildRepository, IEnumerable<T1> rootEntitys, int[] affrows, CancellationToken cancellationToken) where T1 : class
 | 
			
		||||
        {
 | 
			
		||||
            Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
 | 
			
		||||
            Dictionary<Type, IBaseRepository<object>> repos = new Dictionary<Type, IBaseRepository<object>>();
 | 
			
		||||
            var localAffrows = 0;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return await LocalInsertAsync(rootRepository, rootEntitys, true);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                if (affrows != null) affrows[0] = localAffrows;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool LocalCanInsert(Type entityType, object entity, bool isadd)
 | 
			
		||||
            {
 | 
			
		||||
                var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false);
 | 
			
		||||
                if (stateKey == null) return true;
 | 
			
		||||
                if (ignores.TryGetValue(entityType, out var stateKeys) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    if (isadd)
 | 
			
		||||
                    {
 | 
			
		||||
                        ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
 | 
			
		||||
                        stateKeys.Add(stateKey, true);
 | 
			
		||||
                    }
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                if (stateKeys.ContainsKey(stateKey) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    if (isadd) stateKeys.Add(stateKey, true);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            async Task<List<T2>> LocalInsertAsync<T2>(IBaseRepository<T2> repository, IEnumerable<T2> entitys, bool cascade) where T2 : class
 | 
			
		||||
            {
 | 
			
		||||
                var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType);
 | 
			
		||||
                if (table.Primarys.Any(col => col.Attribute.IsIdentity))
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var entity in entitys)
 | 
			
		||||
                        repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity);
 | 
			
		||||
                }
 | 
			
		||||
                var ret = await repository.InsertAsync(entitys, cancellationToken);
 | 
			
		||||
                localAffrows += ret.Count;
 | 
			
		||||
                foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true);
 | 
			
		||||
                if (cascade == false) return ret;
 | 
			
		||||
 | 
			
		||||
                foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
 | 
			
		||||
                {
 | 
			
		||||
                    var tbref = tr.Value;
 | 
			
		||||
                    if (tbref.Exception != null) continue;
 | 
			
		||||
                    if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
 | 
			
		||||
                    var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName);
 | 
			
		||||
                    if (boundaryAttr?.Break == true) continue;
 | 
			
		||||
                    switch (tbref.RefType)
 | 
			
		||||
                    {
 | 
			
		||||
                        case TableRefType.OneToOne:
 | 
			
		||||
                            var otoList = ret.Select(entity =>
 | 
			
		||||
                            {
 | 
			
		||||
                                var otoItem = table.GetPropertyValue(entity, prop.Name);
 | 
			
		||||
                                if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null;
 | 
			
		||||
                                AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem);
 | 
			
		||||
                                return otoItem;
 | 
			
		||||
                            }).Where(entity => entity != null).ToArray();
 | 
			
		||||
                            if (otoList.Any())
 | 
			
		||||
                            {
 | 
			
		||||
                                var repo = getChildRepository(tbref.RefEntityType);
 | 
			
		||||
                                await LocalInsertAsync(repo, otoList, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.OneToMany:
 | 
			
		||||
                            var otmList = ret.Select(entity =>
 | 
			
		||||
                            {
 | 
			
		||||
                                var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
 | 
			
		||||
                                if (otmEach == null) return null;
 | 
			
		||||
                                var otmItems = new List<object>();
 | 
			
		||||
                                foreach (var otmItem in otmEach)
 | 
			
		||||
                                {
 | 
			
		||||
                                    if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue;
 | 
			
		||||
                                    otmItems.Add(otmItem);
 | 
			
		||||
                                }
 | 
			
		||||
                                AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems);
 | 
			
		||||
                                return otmItems;
 | 
			
		||||
                            }).Where(entity => entity != null).SelectMany(entity => entity).ToArray();
 | 
			
		||||
                            if (otmList.Any())
 | 
			
		||||
                            {
 | 
			
		||||
                                var repo = getChildRepository(tbref.RefEntityType);
 | 
			
		||||
                                await LocalInsertAsync(repo, otmList, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToMany:
 | 
			
		||||
                            var mtmMidList = new List<object>();
 | 
			
		||||
                            ret.ForEach(entity =>
 | 
			
		||||
                            {
 | 
			
		||||
                                var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop);
 | 
			
		||||
                                if (mids != null) mtmMidList.AddRange(mids);
 | 
			
		||||
                            });
 | 
			
		||||
                            if (mtmMidList.Any())
 | 
			
		||||
                            {
 | 
			
		||||
                                var repo = getChildRepository(tbref.RefMiddleEntityType);
 | 
			
		||||
                                await LocalInsertAsync(repo, mtmMidList, false);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.PgArrayToMany:
 | 
			
		||||
                        case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return ret;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        async public virtual Task<TEntity> InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
 | 
			
		||||
            if (entity == null) throw new ArgumentNullException(nameof(entity));
 | 
			
		||||
            var table = Orm.CodeFirst.GetTableByEntity(EntityType);
 | 
			
		||||
            if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity)));
 | 
			
		||||
 | 
			
		||||
            var flagExists = ExistsInStates(entity);
 | 
			
		||||
            if (flagExists == false)
 | 
			
		||||
            {
 | 
			
		||||
                var olddata = await Select.WhereDynamic(entity).FirstAsync(cancellationToken);
 | 
			
		||||
                flagExists = olddata != null;
 | 
			
		||||
            }
 | 
			
		||||
            if (flagExists == true)
 | 
			
		||||
            {
 | 
			
		||||
                var affrows = await UpdateAsync(entity, cancellationToken);
 | 
			
		||||
                if (affrows > 0) return entity;
 | 
			
		||||
            }
 | 
			
		||||
            if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length)
 | 
			
		||||
            {
 | 
			
		||||
                Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity);
 | 
			
		||||
                return await InsertAsync(entity, cancellationToken);
 | 
			
		||||
            }
 | 
			
		||||
            throw new Exception(DbContextStrings.CannotAdd_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, entity)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => UpdateAsync(new[] { entity }, cancellationToken);
 | 
			
		||||
        public virtual Task<int> UpdateAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            var tracking = new AggregateRootTrackingChangeInfo();
 | 
			
		||||
            foreach (var entity in entitys)
 | 
			
		||||
            {
 | 
			
		||||
                var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
 | 
			
		||||
                if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}");
 | 
			
		||||
                AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var entity in entitys)
 | 
			
		||||
                Attach(entity);
 | 
			
		||||
 | 
			
		||||
            return SaveTrackingChangeAsync(tracking, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public virtual Task<int> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(new[] { entity }, null, cancellationToken);
 | 
			
		||||
        public virtual Task<int> DeleteAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(entitys, null, cancellationToken);
 | 
			
		||||
        async public virtual Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) => await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), null, cancellationToken);
 | 
			
		||||
        async public virtual Task<List<object>> DeleteCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            var deletedOutput = new List<object>();
 | 
			
		||||
            await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), deletedOutput, cancellationToken);
 | 
			
		||||
            return deletedOutput;
 | 
			
		||||
        }
 | 
			
		||||
        async Task<int> DeleteWithinBoundaryAsync(IEnumerable<TEntity> entitys, List<object> deletedOutput, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var tracking = new AggregateRootTrackingChangeInfo();
 | 
			
		||||
            foreach (var entity in entitys)
 | 
			
		||||
            {
 | 
			
		||||
                var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
 | 
			
		||||
                AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking);
 | 
			
		||||
                _states.Remove(stateKey);
 | 
			
		||||
            }
 | 
			
		||||
            var affrows = 0;
 | 
			
		||||
            for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
 | 
			
		||||
            {
 | 
			
		||||
                affrows += await Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule)
 | 
			
		||||
                    .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken);
 | 
			
		||||
                if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2);
 | 
			
		||||
                UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
 | 
			
		||||
                    new DbContext.EntityChangeReport.ChangeInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        Type = DbContext.EntityChangeType.Delete,
 | 
			
		||||
                        EntityType = tracking.DeleteLog[a].Item1,
 | 
			
		||||
                        Object = x
 | 
			
		||||
                    }));
 | 
			
		||||
            }
 | 
			
		||||
            return affrows;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async public virtual Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            var tracking = new AggregateRootTrackingChangeInfo();
 | 
			
		||||
            var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
 | 
			
		||||
            if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}");
 | 
			
		||||
            AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking);
 | 
			
		||||
            Attach(entity); //应该只存储 propertyName 内容
 | 
			
		||||
            await SaveTrackingChangeAsync(tracking, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        async Task<int> SaveTrackingChangeAsync(AggregateRootTrackingChangeInfo tracking, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var affrows = 0;
 | 
			
		||||
            DisposeChildRepositorys();
 | 
			
		||||
            var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray());
 | 
			
		||||
            foreach (var il in insertLogDict)
 | 
			
		||||
            {
 | 
			
		||||
                var repo = GetChildRepository(il.Key);
 | 
			
		||||
                var affrowsOut = new int[1];
 | 
			
		||||
                await InsertWithinBoundaryStaticAsync(_boundaryName, repo, GetChildRepository, il.Value, affrowsOut, cancellationToken);
 | 
			
		||||
                affrows += affrowsOut[0];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
 | 
			
		||||
            {
 | 
			
		||||
                affrows += await Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule)
 | 
			
		||||
                    .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken);
 | 
			
		||||
                UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
 | 
			
		||||
                    new DbContext.EntityChangeReport.ChangeInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        Type = DbContext.EntityChangeType.Delete,
 | 
			
		||||
                        EntityType = tracking.DeleteLog[a].Item1,
 | 
			
		||||
                        Object = x
 | 
			
		||||
                    }));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => new
 | 
			
		||||
            {
 | 
			
		||||
                BeforeObject = b.Item2,
 | 
			
		||||
                AfterObject = b.Item3,
 | 
			
		||||
                UpdateColumns = b.Item4,
 | 
			
		||||
                UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c))
 | 
			
		||||
            }).ToArray());
 | 
			
		||||
            var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.UpdateColumnsString, b => a.Value.Where(c => c.UpdateColumnsString == b.UpdateColumnsString).ToArray()));
 | 
			
		||||
            foreach (var dl in updateLogDict2)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var dl2 in dl.Value)
 | 
			
		||||
                {
 | 
			
		||||
                    affrows += await Orm.Update<object>().AsType(dl.Key).AsTable(_asTableRule)
 | 
			
		||||
                        .SetSource(dl2.Value.Select(a => a.AfterObject).ToArray())
 | 
			
		||||
                        .UpdateColumns(dl2.Value.First().UpdateColumns.ToArray())
 | 
			
		||||
                        .ExecuteAffrowsAsync(cancellationToken);
 | 
			
		||||
                    UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x =>
 | 
			
		||||
                        new DbContext.EntityChangeReport.ChangeInfo
 | 
			
		||||
                        {
 | 
			
		||||
                            Type = DbContext.EntityChangeType.Update,
 | 
			
		||||
                            EntityType = dl.Key,
 | 
			
		||||
                            Object = x.AfterObject,
 | 
			
		||||
                            BeforeObject = x.BeforeObject
 | 
			
		||||
                        }));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            DisposeChildRepositorys();
 | 
			
		||||
            return affrows;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -0,0 +1,347 @@
 | 
			
		||||
using FreeSql.Extensions.EntityUtil;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    partial class AggregateRootRepository<TEntity>
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        #region BeginEdit/EndEdit
 | 
			
		||||
        List<TEntity> _dataEditing;
 | 
			
		||||
        ConcurrentDictionary<string, EntityState> _statesEditing = new ConcurrentDictionary<string, EntityState>();
 | 
			
		||||
        public virtual void BeginEdit(List<TEntity> data)
 | 
			
		||||
        {
 | 
			
		||||
            if (data == null) return;
 | 
			
		||||
            var table = Orm.CodeFirst.GetTableByEntity(EntityType);
 | 
			
		||||
            if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotEdit_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, data.First())));
 | 
			
		||||
            _statesEditing.Clear();
 | 
			
		||||
            _dataEditing = data;
 | 
			
		||||
            foreach (var item in data)
 | 
			
		||||
            {
 | 
			
		||||
                var key = Orm.GetEntityKeyString(EntityType, item, false);
 | 
			
		||||
                if (string.IsNullOrEmpty(key)) continue;
 | 
			
		||||
 | 
			
		||||
                _statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) =>
 | 
			
		||||
                {
 | 
			
		||||
                    AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, item, ov.Value);
 | 
			
		||||
                    ov.Time = DateTime.Now;
 | 
			
		||||
                    return ov;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        public virtual int EndEdit(List<TEntity> data = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (data == null) data = _dataEditing;
 | 
			
		||||
            if (data == null) return 0;
 | 
			
		||||
            var tracking = new AggregateRootTrackingChangeInfo();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var addList = new List<TEntity>();
 | 
			
		||||
                var ediList = new List<TEntity>();
 | 
			
		||||
                foreach (var item in data)
 | 
			
		||||
                {
 | 
			
		||||
                    var key = Orm.GetEntityKeyString(EntityType, item, false);
 | 
			
		||||
                    if (_statesEditing.TryRemove(key, out var state) == false)
 | 
			
		||||
                    {
 | 
			
		||||
                        tracking.InsertLog.Add(NativeTuple.Create(EntityType, (object)item));
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    _states[key] = state;
 | 
			
		||||
                    AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, item, null, tracking);
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var item in _statesEditing.Values.OrderBy(a => a.Time))
 | 
			
		||||
                    AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, item, null, null, tracking);
 | 
			
		||||
 | 
			
		||||
                return SaveTrackingChange(tracking);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                _dataEditing = null;
 | 
			
		||||
                _statesEditing.Clear();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        #region Insert
 | 
			
		||||
        public virtual TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault();
 | 
			
		||||
        public virtual List<TEntity> Insert(IEnumerable<TEntity> entitys)
 | 
			
		||||
        {
 | 
			
		||||
            var repos = new Dictionary<Type, object>();
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var ret = InsertWithinBoundaryStatic(_boundaryName, _repository, GetChildRepository, entitys, out var affrows);
 | 
			
		||||
                Attach(ret);
 | 
			
		||||
                return ret;
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                DisposeChildRepositorys();
 | 
			
		||||
                _repository.FlushState();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        static List<T1> InsertWithinBoundaryStatic<T1>(string boundaryName, IBaseRepository<T1> rootRepository, Func<Type, IBaseRepository<object>> getChildRepository, IEnumerable<T1> rootEntitys, out int affrows) where T1 : class {
 | 
			
		||||
            Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
 | 
			
		||||
            Dictionary<Type, IBaseRepository<object>> repos = new Dictionary<Type, IBaseRepository<object>>();
 | 
			
		||||
            var localAffrows = 0;
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return LocalInsert(rootRepository, rootEntitys, true);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                affrows = localAffrows;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool LocalCanInsert(Type entityType, object entity, bool isadd)
 | 
			
		||||
            {
 | 
			
		||||
                var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false);
 | 
			
		||||
                if (stateKey == null) return true;
 | 
			
		||||
                if (ignores.TryGetValue(entityType, out var stateKeys) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    if (isadd)
 | 
			
		||||
                    {
 | 
			
		||||
                        ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
 | 
			
		||||
                        stateKeys.Add(stateKey, true);
 | 
			
		||||
                    }
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                if (stateKeys.ContainsKey(stateKey) == false)
 | 
			
		||||
                {
 | 
			
		||||
                    if (isadd) stateKeys.Add(stateKey, true);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            List<T2> LocalInsert<T2>(IBaseRepository<T2> repository, IEnumerable<T2> entitys, bool cascade) where T2 : class
 | 
			
		||||
            {
 | 
			
		||||
                var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType);
 | 
			
		||||
                if (table.Primarys.Any(col => col.Attribute.IsIdentity))
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var entity in entitys) 
 | 
			
		||||
                        repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity);
 | 
			
		||||
                }
 | 
			
		||||
                var ret = repository.Insert(entitys);
 | 
			
		||||
                localAffrows += ret.Count;
 | 
			
		||||
                foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true);
 | 
			
		||||
                if (cascade == false) return ret;
 | 
			
		||||
 | 
			
		||||
                foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
 | 
			
		||||
                {
 | 
			
		||||
                    var tbref = tr.Value;
 | 
			
		||||
                    if (tbref.Exception != null) continue;
 | 
			
		||||
                    if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
 | 
			
		||||
                    var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName);
 | 
			
		||||
                    if (boundaryAttr?.Break == true) continue;
 | 
			
		||||
                    switch (tbref.RefType)
 | 
			
		||||
                    {
 | 
			
		||||
                        case TableRefType.OneToOne:
 | 
			
		||||
                            var otoList = ret.Select(entity =>
 | 
			
		||||
                            {
 | 
			
		||||
                                var otoItem = table.GetPropertyValue(entity, prop.Name);
 | 
			
		||||
                                if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null;
 | 
			
		||||
                                AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem);
 | 
			
		||||
                                return otoItem;
 | 
			
		||||
                            }).Where(entity => entity != null).ToArray();
 | 
			
		||||
                            if (otoList.Any())
 | 
			
		||||
                            {
 | 
			
		||||
                                var repo = getChildRepository(tbref.RefEntityType);
 | 
			
		||||
                                LocalInsert(repo, otoList, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.OneToMany:
 | 
			
		||||
                            var otmList = ret.Select(entity =>
 | 
			
		||||
                            {
 | 
			
		||||
                                var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
 | 
			
		||||
                                if (otmEach == null) return null;
 | 
			
		||||
                                var otmItems = new List<object>();
 | 
			
		||||
                                foreach (var otmItem in otmEach)
 | 
			
		||||
                                {
 | 
			
		||||
                                    if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue;
 | 
			
		||||
                                    otmItems.Add(otmItem);
 | 
			
		||||
                                }
 | 
			
		||||
                                AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems);
 | 
			
		||||
                                return otmItems;
 | 
			
		||||
                            }).Where(entity => entity != null).SelectMany(entity => entity).ToArray();
 | 
			
		||||
                            if (otmList.Any())
 | 
			
		||||
                            {
 | 
			
		||||
                                var repo = getChildRepository(tbref.RefEntityType);
 | 
			
		||||
                                LocalInsert(repo, otmList, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToMany:
 | 
			
		||||
                            var mtmMidList = new List<object>();
 | 
			
		||||
                            ret.ForEach(entity =>
 | 
			
		||||
                            {
 | 
			
		||||
                                var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop);
 | 
			
		||||
                                if (mids != null) mtmMidList.AddRange(mids);
 | 
			
		||||
                            });
 | 
			
		||||
                            if (mtmMidList.Any())
 | 
			
		||||
                            {
 | 
			
		||||
                                var repo = getChildRepository(tbref.RefMiddleEntityType);
 | 
			
		||||
                                LocalInsert(repo, mtmMidList, false);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.PgArrayToMany:
 | 
			
		||||
                        case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return ret;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        #endregion
 | 
			
		||||
 | 
			
		||||
        public virtual TEntity InsertOrUpdate(TEntity entity)
 | 
			
		||||
        {
 | 
			
		||||
            var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
 | 
			
		||||
            if (entity == null) throw new ArgumentNullException(nameof(entity));
 | 
			
		||||
            var table = Orm.CodeFirst.GetTableByEntity(EntityType);
 | 
			
		||||
            if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity)));
 | 
			
		||||
 | 
			
		||||
            var flagExists = ExistsInStates(entity);
 | 
			
		||||
            if (flagExists == false)
 | 
			
		||||
            {
 | 
			
		||||
                var olddata = Select.WhereDynamic(entity).First();
 | 
			
		||||
                flagExists = olddata != null;
 | 
			
		||||
            }
 | 
			
		||||
            if (flagExists == true)
 | 
			
		||||
            {
 | 
			
		||||
                var affrows = Update(entity);
 | 
			
		||||
                if (affrows > 0) return entity;
 | 
			
		||||
            }
 | 
			
		||||
            if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length)
 | 
			
		||||
            {
 | 
			
		||||
                Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity);
 | 
			
		||||
                return Insert(entity);
 | 
			
		||||
            }
 | 
			
		||||
            throw new Exception(DbContextStrings.CannotAdd_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, entity)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual int Update(TEntity entity) => Update(new[] { entity });
 | 
			
		||||
        public virtual int Update(IEnumerable<TEntity> entitys)
 | 
			
		||||
        {
 | 
			
		||||
            var tracking = new AggregateRootTrackingChangeInfo();
 | 
			
		||||
            foreach(var entity in entitys)
 | 
			
		||||
            {
 | 
			
		||||
                var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
 | 
			
		||||
                if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}");
 | 
			
		||||
                AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var entity in entitys)
 | 
			
		||||
                Attach(entity);
 | 
			
		||||
 | 
			
		||||
            return SaveTrackingChange(tracking);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual int Delete(TEntity entity) => DeleteWithinBoundary(new[] { entity }, null);
 | 
			
		||||
        public virtual int Delete(IEnumerable<TEntity> entitys) => DeleteWithinBoundary(entitys, null);
 | 
			
		||||
        public virtual int Delete(Expression<Func<TEntity, bool>> predicate) => DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), null);
 | 
			
		||||
        public virtual List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate)
 | 
			
		||||
        {
 | 
			
		||||
            var deletedOutput = new List<object>();
 | 
			
		||||
            DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), deletedOutput);
 | 
			
		||||
            return deletedOutput;
 | 
			
		||||
        }
 | 
			
		||||
        int DeleteWithinBoundary(IEnumerable<TEntity> entitys, List<object> deletedOutput)
 | 
			
		||||
        {
 | 
			
		||||
            var tracking = new AggregateRootTrackingChangeInfo();
 | 
			
		||||
            foreach (var entity in entitys)
 | 
			
		||||
            {
 | 
			
		||||
                var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
 | 
			
		||||
                AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking);
 | 
			
		||||
                _states.Remove(stateKey);
 | 
			
		||||
            }
 | 
			
		||||
            var affrows = 0;
 | 
			
		||||
            for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
 | 
			
		||||
            {
 | 
			
		||||
                affrows += Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule)
 | 
			
		||||
                    .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows();
 | 
			
		||||
                if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2);
 | 
			
		||||
                UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
 | 
			
		||||
                    new DbContext.EntityChangeReport.ChangeInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        Type = DbContext.EntityChangeType.Delete,
 | 
			
		||||
                        EntityType = tracking.DeleteLog[a].Item1,
 | 
			
		||||
                        Object = x
 | 
			
		||||
                    }));
 | 
			
		||||
            }
 | 
			
		||||
            return affrows;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public virtual void SaveMany(TEntity entity, string propertyName)
 | 
			
		||||
        {
 | 
			
		||||
            var tracking = new AggregateRootTrackingChangeInfo();
 | 
			
		||||
            var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
 | 
			
		||||
            if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}");
 | 
			
		||||
            AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking);
 | 
			
		||||
            Attach(entity); //应该只存储 propertyName 内容
 | 
			
		||||
            SaveTrackingChange(tracking);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        int SaveTrackingChange(AggregateRootTrackingChangeInfo tracking)
 | 
			
		||||
        {
 | 
			
		||||
            var affrows = 0;
 | 
			
		||||
            DisposeChildRepositorys();
 | 
			
		||||
            var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray());
 | 
			
		||||
            foreach (var il in insertLogDict)
 | 
			
		||||
            {
 | 
			
		||||
                var repo = GetChildRepository(il.Key);
 | 
			
		||||
                InsertWithinBoundaryStatic(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut);
 | 
			
		||||
                affrows += affrowsOut;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
 | 
			
		||||
            {
 | 
			
		||||
                affrows += Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule)
 | 
			
		||||
                    .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows();
 | 
			
		||||
                UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
 | 
			
		||||
                    new DbContext.EntityChangeReport.ChangeInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        Type = DbContext.EntityChangeType.Delete,
 | 
			
		||||
                        EntityType = tracking.DeleteLog[a].Item1,
 | 
			
		||||
                        Object = x
 | 
			
		||||
                    }));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => new
 | 
			
		||||
            {
 | 
			
		||||
                BeforeObject = b.Item2,
 | 
			
		||||
                AfterObject = b.Item3,
 | 
			
		||||
                UpdateColumns = b.Item4,
 | 
			
		||||
                UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c))
 | 
			
		||||
            }).ToArray());
 | 
			
		||||
            var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.UpdateColumnsString, b => a.Value.Where(c => c.UpdateColumnsString == b.UpdateColumnsString).ToArray()));
 | 
			
		||||
            foreach (var dl in updateLogDict2)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var dl2 in dl.Value)
 | 
			
		||||
                {
 | 
			
		||||
                    affrows += Orm.Update<object>().AsType(dl.Key).AsTable(_asTableRule)
 | 
			
		||||
                        .SetSource(dl2.Value.Select(a => a.AfterObject).ToArray())
 | 
			
		||||
                        .UpdateColumns(dl2.Value.First().UpdateColumns.ToArray())
 | 
			
		||||
                        .ExecuteAffrows();
 | 
			
		||||
                    UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x =>
 | 
			
		||||
                        new DbContext.EntityChangeReport.ChangeInfo
 | 
			
		||||
                        {
 | 
			
		||||
                            Type = DbContext.EntityChangeType.Update,
 | 
			
		||||
                            EntityType = dl.Key,
 | 
			
		||||
                            Object = x.AfterObject,
 | 
			
		||||
                            BeforeObject = x.BeforeObject
 | 
			
		||||
                        }));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            DisposeChildRepositorys();
 | 
			
		||||
            return affrows;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										652
									
								
								FreeSql.DbContext/AggregateRootRepository/AggregateRootUtils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										652
									
								
								FreeSql.DbContext/AggregateRootRepository/AggregateRootUtils.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,652 @@
 | 
			
		||||
using FreeSql;
 | 
			
		||||
using FreeSql.DataAnnotations;
 | 
			
		||||
using FreeSql.Extensions.EntityUtil;
 | 
			
		||||
using FreeSql.Internal;
 | 
			
		||||
using FreeSql.Internal.CommonProvider;
 | 
			
		||||
using FreeSql.Internal.Model;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.ObjectModel;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Text;
 | 
			
		||||
 | 
			
		||||
namespace FreeSql
 | 
			
		||||
{
 | 
			
		||||
    public class AggregateRootUtils
 | 
			
		||||
    {
 | 
			
		||||
        static ConcurrentDictionary<PropertyInfo, ConcurrentDictionary<string, AggregateRootBoundaryAttribute>> _dicGetPropertyBoundaryAttribute = new ConcurrentDictionary<PropertyInfo, ConcurrentDictionary<string, AggregateRootBoundaryAttribute>>();
 | 
			
		||||
        public static AggregateRootBoundaryAttribute GetPropertyBoundaryAttribute(PropertyInfo prop, string boundaryName)
 | 
			
		||||
        {
 | 
			
		||||
            if (boundaryName == null) return null;
 | 
			
		||||
            return _dicGetPropertyBoundaryAttribute.GetOrAdd(prop, tp => new ConcurrentDictionary<string, AggregateRootBoundaryAttribute>())
 | 
			
		||||
                .GetOrAdd(boundaryName, bn =>
 | 
			
		||||
                {
 | 
			
		||||
                    var attrs = prop.GetCustomAttributes(typeof(AggregateRootBoundaryAttribute), false);
 | 
			
		||||
                    if (attrs == null || attrs.Any() == false) return null;
 | 
			
		||||
                    return attrs.Select(a => a as AggregateRootBoundaryAttribute).Where(a => a.Name == bn).FirstOrDefault();
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void CompareEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityBefore, object rootEntityAfter, string rootNavigatePropertyName, AggregateRootTrackingChangeInfo tracking)
 | 
			
		||||
        {
 | 
			
		||||
            Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
 | 
			
		||||
            LocalCompareEntityValue(rootEntityType, rootEntityBefore, rootEntityAfter, rootNavigatePropertyName, true);
 | 
			
		||||
            ignores.Clear();
 | 
			
		||||
 | 
			
		||||
            void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName, bool cascade)
 | 
			
		||||
            {
 | 
			
		||||
                if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType();
 | 
			
		||||
 | 
			
		||||
                if (entityBefore != null)
 | 
			
		||||
                {
 | 
			
		||||
                    var stateKey = $":before://{fsql.GetEntityKeyString(entityType, entityBefore, false)}";
 | 
			
		||||
                    if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
 | 
			
		||||
                    if (stateKeys.ContainsKey(stateKey)) return;
 | 
			
		||||
                    stateKeys.Add(stateKey, true);
 | 
			
		||||
                }
 | 
			
		||||
                if (entityAfter != null)
 | 
			
		||||
                {
 | 
			
		||||
                    var stateKey = $":after://{fsql.GetEntityKeyString(entityType, entityAfter, false)}";
 | 
			
		||||
                    if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
 | 
			
		||||
                    if (stateKeys.ContainsKey(stateKey)) return;
 | 
			
		||||
                    stateKeys.Add(stateKey, true);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var table = fsql.CodeFirst.GetTableByEntity(entityType);
 | 
			
		||||
                if (table == null) return;
 | 
			
		||||
                if (entityBefore == null && entityAfter == null) return;
 | 
			
		||||
                if (entityBefore == null && entityAfter != null)
 | 
			
		||||
                {
 | 
			
		||||
                    tracking.InsertLog.Add(NativeTuple.Create(entityType, entityAfter));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (entityBefore != null && entityAfter == null)
 | 
			
		||||
                {
 | 
			
		||||
                    tracking.DeleteLog.Add(NativeTuple.Create(entityType, new[] { entityBefore }));
 | 
			
		||||
                    NavigateReader(boundaryName, fsql, entityType, entityBefore, (path, tr, ct, stackvs) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() };
 | 
			
		||||
                        tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist));
 | 
			
		||||
                    });
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var changes = new List<string>();
 | 
			
		||||
                foreach (var col in table.ColumnsByCs.Values)
 | 
			
		||||
                {
 | 
			
		||||
                    if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue;
 | 
			
		||||
                    if (table.ColumnsByCs.ContainsKey(col.CsName))
 | 
			
		||||
                    {
 | 
			
		||||
                        if (col.Attribute.IsVersion) continue;
 | 
			
		||||
                        var propvalBefore = table.GetPropertyValue(entityBefore, col.CsName);
 | 
			
		||||
                        var propvalAfter = table.GetPropertyValue(entityAfter, col.CsName);
 | 
			
		||||
                        //if (object.Equals(propvalBefore, propvalAfter) == false) changes.Add(col.CsName);
 | 
			
		||||
                        if (CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) changes.Add(col.CsName);
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (changes.Any()) tracking.UpdateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes));
 | 
			
		||||
                if (cascade == false) return;
 | 
			
		||||
 | 
			
		||||
                foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
 | 
			
		||||
                {
 | 
			
		||||
                    var tbref = tr.Value;
 | 
			
		||||
                    if (tbref.Exception != null) continue;
 | 
			
		||||
                    if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
 | 
			
		||||
                    var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
 | 
			
		||||
                    if (boundaryAttr?.Break == true) continue;
 | 
			
		||||
                    if (navigatePropertyName != null && prop.Name != navigatePropertyName) continue;
 | 
			
		||||
                    var propvalBefore = table.GetPropertyValue(entityBefore, prop.Name);
 | 
			
		||||
                    var propvalAfter = table.GetPropertyValue(entityAfter, prop.Name);
 | 
			
		||||
                    switch (tbref.RefType)
 | 
			
		||||
                    {
 | 
			
		||||
                        case TableRefType.OneToOne:
 | 
			
		||||
                            SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore);
 | 
			
		||||
                            SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter);
 | 
			
		||||
                            LocalCompareEntityValue(tbref.RefEntityType, propvalBefore, propvalAfter, null, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.OneToMany:
 | 
			
		||||
                            SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore);
 | 
			
		||||
                            SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter);
 | 
			
		||||
                            LocalCompareEntityValueCollection(tbref.RefEntityType, propvalBefore as IEnumerable, propvalAfter as IEnumerable, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToMany:
 | 
			
		||||
                            var middleValuesBefore = GetManyToManyObjects(fsql, table, tbref, entityBefore, prop);
 | 
			
		||||
                            var middleValuesAfter = GetManyToManyObjects(fsql, table, tbref, entityAfter, prop);
 | 
			
		||||
                            LocalCompareEntityValueCollection(tbref.RefMiddleEntityType, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable, false);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.PgArrayToMany:
 | 
			
		||||
                        case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            void LocalCompareEntityValueCollection(Type elementType, IEnumerable collectionBefore, IEnumerable collectionAfter, bool cascade)
 | 
			
		||||
            {
 | 
			
		||||
                if (collectionBefore == null && collectionAfter == null) return;
 | 
			
		||||
                if (collectionBefore == null && collectionAfter != null)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var item in collectionAfter)
 | 
			
		||||
                        tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (collectionBefore != null && collectionAfter == null)
 | 
			
		||||
                {
 | 
			
		||||
                    //foreach (var item in collectionBefore as IEnumerable)
 | 
			
		||||
                    //{
 | 
			
		||||
                    //    changelog.DeleteLog.Add(NativeTuple.Create(elementType, new[] { item }));
 | 
			
		||||
                    //    NavigateReader(boundaryName, fsql, elementType, item, (path, tr, ct, stackvs) =>
 | 
			
		||||
                    //    {
 | 
			
		||||
                    //        var dellist = stackvs.Last() as object[] ?? new [] { stackvs.Last() };
 | 
			
		||||
                    //        changelog.DeleteLog.Add(NativeTuple.Create(ct, dellist));
 | 
			
		||||
                    //    });
 | 
			
		||||
                    //}
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                Dictionary<string, object> dictBefore = new Dictionary<string, object>();
 | 
			
		||||
                Dictionary<string, object> dictAfter = new Dictionary<string, object>();
 | 
			
		||||
                foreach (var item in collectionBefore as IEnumerable)
 | 
			
		||||
                {
 | 
			
		||||
                    var key = fsql.GetEntityKeyString(elementType, item, false);
 | 
			
		||||
                    if (key != null) dictBefore.Add(key, item);
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var item in collectionAfter as IEnumerable)
 | 
			
		||||
                {
 | 
			
		||||
                    var key = fsql.GetEntityKeyString(elementType, item, false);
 | 
			
		||||
                    if (key != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (dictAfter.ContainsKey(key) == false) 
 | 
			
		||||
                            dictAfter.Add(key, item);
 | 
			
		||||
                    }
 | 
			
		||||
                    else tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var key in dictBefore.Keys.ToArray())
 | 
			
		||||
                {
 | 
			
		||||
                    if (dictAfter.ContainsKey(key) == false)
 | 
			
		||||
                    {
 | 
			
		||||
                        var value = dictBefore[key];
 | 
			
		||||
                        tracking.DeleteLog.Add(NativeTuple.Create(elementType, new[] { value }));
 | 
			
		||||
                        NavigateReader(boundaryName, fsql, elementType, value, (path, tr, ct, stackvs) =>
 | 
			
		||||
                        {
 | 
			
		||||
                            var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() };
 | 
			
		||||
                            tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist));
 | 
			
		||||
                        });
 | 
			
		||||
                        dictBefore.Remove(key);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var key in dictAfter.Keys.ToArray())
 | 
			
		||||
                {
 | 
			
		||||
                    if (dictBefore.ContainsKey(key) == false)
 | 
			
		||||
                    {
 | 
			
		||||
                        tracking.InsertLog.Add(NativeTuple.Create(elementType, dictAfter[key]));
 | 
			
		||||
                        dictAfter.Remove(key);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                foreach (var key in dictBefore.Keys)
 | 
			
		||||
                    LocalCompareEntityValue(elementType, dictBefore[key], dictAfter[key], null, cascade);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static ConcurrentDictionary<Type, bool> _dicCompareEntityPropertyValue = new ConcurrentDictionary<Type, bool>
 | 
			
		||||
        {
 | 
			
		||||
            [typeof(string)] = true,
 | 
			
		||||
            [typeof(DateTime)] = true,
 | 
			
		||||
            [typeof(DateTime?)] = true,
 | 
			
		||||
            [typeof(DateTimeOffset)] = true,
 | 
			
		||||
            [typeof(DateTimeOffset?)] = true,
 | 
			
		||||
            [typeof(TimeSpan)] = true,
 | 
			
		||||
            [typeof(TimeSpan?)] = true,
 | 
			
		||||
        };
 | 
			
		||||
        public static bool CompareEntityPropertyValue(Type type, object propvalBefore, object propvalAfter)
 | 
			
		||||
        {
 | 
			
		||||
            if (propvalBefore == null && propvalAfter == null) return true;
 | 
			
		||||
            if (type.IsNumberType() ||
 | 
			
		||||
                _dicCompareEntityPropertyValue.ContainsKey(type) ||
 | 
			
		||||
                type.IsEnum ||
 | 
			
		||||
                type.IsValueType ||
 | 
			
		||||
                type.NullableTypeOrThis().IsEnum) return object.Equals(propvalBefore, propvalAfter);
 | 
			
		||||
            if (propvalBefore == null && propvalAfter != null) return false;
 | 
			
		||||
            if (propvalBefore != null && propvalAfter == null) return false;
 | 
			
		||||
 | 
			
		||||
            if (FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(type)) {
 | 
			
		||||
                if (type.FullName.StartsWith("Newtonsoft.")) 
 | 
			
		||||
                    return object.Equals(propvalBefore.ToString(), propvalAfter.ToString());
 | 
			
		||||
 | 
			
		||||
                if (typeof(IDictionary).IsAssignableFrom(type))
 | 
			
		||||
                {
 | 
			
		||||
                    var dictBefore = (propvalBefore as IDictionary);
 | 
			
		||||
                    var dictAfter = (propvalAfter as IDictionary);
 | 
			
		||||
                    if (dictBefore.Count != dictAfter.Count) return false;
 | 
			
		||||
                    foreach (var key in dictBefore.Keys)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (dictAfter.Contains(key) == false) return false;
 | 
			
		||||
                        var valBefore = dictBefore[key];
 | 
			
		||||
                        var valAfter = dictAfter[key];
 | 
			
		||||
                        if (valBefore == null && valAfter == null) continue;
 | 
			
		||||
                        if (valBefore == null && valAfter != null) return false;
 | 
			
		||||
                        if (valBefore != null && valAfter == null) return false;
 | 
			
		||||
                        if (CompareEntityPropertyValue(valBefore.GetType(), valBefore, valAfter) == false) return false;
 | 
			
		||||
                    }
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (type.IsArrayOrList())
 | 
			
		||||
                {
 | 
			
		||||
                    var enumableBefore = propvalBefore as IEnumerable;
 | 
			
		||||
                    var enumableAfter = propvalAfter as IEnumerable;
 | 
			
		||||
                    var itorBefore = enumableBefore.GetEnumerator();
 | 
			
		||||
                    var itorAfter = enumableAfter.GetEnumerator();
 | 
			
		||||
                    while (true)
 | 
			
		||||
                    {
 | 
			
		||||
                        var moveNextBefore = itorBefore.MoveNext();
 | 
			
		||||
                        var moveNextAfter = itorAfter.MoveNext();
 | 
			
		||||
                        if (moveNextBefore != moveNextAfter) return false;
 | 
			
		||||
                        if (moveNextBefore == false) break;
 | 
			
		||||
                        var currentBefore = itorBefore.Current;
 | 
			
		||||
                        var currentAfter = itorAfter.Current;
 | 
			
		||||
                        if (currentBefore == null && enumableAfter == null) continue;
 | 
			
		||||
                        if (currentBefore == null && currentAfter != null) return false;
 | 
			
		||||
                        if (currentBefore != null && currentAfter == null) return false;
 | 
			
		||||
                        if (CompareEntityPropertyValue(currentBefore.GetType(), currentBefore, currentAfter) == false) return false;
 | 
			
		||||
                    }
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (type.FullName.StartsWith("System.") ||
 | 
			
		||||
                    type.FullName.StartsWith("Npgsql.") ||
 | 
			
		||||
                    type.FullName.StartsWith("NetTopologySuite.")) 
 | 
			
		||||
                    return object.Equals(propvalBefore, propvalAfter);
 | 
			
		||||
 | 
			
		||||
                if (type.IsClass)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var prop in type.GetProperties())
 | 
			
		||||
                    {
 | 
			
		||||
                        var valBefore = prop.GetValue(propvalBefore, new object[0]);
 | 
			
		||||
                        var valAfter = prop.GetValue(propvalAfter, new object[0]);
 | 
			
		||||
                        if (CompareEntityPropertyValue(prop.PropertyType, valBefore, valAfter) == false) return false;
 | 
			
		||||
                    }
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return object.Equals(propvalBefore, propvalAfter);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void NavigateReader(string boundaryName, IFreeSql fsql, Type rootType, object rootEntity, Action<string, TableRef, Type, List<object>> callback)
 | 
			
		||||
        {
 | 
			
		||||
            Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
 | 
			
		||||
            var statckPath = new Stack<string>();
 | 
			
		||||
            var stackValues = new List<object>();
 | 
			
		||||
            statckPath.Push("_");
 | 
			
		||||
            stackValues.Add(rootEntity);
 | 
			
		||||
            LocalNavigateReader(rootType, rootEntity);
 | 
			
		||||
            ignores.Clear();
 | 
			
		||||
 | 
			
		||||
            void LocalNavigateReader(Type entityType, object entity)
 | 
			
		||||
            {
 | 
			
		||||
                if (entity == null) return;
 | 
			
		||||
                if (entityType == null) entityType = entity.GetType();
 | 
			
		||||
                var table = fsql.CodeFirst.GetTableByEntity(entityType);
 | 
			
		||||
                if (table == null) return;
 | 
			
		||||
 | 
			
		||||
                var stateKey = fsql.GetEntityKeyString(entityType, entity, false);
 | 
			
		||||
                if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
 | 
			
		||||
                if (stateKeys.ContainsKey(stateKey)) return;
 | 
			
		||||
                stateKeys.Add(stateKey, true);
 | 
			
		||||
 | 
			
		||||
                foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
 | 
			
		||||
                {
 | 
			
		||||
                    var tbref = tr.Value;
 | 
			
		||||
                    if (tbref.Exception != null) continue;
 | 
			
		||||
                    if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
 | 
			
		||||
                    var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
 | 
			
		||||
                    if (boundaryAttr?.Break == true) continue;
 | 
			
		||||
                    switch (tbref.RefType)
 | 
			
		||||
                    {
 | 
			
		||||
                        case TableRefType.OneToOne:
 | 
			
		||||
                            var propval = table.GetPropertyValue(entity, prop.Name);
 | 
			
		||||
                            if (propval == null) continue;
 | 
			
		||||
                            statckPath.Push(prop.Name);
 | 
			
		||||
                            stackValues.Add(propval);
 | 
			
		||||
                            SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propval);
 | 
			
		||||
                            callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues);
 | 
			
		||||
                            if (boundaryAttr?.BreakThen != true)
 | 
			
		||||
                                LocalNavigateReader(tbref.RefEntityType, propval);
 | 
			
		||||
                            stackValues.RemoveAt(stackValues.Count - 1);
 | 
			
		||||
                            statckPath.Pop();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.OneToMany:
 | 
			
		||||
                            var propvalOtm = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
 | 
			
		||||
                            if (propvalOtm == null) continue;
 | 
			
		||||
                            SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propvalOtm);
 | 
			
		||||
                            var propvalOtmList = new List<object>();
 | 
			
		||||
                            foreach (var val in propvalOtm)
 | 
			
		||||
                                propvalOtmList.Add(val);
 | 
			
		||||
                            statckPath.Push($"{prop.Name}[]");
 | 
			
		||||
                            stackValues.Add(propvalOtmList.ToArray());
 | 
			
		||||
                            callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues);
 | 
			
		||||
                            if (boundaryAttr?.BreakThen != true)
 | 
			
		||||
                                foreach (var val in propvalOtm)
 | 
			
		||||
                                    LocalNavigateReader(tbref.RefEntityType, val);
 | 
			
		||||
                            stackValues.RemoveAt(stackValues.Count - 1);
 | 
			
		||||
                            statckPath.Pop();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToMany:
 | 
			
		||||
                            var middleValues = GetManyToManyObjects(fsql, table, tbref, entity, prop).ToArray();
 | 
			
		||||
                            if (middleValues == null) continue;
 | 
			
		||||
                            statckPath.Push($"{prop.Name}[]");
 | 
			
		||||
                            stackValues.Add(middleValues);
 | 
			
		||||
                            callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefMiddleEntityType, stackValues);
 | 
			
		||||
                            stackValues.RemoveAt(stackValues.Count - 1);
 | 
			
		||||
                            statckPath.Pop();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.PgArrayToMany:
 | 
			
		||||
                        case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void MapEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo)
 | 
			
		||||
        {
 | 
			
		||||
            Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
 | 
			
		||||
            LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo, true);
 | 
			
		||||
            ignores.Clear();
 | 
			
		||||
 | 
			
		||||
            void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo, bool cascade)
 | 
			
		||||
            {
 | 
			
		||||
                if (entityFrom == null || entityTo == null) return;
 | 
			
		||||
                if (entityType == null) entityType = entityFrom?.GetType() ?? entityTo?.GetType();
 | 
			
		||||
                var table = fsql.CodeFirst.GetTableByEntity(entityType);
 | 
			
		||||
                if (table == null) return;
 | 
			
		||||
 | 
			
		||||
                var stateKey = fsql.GetEntityKeyString(entityType, entityFrom, false);
 | 
			
		||||
                if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
 | 
			
		||||
                if (stateKeys.ContainsKey(stateKey)) return;
 | 
			
		||||
                stateKeys.Add(stateKey, true);
 | 
			
		||||
 | 
			
		||||
                foreach (var prop in table.Properties.Values)
 | 
			
		||||
                {
 | 
			
		||||
                    if (table.ColumnsByCsIgnore.ContainsKey(prop.Name)) continue;
 | 
			
		||||
                    if (table.ColumnsByCs.ContainsKey(prop.Name))
 | 
			
		||||
                    {
 | 
			
		||||
                        table.SetPropertyValue(entityTo, prop.Name, table.GetPropertyValue(entityFrom, prop.Name));
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (cascade == false) continue;
 | 
			
		||||
                    var tbref = table.GetTableRef(prop.Name, false);
 | 
			
		||||
                    if (tbref == null) continue;
 | 
			
		||||
                    var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
 | 
			
		||||
                    if (boundaryAttr?.Break == true) continue;
 | 
			
		||||
                    var propvalFrom = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, entityType, entityFrom, prop.Name);
 | 
			
		||||
                    if (propvalFrom == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, null);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    switch (tbref.RefType)
 | 
			
		||||
                    {
 | 
			
		||||
                        case TableRefType.OneToOne:
 | 
			
		||||
                            var propvalTo = tbref.RefEntityType.CreateInstanceGetDefaultValue();
 | 
			
		||||
                            SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom);
 | 
			
		||||
                            LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.OneToMany:
 | 
			
		||||
                            SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom);
 | 
			
		||||
                            LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToMany:
 | 
			
		||||
                            LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, false);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.PgArrayToMany:
 | 
			
		||||
                        case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            void LocalMapEntityValueCollection(Type entityType, object entityFrom, object entityTo, TableRef tbref, IEnumerable propvalFrom, PropertyInfo prop, bool cascade)
 | 
			
		||||
            {
 | 
			
		||||
                var propvalTo = typeof(List<>).MakeGenericType(tbref.RefEntityType).CreateInstanceGetDefaultValue();
 | 
			
		||||
                var propvalToIList = propvalTo as IList;
 | 
			
		||||
                foreach (var fromItem in propvalFrom)
 | 
			
		||||
                {
 | 
			
		||||
                    var toItem = tbref.RefEntityType.CreateInstanceGetDefaultValue();
 | 
			
		||||
                    LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem, cascade);
 | 
			
		||||
                    propvalToIList.Add(toItem);
 | 
			
		||||
                }
 | 
			
		||||
                var propvalType = prop.PropertyType.GetGenericTypeDefinition();
 | 
			
		||||
                if (propvalType == typeof(List<>) || propvalType == typeof(ICollection<>))
 | 
			
		||||
                    EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo);
 | 
			
		||||
                else if (propvalType == typeof(ObservableCollection<>))
 | 
			
		||||
                {
 | 
			
		||||
                    //var propvalTypeOcCtor = typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType).GetConstructor(new[] { typeof(List<>).MakeGenericType(tbref.RefEntityType) });
 | 
			
		||||
                    var propvalTypeOc = Activator.CreateInstance(typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType), new object[] { propvalTo });
 | 
			
		||||
                    EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTypeOc);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static ConcurrentDictionary<string, ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>> _dicGetAutoIncludeQuery = new ConcurrentDictionary<string, ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>>();
 | 
			
		||||
        public static ISelect<TEntity> GetAutoIncludeQuery<TEntity>(string boundaryName, ISelect<TEntity> select)
 | 
			
		||||
        {
 | 
			
		||||
            var select0p = select as Select0Provider;
 | 
			
		||||
            var table0Type = select0p._tables[0].Table.Type;
 | 
			
		||||
            var func = _dicGetAutoIncludeQuery.GetOrAdd(boundaryName ?? "", bn => new ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>())
 | 
			
		||||
                .GetOrAdd(typeof(TEntity), t => new ConcurrentDictionary<Type, Action<ISelect0>>())
 | 
			
		||||
                .GetOrAdd(table0Type, t =>
 | 
			
		||||
             {
 | 
			
		||||
                 var parmExp1 = Expression.Parameter(typeof(ISelect0));
 | 
			
		||||
                 var parmNavigateParameterExp = Expression.Parameter(typeof(TEntity), "a");
 | 
			
		||||
                 var parmQueryExp = Expression.Convert(parmExp1, typeof(ISelect<>).MakeGenericType(typeof(TEntity)));
 | 
			
		||||
                 var exp = LocalGetAutoIncludeQuery(parmQueryExp, 1, t, parmNavigateParameterExp, parmNavigateParameterExp, new Stack<Type>());
 | 
			
		||||
                 return Expression.Lambda<Action<ISelect0>>(exp, parmExp1).Compile();
 | 
			
		||||
             });
 | 
			
		||||
            func(select);
 | 
			
		||||
            return select;
 | 
			
		||||
            Expression LocalGetAutoIncludeQuery(Expression queryExp, int depth, Type entityType, ParameterExpression navigateParameterExp, Expression navigatePathExp, Stack<Type> ignores)
 | 
			
		||||
            {
 | 
			
		||||
                if (ignores.Any(a => a == entityType)) return queryExp;
 | 
			
		||||
                ignores.Push(entityType);
 | 
			
		||||
                var table = select0p._orm.CodeFirst.GetTableByEntity(entityType);
 | 
			
		||||
                if (table == null) return queryExp;
 | 
			
		||||
                foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
 | 
			
		||||
                {
 | 
			
		||||
                    var tbref = tr.Value;
 | 
			
		||||
                    if (tbref.Exception != null) continue;
 | 
			
		||||
                    if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
 | 
			
		||||
                    var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
 | 
			
		||||
                    if (boundaryAttr?.Break == true) continue;
 | 
			
		||||
                    Expression navigateExp = Expression.MakeMemberAccess(navigatePathExp, prop);
 | 
			
		||||
                    //var lambdaAlias = (char)((byte)'a' + (depth - 1));
 | 
			
		||||
                    switch (tbref.RefType)
 | 
			
		||||
                    {
 | 
			
		||||
                        case TableRefType.OneToOne:
 | 
			
		||||
                            if (ignores.Any(a => a == tbref.RefEntityType)) break;
 | 
			
		||||
                            LocalInclude(tbref, navigateExp);
 | 
			
		||||
                            if (boundaryAttr?.BreakThen != true)
 | 
			
		||||
                                queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.OneToMany:
 | 
			
		||||
                            LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen != true);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToMany:
 | 
			
		||||
                            LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.PgArrayToMany:
 | 
			
		||||
                            if (boundaryAttr?.Break == false)
 | 
			
		||||
                                LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
 | 
			
		||||
                            if (boundaryAttr?.Break == false)
 | 
			
		||||
                            {
 | 
			
		||||
                                LocalInclude(tbref, navigateExp);
 | 
			
		||||
                                if (boundaryAttr?.BreakThen == false)
 | 
			
		||||
                                    queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores);
 | 
			
		||||
                            }
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                ignores.Pop();
 | 
			
		||||
                return queryExp;
 | 
			
		||||
                void LocalInclude(TableRef tbref, Expression exp)
 | 
			
		||||
                {
 | 
			
		||||
                    var incMethod = queryExp.Type.GetMethod("Include");
 | 
			
		||||
                    if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany.Replace("IncludeMany", "Include"));
 | 
			
		||||
                    queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType),
 | 
			
		||||
                        Expression.Lambda(typeof(Func<,>).MakeGenericType(entityType, tbref.RefEntityType), exp, navigateParameterExp));
 | 
			
		||||
                }
 | 
			
		||||
                void LocalIncludeMany(TableRef tbref, Expression exp, bool isthen)
 | 
			
		||||
                {
 | 
			
		||||
                    var funcType = typeof(Func<,>).MakeGenericType(entityType, typeof(IEnumerable<>).MakeGenericType(tbref.RefEntityType));
 | 
			
		||||
                    var navigateSelector = Expression.Lambda(funcType, exp, navigateParameterExp);
 | 
			
		||||
                    var incMethod = queryExp.Type.GetMethod("IncludeMany");
 | 
			
		||||
                    if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany);
 | 
			
		||||
                    LambdaExpression navigateThen = null;
 | 
			
		||||
                    var navigateThenType = typeof(Action<>).MakeGenericType(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType));
 | 
			
		||||
                    var thenParameter = Expression.Parameter(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType), "then");
 | 
			
		||||
                    Expression paramQueryExp = thenParameter;
 | 
			
		||||
                    var paramNavigateParameterExp = Expression.Parameter(tbref.RefEntityType, string.Concat((char)((byte)'a' + (depth - 1))));
 | 
			
		||||
                    if (isthen) paramQueryExp = LocalGetAutoIncludeQuery(paramQueryExp, depth + 1, tbref.RefEntityType, paramNavigateParameterExp, paramNavigateParameterExp, ignores);
 | 
			
		||||
                    navigateThen = Expression.Lambda(navigateThenType, paramQueryExp, thenParameter);
 | 
			
		||||
                    queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType), navigateSelector, navigateThen);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetAutoIncludeQueryStaicCode(string boundaryName, IFreeSql fsql, Type rootEntityType)
 | 
			
		||||
        {
 | 
			
		||||
            return $"//fsql.Select<{rootEntityType.Name}>()\r\nSelectDiy{LocalGetAutoIncludeQueryStaicCode(1, rootEntityType, "", new Stack<Type>())}";
 | 
			
		||||
            string LocalGetAutoIncludeQueryStaicCode(int depth, Type entityType, string navigatePath, Stack<Type> ignores)
 | 
			
		||||
            {
 | 
			
		||||
                var code = new StringBuilder();
 | 
			
		||||
                if (ignores.Any(a => a == entityType)) return null;
 | 
			
		||||
                ignores.Push(entityType);
 | 
			
		||||
                var table = fsql.CodeFirst.GetTableByEntity(entityType);
 | 
			
		||||
                if (table == null) return null;
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}.";
 | 
			
		||||
                foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
 | 
			
		||||
                {
 | 
			
		||||
                    var tbref = tr.Value;
 | 
			
		||||
                    if (tbref.Exception != null) continue;
 | 
			
		||||
                    if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
 | 
			
		||||
                    var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
 | 
			
		||||
                    if (boundaryAttr?.Break == true) continue;
 | 
			
		||||
                    var navigateExpression = $"{navigatePath}{tr.Key}";
 | 
			
		||||
                    var depthTab = "".PadLeft(depth * 4);
 | 
			
		||||
                    var lambdaAlias = (char)((byte)'a' + (depth - 1));
 | 
			
		||||
                    var lambdaStr = $"{lambdaAlias} => {lambdaAlias}.";
 | 
			
		||||
                    switch (tbref.RefType)
 | 
			
		||||
                    {
 | 
			
		||||
                        case TableRefType.OneToOne:
 | 
			
		||||
                            if (ignores.Any(a => a == tbref.RefEntityType)) break;
 | 
			
		||||
                            code.Append("\r\n").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")");
 | 
			
		||||
                            if (boundaryAttr?.BreakThen != true)
 | 
			
		||||
                                code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores));
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.OneToMany:
 | 
			
		||||
                            code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
 | 
			
		||||
                            if (boundaryAttr?.BreakThen != true)
 | 
			
		||||
                            {
 | 
			
		||||
                                var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
 | 
			
		||||
                                if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
 | 
			
		||||
                            }
 | 
			
		||||
                            code.Append(")");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToMany:
 | 
			
		||||
                            code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
 | 
			
		||||
                            if (boundaryAttr?.BreakThen == false)
 | 
			
		||||
                            {
 | 
			
		||||
                                var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
 | 
			
		||||
                                if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
 | 
			
		||||
                            }
 | 
			
		||||
                            code.Append(")");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.PgArrayToMany:
 | 
			
		||||
                            code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
 | 
			
		||||
                            if (boundaryAttr?.BreakThen == false)
 | 
			
		||||
                            {
 | 
			
		||||
                                var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
 | 
			
		||||
                                if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
 | 
			
		||||
                            }
 | 
			
		||||
                            code.Append(")");
 | 
			
		||||
                            break;
 | 
			
		||||
                        case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
 | 
			
		||||
                            code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")");
 | 
			
		||||
                            if (boundaryAttr?.BreakThen == false)
 | 
			
		||||
                                code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores));
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                ignores.Pop();
 | 
			
		||||
                return code.ToString();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static List<object> GetManyToManyObjects(IFreeSql fsql, TableInfo table, TableRef tbref, object entity, PropertyInfo prop)
 | 
			
		||||
        {
 | 
			
		||||
            if (tbref.RefType != TableRefType.ManyToMany) return null;
 | 
			
		||||
            var rights = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
 | 
			
		||||
            if (rights == null) return null;
 | 
			
		||||
            var middles = new List<object>();
 | 
			
		||||
            var leftpkvals = new object[tbref.Columns.Count];
 | 
			
		||||
            for (var x = 0; x < tbref.Columns.Count; x++)
 | 
			
		||||
                leftpkvals[x] = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, table.Type, entity, tbref.Columns[x].CsName));
 | 
			
		||||
            foreach (var right in rights)
 | 
			
		||||
            {
 | 
			
		||||
                var midval = tbref.RefMiddleEntityType.CreateInstanceGetDefaultValue();
 | 
			
		||||
                for (var x = 0; x < tbref.Columns.Count; x++)
 | 
			
		||||
                    EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, leftpkvals[x]);
 | 
			
		||||
 | 
			
		||||
                for (var x = tbref.Columns.Count; x < tbref.MiddleColumns.Count; x++)
 | 
			
		||||
                {
 | 
			
		||||
                    var refcol = tbref.RefColumns[x - tbref.Columns.Count];
 | 
			
		||||
                    var refval = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, tbref.RefEntityType, right, refcol.CsName);
 | 
			
		||||
                    if (refval == refcol.CsType.CreateInstanceGetDefaultValue()) throw new Exception($"ManyToMany 关联对象的主键属性({tbref.RefEntityType.DisplayCsharp()}.{refcol.CsName})不能为空");
 | 
			
		||||
                    refval = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, refval);
 | 
			
		||||
                    EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, refval);
 | 
			
		||||
                }
 | 
			
		||||
                middles.Add(midval);
 | 
			
		||||
            }
 | 
			
		||||
            return middles;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem)
 | 
			
		||||
        {
 | 
			
		||||
            switch (tbref.RefType)
 | 
			
		||||
            {
 | 
			
		||||
                case TableRefType.OneToOne:
 | 
			
		||||
                    if (rightItem == null) return;
 | 
			
		||||
                    for (var idx = 0; idx < tbref.Columns.Count; idx++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var colval = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName));
 | 
			
		||||
                        EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName, colval);
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case TableRefType.OneToMany:
 | 
			
		||||
                    if (rightItem == null) return;
 | 
			
		||||
                    var rightEachOtm = rightItem as IEnumerable;
 | 
			
		||||
                    if (rightEachOtm == null) break;
 | 
			
		||||
                    var leftColValsOtm = new object[tbref.Columns.Count];
 | 
			
		||||
                    for (var idx = 0; idx < tbref.Columns.Count; idx++)
 | 
			
		||||
                        leftColValsOtm[idx] = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName));
 | 
			
		||||
                    foreach (var rightEle in rightEachOtm)
 | 
			
		||||
                        for (var idx = 0; idx < tbref.Columns.Count; idx++)
 | 
			
		||||
                            EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightEle, tbref.RefColumns[idx].CsName, leftColValsOtm[idx]);
 | 
			
		||||
                    break;
 | 
			
		||||
                case TableRefType.ManyToOne:
 | 
			
		||||
                    for (var idx = 0; idx < tbref.RefColumns.Count; idx++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var colval = rightItem == null ? 
 | 
			
		||||
                            tbref.Columns[idx].CsType.CreateInstanceGetDefaultValue() : 
 | 
			
		||||
                            Utils.GetDataReaderValue(tbref.Columns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName));
 | 
			
		||||
                        EntityUtilExtensions.SetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName, colval);
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
using FreeSql;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Linq.Expressions;
 | 
			
		||||
 | 
			
		||||
public static class FreeSqlAggregateRootRepositoryGlobalExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static IBaseRepository<TEntity> GetAggregateRootRepository<TEntity>(this IFreeSql that) where TEntity : class
 | 
			
		||||
    {
 | 
			
		||||
        return new AggregateRootRepository<TEntity>(that);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user