From a590b8aa7bd8386d65c3592e1a56839845778f4d Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Sat, 3 Sep 2022 17:59:57 +0800 Subject: [PATCH] AggregateRootRepository --- FreeSql.Repository/AggregateRootModel.cs | 24 + .../AggregateRootRepositoryAsync.cs | 24 +- .../AggregateRootRepositorySync.cs | 61 +- FreeSql.Repository/AggregateRootUtils.cs | 905 +++++++++--------- .../AggregateRootRepositoryTest.cs | 48 +- .../AggregateRootRepositoryTest2.cs | 9 +- 6 files changed, 577 insertions(+), 494 deletions(-) create mode 100644 FreeSql.Repository/AggregateRootModel.cs diff --git a/FreeSql.Repository/AggregateRootModel.cs b/FreeSql.Repository/AggregateRootModel.cs new file mode 100644 index 00000000..979a9c43 --- /dev/null +++ b/FreeSql.Repository/AggregateRootModel.cs @@ -0,0 +1,24 @@ +using FreeSql; +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.Internal.Model +{ + public class AggregateRootTrackingChangeInfo + { + public List> InsertLog { get; } = new List>(); + public List>> UpdateLog { get; } = new List>>(); + public List> DeleteLog { get; } = new List>(); + } +} \ No newline at end of file diff --git a/FreeSql.Repository/AggregateRootRepositoryAsync.cs b/FreeSql.Repository/AggregateRootRepositoryAsync.cs index fde1bd79..4ac32a9e 100644 --- a/FreeSql.Repository/AggregateRootRepositoryAsync.cs +++ b/FreeSql.Repository/AggregateRootRepositoryAsync.cs @@ -17,18 +17,22 @@ namespace FreeSql { partial class AggregateRootRepository { - public Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => _repository.InsertAsync(entity, cancellationToken); - public Task> InsertAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repository.InsertAsync(entitys, cancellationToken); - public Task InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repository.InsertOrUpdateAsync(entity, cancellationToken); - public Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default) => _repository.SaveManyAsync(entity, propertyName, cancellationToken); + //以下临时调用同步方法 + public Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => Task.FromResult(Insert(entity)); + public Task> InsertAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => Task.FromResult(Insert(entitys)); + public Task InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => Task.FromResult(InsertOrUpdate(entity)); + async public Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default) + { + SaveMany(entity, propertyName); + } - public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repository.UpdateAsync(entity, cancellationToken); - public Task UpdateAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repository.UpdateAsync(entitys, cancellationToken); + public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => Task.FromResult(Update(entity)); + public Task UpdateAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => Task.FromResult(Update(entitys)); - public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => _repository.DeleteAsync(entity, cancellationToken); - public Task DeleteAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repository.DeleteAsync(entitys, cancellationToken); - public Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) => _repository.DeleteAsync(predicate, cancellationToken); - public Task> DeleteCascadeByDatabaseAsync(Expression> predicate, CancellationToken cancellationToken = default) => _repository.DeleteCascadeByDatabaseAsync(predicate, cancellationToken); + public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => Task.FromResult(Delete(entity)); + public Task DeleteAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => Task.FromResult(Delete(entitys)); + public Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) => Task.FromResult(Delete(predicate)); + public Task> DeleteCascadeByDatabaseAsync(Expression> predicate, CancellationToken cancellationToken = default) => Task.FromResult(DeleteCascadeByDatabase(predicate)); } } diff --git a/FreeSql.Repository/AggregateRootRepositorySync.cs b/FreeSql.Repository/AggregateRootRepositorySync.cs index fb6ce851..34487011 100644 --- a/FreeSql.Repository/AggregateRootRepositorySync.cs +++ b/FreeSql.Repository/AggregateRootRepositorySync.cs @@ -91,7 +91,7 @@ namespace FreeSql localAffrows += ret.Count; foreach (var entity in entitys) LocalCanAggregateRoot(repository.EntityType, entity, true); - foreach (var tr in table.GetAllTableRef()) + foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) { var tbref = tr.Value; if (tbref.Exception != null) continue; @@ -180,36 +180,35 @@ namespace FreeSql } protected virtual int UpdateAggregateRoot(IEnumerable entitys) { - List> insertLog = new List>(); - List>> updateLog = new List>>(); - List> deleteLog = new List>(); + 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(Orm, EntityType, state.Value, entity, null, insertLog, updateLog, deleteLog); + AggregateRootUtils.CompareEntityValue(Orm, EntityType, state.Value, entity, null, tracking); } var affrows = 0; - DisposeChildRepositorys(); - var insertLogDict = insertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => insertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray()); + 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) { - InsertAggregateRootStatic(GetChildRepository(il.Key), GetChildRepository, il.Value, out var affrowsOut); + var repo = GetChildRepository(il.Key); + InsertAggregateRootStatic(repo, GetChildRepository, il.Value, out var affrowsOut); affrows += affrowsOut; } - for (var a = 0; a < deleteLog.Count - 1; a++) - affrows += Orm.Delete().AsType(deleteLog[a].Item1).WhereDynamic(deleteLog[a].Item2).ExecuteAffrows(); + for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--) + affrows += Orm.Delete().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule) + .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows(); - var updateLogDict = updateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => updateLog.Where(b => b.Item1 == a.Key).Select(b => + var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => NativeTuple.Create(b.Item2, b.Item3, string.Join(",", b.Item4.OrderBy(c => c)), b.Item4)).ToArray()); var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.Item3, b => a.Value.Where(c => c.Item3 == b.Item3).ToArray())); foreach (var dl in updateLogDict2) { foreach (var dl2 in dl.Value) { - affrows += Orm.Update().AsType(dl.Key) + affrows += Orm.Update().AsType(dl.Key).AsTable(_asTableRule) .SetSource(dl2.Value.Select(a => a.Item2).ToArray()) .UpdateColumns(dl2.Value.First().Item4.ToArray()) .ExecuteAffrows(); @@ -223,27 +222,29 @@ namespace FreeSql } protected virtual int DeleteAggregateRoot(IEnumerable entitys, List deletedOutput = null) { - List> insertLog = new List>(); - List>> updateLog = new List>>(); - List> deleteLog = new List>(); + var tracking = new AggregateRootTrackingChangeInfo(); foreach (var entity in entitys) { var stateKey = Orm.GetEntityKeyString(EntityType, entity, false); - AggregateRootUtils.CompareEntityValue(Orm, EntityType, entity, null, null, insertLog, updateLog, deleteLog); + AggregateRootUtils.CompareEntityValue(Orm, EntityType, entity, null, null, tracking); _states.Remove(stateKey); } - if (deletedOutput != null) deletedOutput.AddRange(deleteLog.Select(a => a.Item2)); - return deleteLog.Count; + var affrows = 0; + for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--) + { + affrows += Orm.Delete().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule) + .WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows(); + if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2); + } + return affrows; } protected virtual void SaveManyAggregateRoot(TEntity entity, string propertyName) { - List> insertLog = new List>(); - List>> updateLog = new List>>(); - List> deleteLog = new List>(); + 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(Orm, EntityType, state.Value, entity, propertyName, insertLog, updateLog, deleteLog); + AggregateRootUtils.CompareEntityValue(Orm, EntityType, state.Value, entity, propertyName, tracking); Attach(entity); } @@ -273,9 +274,7 @@ namespace FreeSql { if (data == null) data = _dataEditing; if (data == null) return 0; - List> insertLog = new List>(); - List>> updateLog = new List>>(); - List> deleteLog = new List>(); + var tracking = new AggregateRootTrackingChangeInfo(); try { var addList = new List(); @@ -285,23 +284,23 @@ namespace FreeSql var key = Orm.GetEntityKeyString(EntityType, item, false); if (_statesEditing.TryRemove(key, out var state) == false) { - insertLog.Add(NativeTuple.Create(EntityType, (object)item)); + tracking.InsertLog.Add(NativeTuple.Create(EntityType, (object)item)); continue; } _states[key] = state; - AggregateRootUtils.CompareEntityValue(Orm, EntityType, state.Value, item, null, insertLog, updateLog, deleteLog); + AggregateRootUtils.CompareEntityValue(Orm, EntityType, state.Value, item, null, tracking); } foreach (var item in _statesEditing.Values.OrderBy(a => a.Time)) - { - AggregateRootUtils.CompareEntityValue(Orm, EntityType, item, null, null, insertLog, updateLog, deleteLog); - } + AggregateRootUtils.CompareEntityValue(Orm, EntityType, item, null, null, tracking); + + } finally { _dataEditing = null; _statesEditing.Clear(); } - return insertLog.Count + updateLog.Count + deleteLog.Count; + return tracking.InsertLog.Count + tracking.UpdateLog.Count + tracking.DeleteLog.Count; } } diff --git a/FreeSql.Repository/AggregateRootUtils.cs b/FreeSql.Repository/AggregateRootUtils.cs index 214c3a62..ede67833 100644 --- a/FreeSql.Repository/AggregateRootUtils.cs +++ b/FreeSql.Repository/AggregateRootUtils.cs @@ -13,488 +13,491 @@ using System.Linq.Expressions; using System.Reflection; using System.Text; -static class AggregateRootUtils +namespace FreeSql { - public static void CompareEntityValue(IFreeSql fsql, Type rootEntityType, object rootEntityBefore, object rootEntityAfter, string rootNavigatePropertyName, - List> insertLog, - List>> updateLog, - List> deleteLog) + public static class AggregateRootUtils { - Dictionary> ignores = new Dictionary>(); - LocalCompareEntityValue(rootEntityType, rootEntityBefore, rootEntityAfter, rootNavigatePropertyName); - ignores.Clear(); - - void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName) + public static void CompareEntityValue(IFreeSql fsql, Type rootEntityType, object rootEntityBefore, object rootEntityAfter, string rootNavigatePropertyName, AggregateRootTrackingChangeInfo tracking) { - if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType(); + Dictionary> ignores = new Dictionary>(); + LocalCompareEntityValue(rootEntityType, rootEntityBefore, rootEntityAfter, rootNavigatePropertyName); + ignores.Clear(); - if (entityBefore != null) + void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName) { - var stateKey = $":before://{fsql.GetEntityKeyString(entityType, entityBefore, false)}"; - if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary()); - 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()); - if (stateKeys.ContainsKey(stateKey)) return; - stateKeys.Add(stateKey, true); - } + if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType(); - var table = fsql.CodeFirst.GetTableByEntity(entityType); - if (table == null) return; - if (entityBefore == null && entityAfter == null) return; - if (entityBefore == null && entityAfter != null) - { - insertLog.Add(NativeTuple.Create(entityType, entityAfter)); - return; - } - if (entityBefore != null && entityAfter == null) - { - deleteLog.Add(NativeTuple.Create(entityType, new[] { entityBefore })); - NavigateReader(fsql, entityType, entityBefore, (path, tr, ct, stackvs) => + if (entityBefore != null) { - var dellist = stackvs.First() as object[] ?? new[] { stackvs.First() }; - deleteLog.Add(NativeTuple.Create(ct, dellist)); - }); - return; - } - var changes = new List(); - 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); - continue; + var stateKey = $":before://{fsql.GetEntityKeyString(entityType, entityBefore, false)}"; + if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary()); + if (stateKeys.ContainsKey(stateKey)) return; + stateKeys.Add(stateKey, true); } - } - if (changes.Any()) - updateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes)); - - foreach (var tr in table.GetAllTableRef()) - { - var tbref = tr.Value; - if (tbref.Exception != null) continue; - if (table.Properties.TryGetValue(tr.Key, out var prop) == false) 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) + if (entityAfter != null) { - case TableRefType.OneToOne: - SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore); - SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter); - LocalCompareEntityValue(tbref.RefEntityType, propvalBefore, propvalAfter, null); - break; - case TableRefType.OneToMany: - SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore); - SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter); - LocalCompareEntityValueCollection(tbref, propvalBefore as IEnumerable, propvalAfter as IEnumerable); - break; - case TableRefType.ManyToMany: - var middleValuesBefore = GetManyToManyObjects(fsql, table, tbref, entityBefore, prop); - var middleValuesAfter = GetManyToManyObjects(fsql, table, tbref, entityAfter, prop); - LocalCompareEntityValueCollection(tbref, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable); - break; - case TableRefType.PgArrayToMany: - case TableRefType.ManyToOne: //不属于聚合根 - break; + var stateKey = $":after://{fsql.GetEntityKeyString(entityType, entityAfter, false)}"; + if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary()); + if (stateKeys.ContainsKey(stateKey)) return; + stateKeys.Add(stateKey, true); } - } - } - void LocalCompareEntityValueCollection(TableRef tbref, IEnumerable collectionBefore, IEnumerable collectionAfter) - { - var elementType = tbref.RefType == TableRefType.ManyToMany ? tbref.RefMiddleEntityType : tbref.RefEntityType; - if (collectionBefore == null && collectionAfter == null) return; - if (collectionBefore == null && collectionAfter != null) - { - foreach (var item in collectionAfter) - insertLog.Add(NativeTuple.Create(elementType, item)); - return; - } - if (collectionBefore != null && collectionAfter == null) - { - //foreach (var item in collectionBefore as IEnumerable) - //{ - // deleteLog.Add(NativeTuple.Create(elementType, new[] { item })); - // NavigateReader(fsql, elementType, item, (path, tr, ct, stackvs) => - // { - // var dellist = stackvs.First() as object[] ?? new [] { stackvs.First() }; - // deleteLog.Add(NativeTuple.Create(ct, dellist)); - // }); - //} - return; - } - Dictionary dictBefore = new Dictionary(); - Dictionary dictAfter = new Dictionary(); - 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) insertLog.Add(NativeTuple.Create(elementType, item)); - else dictAfter.Add(key, item); - } - foreach (var key in dictBefore.Keys.ToArray()) - { - if (dictAfter.ContainsKey(key) == false) + + var table = fsql.CodeFirst.GetTableByEntity(entityType); + if (table == null) return; + if (entityBefore == null && entityAfter == null) return; + if (entityBefore == null && entityAfter != null) { - var value = dictBefore[key]; - deleteLog.Add(NativeTuple.Create(elementType, new[] { value })); - NavigateReader(fsql, elementType, value, (path, tr, ct, stackvs) => - { - var dellist = stackvs.First() as object[] ?? new[] { stackvs.First() }; - deleteLog.Add(NativeTuple.Create(ct, dellist)); - }); - dictBefore.Remove(key); - } - } - foreach (var key in dictAfter.Keys.ToArray()) - { - if (dictBefore.ContainsKey(key) == false) - { - insertLog.Add(NativeTuple.Create(elementType, dictAfter[key])); - dictAfter.Remove(key); - } - } - foreach (var key in dictBefore.Keys) - LocalCompareEntityValue(elementType, dictBefore[key], dictAfter[key], null); - } - } - - public static void NavigateReader(IFreeSql fsql, Type rootType, object rootEntity, Action> callback) - { - Dictionary> ignores = new Dictionary>(); - var statckPath = new Stack(); - var stackValues = new List(); - 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()); - if (stateKeys.ContainsKey(stateKey)) return; - stateKeys.Add(stateKey, true); - - foreach (var tr in table.GetAllTableRef()) - { - var tbref = tr.Value; - if (tbref.Exception != null) continue; - if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; - switch (tbref.RefType) - { - case TableRefType.OneToOne: - var propval = table.GetPropertyValue(entity, prop.Name); - statckPath.Push(prop.Name); - stackValues.Add(propval); - SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propval); - callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); - LocalNavigateReader(tbref.RefEntityType, propval); - stackValues.RemoveAt(stackValues.Count - 1); - statckPath.Pop(); - break; - case TableRefType.OneToMany: - var propvalOtm = table.GetPropertyValue(entity, prop.Name); - SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propvalOtm); - var propvalOtmList = new List(); - foreach (var val in propvalOtm as IEnumerable) - propvalOtmList.Add(val); - statckPath.Push($"{prop.Name}[]"); - stackValues.Add(propvalOtmList.ToArray()); - callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); - foreach (var val in propvalOtm as IEnumerable) - LocalNavigateReader(tbref.RefEntityType, val); - stackValues.RemoveAt(stackValues.Count - 1); - statckPath.Pop(); - break; - case TableRefType.ManyToMany: - var middleValues = GetManyToManyObjects(fsql, table, tbref, entity, prop).ToArray(); - statckPath.Push($"{prop.Name}[]"); - stackValues.Add(middleValues); - callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); - stackValues.RemoveAt(stackValues.Count - 1); - statckPath.Pop(); - break; - case TableRefType.PgArrayToMany: - case TableRefType.ManyToOne: //不属于聚合根 - break; - } - } - } - } - - public static void MapEntityValue(IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo) - { - Dictionary> ignores = new Dictionary>(); - LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo); - ignores.Clear(); - - void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo) - { - 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()); - 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; - } - var tbref = table.GetTableRef(prop.Name, false); - if (tbref == null) continue; - var propvalFrom = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, entityType, entityFrom, prop.Name); - if (propvalFrom == null) - { - EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, null); + tracking.InsertLog.Add(NativeTuple.Create(entityType, entityAfter)); return; } - switch (tbref.RefType) + if (entityBefore != null && entityAfter == null) { - case TableRefType.OneToOne: - var propvalTo = tbref.RefEntityType.CreateInstanceGetDefaultValue(); - SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom); - LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo); - 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, true); - break; - case TableRefType.ManyToMany: - LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, false); - break; - case TableRefType.PgArrayToMany: - case TableRefType.ManyToOne: //不属于聚合根 - break; + tracking.DeleteLog.Add(NativeTuple.Create(entityType, new[] { entityBefore })); + NavigateReader(fsql, entityType, entityBefore, (path, tr, ct, stackvs) => + { + var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() }; + tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist)); + }); + return; } - } - } - 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(); - if (cascade) LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem); - else EntityUtilExtensions.MapEntityValue(fsql, tbref.RefEntityType, fromItem, toItem); - 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); - } - } - } + var changes = new List(); + 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); + continue; + } + } + if (changes.Any()) + tracking.UpdateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes)); - static ConcurrentDictionary>> _dicGetAutoIncludeQuery = new ConcurrentDictionary>>(); - public static ISelect GetAutoIncludeQuery(ISelect select) - { - var select0p = select as Select0Provider; - var table0Type = select0p._tables[0].Table.Type; - var func = _dicGetAutoIncludeQuery.GetOrAdd(typeof(TEntity), t => new ConcurrentDictionary>()).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()); - return Expression.Lambda>(exp, parmExp1).Compile(); - }); - func(select); - return select; - Expression LocalGetAutoIncludeQuery(Expression queryExp, int depth, Type entityType, ParameterExpression navigateParameterExp, Expression navigatePathExp, Stack 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()) - { - var tbref = tr.Value; - if (tbref.Exception != null) continue; - if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue; - Expression navigateExp = Expression.MakeMemberAccess(navigatePathExp, prop); - //var lambdaAlias = (char)((byte)'a' + (depth - 1)); - switch (tbref.RefType) + foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key)) { - case TableRefType.OneToOne: - if (ignores.Any(a => a == tbref.RefEntityType)) break; - LocalInclude(tbref, navigateExp); - queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores); - break; - case TableRefType.OneToMany: - LocalIncludeMany(tbref, navigateExp, true); - break; - case TableRefType.ManyToMany: - LocalIncludeMany(tbref, navigateExp, false); - break; - case TableRefType.PgArrayToMany: - break; - case TableRefType.ManyToOne: //不属于聚合根 - break; + var tbref = tr.Value; + if (tbref.Exception != null) continue; + if (table.Properties.TryGetValue(tr.Key, out var prop) == false) 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); + break; + case TableRefType.OneToMany: + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore); + SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter); + LocalCompareEntityValueCollection(tbref, propvalBefore as IEnumerable, propvalAfter as IEnumerable); + break; + case TableRefType.ManyToMany: + var middleValuesBefore = GetManyToManyObjects(fsql, table, tbref, entityBefore, prop); + var middleValuesAfter = GetManyToManyObjects(fsql, table, tbref, entityAfter, prop); + LocalCompareEntityValueCollection(tbref, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable); + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //不属于聚合根 + break; + } } } - ignores.Pop(); - return queryExp; - void LocalInclude(TableRef tbref, Expression exp) + void LocalCompareEntityValueCollection(TableRef tbref, IEnumerable collectionBefore, IEnumerable collectionAfter) { - 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(IFreeSql fsql, Type rootEntityType) - { - return LocalGetAutoIncludeQueryStaicCode(1, rootEntityType, "", new Stack()); - string LocalGetAutoIncludeQueryStaicCode(int depth, Type entityType, string navigatePath, Stack 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()) - { - var tbref = tr.Value; - if (tbref.Exception != null) 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) + var elementType = tbref.RefType == TableRefType.ManyToMany ? tbref.RefMiddleEntityType : tbref.RefEntityType; + if (collectionBefore == null && collectionAfter == null) return; + if (collectionBefore == null && collectionAfter != null) { - case TableRefType.OneToOne: - if (ignores.Any(a => a == tbref.RefEntityType)) break; - code.Append("\r\n").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")"); - code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores)); - break; - case TableRefType.OneToMany: - code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression); - var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack(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).Append(")"); - break; - case TableRefType.PgArrayToMany: - code.Append("\r\n//").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression).Append(")"); - break; - case TableRefType.ManyToOne: //不属于聚合根 - code.Append("\r\n//").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")"); - break; + 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(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 dictBefore = new Dictionary(); + Dictionary dictAfter = new Dictionary(); + 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) tracking.InsertLog.Add(NativeTuple.Create(elementType, item)); + else dictAfter.Add(key, 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(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); } - ignores.Pop(); - return code.ToString(); } - } - public static List 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(); - 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) + public static void NavigateReader(IFreeSql fsql, Type rootType, object rootEntity, Action> callback) { - var midval = tbref.RefMiddleEntityType.CreateInstanceGetDefaultValue(); + Dictionary> ignores = new Dictionary>(); + var statckPath = new Stack(); + var stackValues = new List(); + 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()); + 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; + 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); + LocalNavigateReader(tbref.RefEntityType, propval); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + break; + case TableRefType.OneToMany: + var propvalOtm = table.GetPropertyValue(entity, prop.Name); + if (propvalOtm == null) continue; + SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propvalOtm); + var propvalOtmList = new List(); + foreach (var val in propvalOtm as IEnumerable) + propvalOtmList.Add(val); + statckPath.Push($"{prop.Name}[]"); + stackValues.Add(propvalOtmList.ToArray()); + callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); + foreach (var val in propvalOtm as IEnumerable) + 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.RefEntityType, stackValues); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //不属于聚合根 + break; + } + } + } + } + + public static void MapEntityValue(IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo) + { + Dictionary> ignores = new Dictionary>(); + LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo); + ignores.Clear(); + + void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo) + { + 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()); + 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; + } + var tbref = table.GetTableRef(prop.Name, false); + if (tbref == null) 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); + 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, true); + break; + case TableRefType.ManyToMany: + LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, false); + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //不属于聚合根 + 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(); + if (cascade) LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem); + else EntityUtilExtensions.MapEntityValue(fsql, tbref.RefEntityType, fromItem, toItem); + 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>> _dicGetAutoIncludeQuery = new ConcurrentDictionary>>(); + public static ISelect GetAutoIncludeQuery(ISelect select) + { + var select0p = select as Select0Provider; + var table0Type = select0p._tables[0].Table.Type; + var func = _dicGetAutoIncludeQuery.GetOrAdd(typeof(TEntity), t => new ConcurrentDictionary>()).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()); + return Expression.Lambda>(exp, parmExp1).Compile(); + }); + func(select); + return select; + Expression LocalGetAutoIncludeQuery(Expression queryExp, int depth, Type entityType, ParameterExpression navigateParameterExp, Expression navigatePathExp, Stack 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; + 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); + queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores); + break; + case TableRefType.OneToMany: + LocalIncludeMany(tbref, navigateExp, true); + break; + case TableRefType.ManyToMany: + LocalIncludeMany(tbref, navigateExp, false); + break; + case TableRefType.PgArrayToMany: + break; + case TableRefType.ManyToOne: //不属于聚合根 + 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(IFreeSql fsql, Type rootEntityType) + { + return $"//fsql.Select<{rootEntityType.Name}>()\r\nSelectDiy{LocalGetAutoIncludeQueryStaicCode(1, rootEntityType, "", new Stack())}"; + string LocalGetAutoIncludeQueryStaicCode(int depth, Type entityType, string navigatePath, Stack 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; + 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(")"); + code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores)); + break; + case TableRefType.OneToMany: + code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression); + var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack(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).Append(")"); + break; + case TableRefType.PgArrayToMany: + code.Append("\r\n//").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression).Append(")"); + break; + case TableRefType.ManyToOne: //不属于聚合根 + code.Append("\r\n//").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")"); + break; + } + } + ignores.Pop(); + return code.ToString(); + } + } + + public static List 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(); + var leftpkvals = new object[tbref.Columns.Count]; 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++) + leftpkvals[x] = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, table.Type, entity, tbref.Columns[x].CsName)); + foreach (var right in rights) { - 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); + 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); } - middles.Add(midval); + return middles; } - return middles; - } - public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem) - { - if (rightItem == null) return; - switch (tbref.RefType) + public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem) { - case TableRefType.OneToOne: - 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: - 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) + if (rightItem == null) return; + switch (tbref.RefType) + { + case TableRefType.OneToOne: 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 = 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; + { + 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: + 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 = 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; + } } } -} +} \ No newline at end of file diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest.cs b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest.cs index 705d6b74..93698b0a 100644 --- a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest.cs +++ b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest.cs @@ -14,7 +14,6 @@ namespace FreeSql.Tests.DbContext2 { public UserRepository(IFreeSql fsql, UnitOfWorkManager uowManager) : base(uowManager?.Orm ?? fsql) { - var code = SelectAggregateRootStaticCode; } public override ISelect Select => base.SelectDiy; @@ -27,6 +26,11 @@ namespace FreeSql.Tests.DbContext2 { new UserRepository(fsql, null); + var code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(fsql, typeof(User)); + Assert.Equal(@"//fsql.Select() +SelectDiy + .Include(a => a.Ext)", code); + var repo = fsql.GetAggregateRootRepository(); var user = new User { @@ -93,6 +97,48 @@ namespace FreeSql.Tests.DbContext2 user.Ext.Remark = "admin01_remark changed01"; repo.Update(user); + user = repo.Where(a => a.Id == 1).First(); + Assert.NotNull(user); + Assert.Equal(1, user.Id); + Assert.Equal("admin01", user.UserName); + Assert.Equal("admin01_pwd", user.Password); + Assert.NotNull(user.Ext); + Assert.Equal(1, user.Ext.UserId); + Assert.Equal("admin01_remark changed01", user.Ext.Remark); + + var affrows = repo.Delete(user); + Assert.Equal(2, affrows); + Assert.False(fsql.Select().Where(a => a.Id == 1).Any()); + Assert.False(fsql.Select().Where(a => a.UserId == 1).Any()); + + var deleted = repo.DeleteCascadeByDatabase(a => a.Id == 2 || a.Id == 3); + Assert.NotNull(deleted); + Assert.Equal(4, deleted.Count); + Assert.False(fsql.Select().Where(a => a.Id == 2).Any()); + Assert.False(fsql.Select().Where(a => a.UserId == 2).Any()); + Assert.False(fsql.Select().Where(a => a.Id == 3).Any()); + Assert.False(fsql.Select().Where(a => a.UserId == 3).Any()); + users = new[] + { + (User)deleted[3], + (User)deleted[1], + }; + users[0].Ext = (UserExt)deleted[2]; + users[1].Ext = (UserExt)deleted[0]; + Assert.Equal(2, users.Length); + Assert.Equal(2, users[0].Id); + Assert.Equal("admin02", users[0].UserName); + Assert.Equal("admin02_pwd", users[0].Password); + Assert.NotNull(users[0].Ext); + Assert.Equal(2, users[0].Ext.UserId); + Assert.Equal("admin02_remark", users[0].Ext.Remark); + Assert.Equal(3, users[1].Id); + Assert.Equal("admin03", users[1].UserName); + Assert.Equal("admin03_pwd", users[1].Password); + Assert.NotNull(users[1].Ext); + Assert.Equal(3, users[1].Ext.UserId); + Assert.Equal("admin03_remark", users[1].Ext.Remark); + } } class User diff --git a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs index 4540af13..010b5416 100644 --- a/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs +++ b/FreeSql.Tests/FreeSql.Tests.DbContext2/AggregateRootRepositoryTest2.cs @@ -15,7 +15,6 @@ namespace FreeSql.Tests.DbContext2 { public OrderRepository(IFreeSql fsql, UnitOfWorkManager uowManager) : base(uowManager?.Orm ?? fsql) { - var code = SelectAggregateRootStaticCode; } public override ISelect Select => base.SelectDiy; @@ -28,6 +27,14 @@ namespace FreeSql.Tests.DbContext2 { new OrderRepository(fsql, null); + var code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(fsql, typeof(Order)); + Assert.Equal(@"//fsql.Select() +SelectDiy + .IncludeMany(a => a.Details, then => then + .Include(b => b.Extdata)) + .IncludeMany(a => a.Tags) + .Include(a => a.Extdata)", code); + fsql.Insert(new[] { new Tag { Name = "tag1" },