From ca2df947e8bd9492bf08cecee0a2e6d2500f3ac0 Mon Sep 17 00:00:00 2001 From: 2881099 <2881099@qq.com> Date: Fri, 2 Sep 2022 00:58:47 +0800 Subject: [PATCH] AggregateRootRepository --- FreeSql.Repository/AggregateRootRepository.cs | 370 ++++++++++++++++++ FreeSql.Repository/AggregateRootUtils.cs | 345 ++++++++++++++++ FreeSql.Repository/FreeSql.Repository.csproj | 6 +- 3 files changed, 720 insertions(+), 1 deletion(-) create mode 100644 FreeSql.Repository/AggregateRootRepository.cs create mode 100644 FreeSql.Repository/AggregateRootUtils.cs diff --git a/FreeSql.Repository/AggregateRootRepository.cs b/FreeSql.Repository/AggregateRootRepository.cs new file mode 100644 index 00000000..effe1d8e --- /dev/null +++ b/FreeSql.Repository/AggregateRootRepository.cs @@ -0,0 +1,370 @@ +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 +{ + public class AggregateRootRepository : IBaseRepository where TEntity : class + { + readonly IBaseRepository _repositoryPriv; + readonly Dictionary> _repositorys = new Dictionary>(); + protected IBaseRepository MainRepository + { + get + { + _repositoryPriv.DbContextOptions.EnableCascadeSave = false; + return _repositoryPriv; + } + } + protected IBaseRepository GetOrAddRepository(Type otherEntityType) + { + if (_repositorys.TryGetValue(otherEntityType, out var repo) == false) + { + repo = Orm.GetRepository(); + repo.AsType(otherEntityType); + _repositorys.Add(otherEntityType, repo); + } + repo.UnitOfWork = UnitOfWork; + repo.DbContextOptions = DbContextOptions; + repo.DbContextOptions.EnableCascadeSave = false; + repo.AsTable(_asTableRule); + return repo; + } + + public AggregateRootRepository(IFreeSql fsql) + { + _repositoryPriv = fsql.GetRepository(); + } + public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager) + { + _repositoryPriv = (uowManager?.Orm ?? fsql).GetRepository(); + uowManager?.Binding(_repositoryPriv); + } + + protected void DisposeRepositorys() + { + foreach (var repo in _repositorys.Values) + { + repo.FlushState(); + repo.Dispose(); + } + _repositorys.Clear(); + } + public void Dispose() + { + foreach (var repo in _repositorys.Values) + { + repo.FlushState(); + repo.Dispose(); + } + _repositorys.Clear(); + _repositoryPriv.FlushState(); + _repositoryPriv.Dispose(); + } + + public IUnitOfWork UnitOfWork + { + get => _repositoryPriv.UnitOfWork; + set => _repositoryPriv.UnitOfWork = value; + } + public DbContextOptions DbContextOptions + { + get => _repositoryPriv.DbContextOptions; + set => _repositoryPriv.DbContextOptions = value ?? throw new ArgumentNullException(nameof(DbContextOptions)); + } + public void AsType(Type entityType) => _repositoryPriv.AsType(entityType); + Func _asTableRule; + public void AsTable(Func rule) => _repositoryPriv.AsTable(_asTableRule = rule); + public Type EntityType => _repositoryPriv.EntityType; + public IDataFilter DataFilter => _repositoryPriv.DataFilter; + + public void Attach(TEntity entity) => AttachCascade(entity); + public void Attach(IEnumerable entity) + { + foreach (var item in entity) + AttachCascade(item); + } + public IBaseRepository AttachOnlyPrimary(TEntity data) => _repositoryPriv.AttachOnlyPrimary(data); + public Dictionary CompareState(TEntity newdata) => _repositoryPriv.CompareState(newdata); + public void FlushState() => _repositoryPriv.FlushState(); + + public IFreeSql Orm => _repositoryPriv.Orm; + public IUpdate UpdateDiy => _repositoryPriv.UpdateDiy; + public ISelect Where(Expression> exp) => Select.Where(exp); + public ISelect WhereIf(bool condition, Expression> exp) => Select.WhereIf(condition, exp); + + public TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault(); + public List Insert(IEnumerable entitys) + { + var ret = InsertCascade(MainRepository, GetOrAddRepository, entitys, new Dictionary()); + DisposeRepositorys(); + foreach (var item in ret) + AttachCascade(item); + return ret; + } + + public int Update(TEntity entity) => Update(new[] { entity }); + public int Update(IEnumerable entitys) + { + var ret = UpdateCascade(MainRepository, GetOrAddRepository, entitys, new Dictionary()); + DisposeRepositorys(); + foreach (var item in entitys) + AttachCascade(item); + return ret; + } + + public int Delete(TEntity entity) => Delete(new[] { entity }); + +#if net40 +#else + public Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.InsertAsync(entity, cancellationToken); + public Task> InsertAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repositoryPriv.InsertAsync(entitys, cancellationToken); + public Task InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.InsertOrUpdateAsync(entity, cancellationToken); + public Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default) => _repositoryPriv.SaveManyAsync(entity, propertyName, cancellationToken); + + public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.UpdateAsync(entity, cancellationToken); + public Task UpdateAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repositoryPriv.UpdateAsync(entitys, cancellationToken); + + public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(entity, cancellationToken); + public Task DeleteAsync(IEnumerable entitys, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(entitys, cancellationToken); + public Task DeleteAsync(Expression> predicate, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(predicate, cancellationToken); + public Task> DeleteCascadeByDatabaseAsync(Expression> predicate, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteCascadeByDatabaseAsync(predicate, cancellationToken); +#endif + + protected Dictionary _states = new Dictionary(); + 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.MapEntityValueCascade(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); + } + void AttachCascade(TEntity entity) + { + var state = CreateEntityState(entity); + if (_states.ContainsKey(state.Key)) _states[state.Key] = state; + else _states.Add(state.Key, state); + } + + #region Select + public ISelect Select + { + get + { + var query = MainRepository.Select.TrackToList(SelectTrackingAggregateRootNavigate); + SelectFetchAggregateRootNavigate(query, EntityType, "", new Stack()); + return query; + } + } + void SelectFetchAggregateRootNavigate(ISelect currentQuery, Type entityType, string navigatePath, Stack ignores) + { + if (ignores.Any(a => a == entityType)) return; + ignores.Push(entityType); + var tb = Orm.CodeFirst.GetTableByEntity(entityType); + foreach (var prop in tb.Properties.Values) + { + var tbref = tb.GetTableRef(prop.Name, false); + if (tbref == null) continue; + if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}."; + var navigateExpression = $"{navigatePath}{prop.Name}"; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + if (ignores.Any(a => a == tbref.RefEntityType)) break; + currentQuery.IncludeByPropertyName(navigateExpression); + SelectFetchAggregateRootNavigate(currentQuery, tbref.RefEntityType, navigateExpression, ignores); + break; + case TableRefType.OneToMany: + var ignoresCopy = new Stack(ignores.ToArray()); + currentQuery.IncludeByPropertyName(navigateExpression, then => + SelectFetchAggregateRootNavigate(then, tbref.RefEntityType, "", ignoresCopy)); + break; + case TableRefType.ManyToMany: + currentQuery.IncludeByPropertyName(navigateExpression); + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //不属于聚合根 + break; + } + } + ignores.Pop(); + } + void SelectTrackingAggregateRootNavigate(object list) + { + if (list == null) return; + var ls = list as IEnumerable; + 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; + } + AttachCascade(item as TEntity); + } + return; + } + } + #endregion + + protected static List InsertCascade(IBaseRepository repository, Func> getOrAddRepository, IEnumerable entitys, Dictionary states) where T1 : class + { + var ret = repository.Insert(entitys); + foreach (var entity in entitys) IsCascade(repository.EntityType, entity, true); + + var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType); + foreach (var prop in table.Properties.Values) + { + var tbref = table.GetTableRef(prop.Name, false); + if (tbref == null) continue; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + var otoList = ret.Select(entity => + { + var otoItem = table.GetPropertyValue(entity, prop.Name); + if (IsCascade(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 = getOrAddRepository(tbref.RefEntityType); + InsertCascade(repo, getOrAddRepository, otoList, states); + } + 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(); + foreach (var otmItem in otmEach) + { + if (IsCascade(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 = getOrAddRepository(tbref.RefEntityType); + InsertCascade(repo, getOrAddRepository, otmList, states); + } + break; + case TableRefType.ManyToMany: + var mtmMidList = new List(); + ret.ForEach(entity => + { + var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop); + if (mids != null) mtmMidList.AddRange(mids); + }); + if (mtmMidList.Any()) + { + var repo = getOrAddRepository(tbref.RefMiddleEntityType); + InsertCascade(repo, getOrAddRepository, mtmMidList, states); + } + break; + case TableRefType.PgArrayToMany: + break; + } + } + return ret; + + bool IsCascade(Type entityType, object entity, bool isadd) + { + var stateKey = repository.Orm.GetEntityKeyString(entityType, entity, false); + if (stateKey == null) return true; + stateKey = $"{stateKey}*|_,[,_|*{entityType.DisplayCsharp()}"; + if (states.ContainsKey(stateKey)) return false; + if (isadd) states.Add(stateKey, entity); + return true; + } + } + public TEntity InsertOrUpdate(TEntity entity) => MainRepository.InsertOrUpdate(entity); + public int UpdateCascade(IBaseRepository repository, Func> getOrAddRepository, IEnumerable entitys, Dictionary states) where T1 : class + { + return 0; + } + public int Delete(IEnumerable entitys) => MainRepository.Delete(entitys); + public int Delete(Expression> predicate) => MainRepository.Delete(predicate); + public List DeleteCascadeByDatabase(Expression> predicate) => MainRepository.DeleteCascadeByDatabase(predicate); + + public void SaveMany(TEntity entity, string propertyName) => MainRepository.SaveMany(entity, propertyName); + + public void BeginEdit(List data) => MainRepository.BeginEdit(data); + public int EndEdit(List data = null) => MainRepository.EndEdit(data); + + protected static void FetchAggregateRootNavigate(IFreeSql orm, Type entityType, Func callback, Dictionary ignores) + { + if (ignores.ContainsKey(entityType)) return; + ignores.Add(entityType, true); + var tb = orm.CodeFirst.GetTableByEntity(entityType); + foreach (var prop in tb.Properties.Values) + { + var tbref = tb.GetTableRef(prop.Name, false); + if (tbref == null) continue; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + callback(tb.Type, tbref); + FetchAggregateRootNavigate(orm, tbref.RefEntityType, callback, ignores); + break; + case TableRefType.OneToMany: + callback(tb.Type, tbref); + FetchAggregateRootNavigate(orm, tbref.RefEntityType, callback, ignores); + break; + case TableRefType.ManyToMany: + callback(tb.Type, tbref); + FetchAggregateRootNavigate(orm, tbref.RefMiddleEntityType, callback, ignores); + break; + case TableRefType.PgArrayToMany: + break; + } + } + } + + + } +} diff --git a/FreeSql.Repository/AggregateRootUtils.cs b/FreeSql.Repository/AggregateRootUtils.cs new file mode 100644 index 00000000..9ec6a78c --- /dev/null +++ b/FreeSql.Repository/AggregateRootUtils.cs @@ -0,0 +1,345 @@ +using FreeSql.Aop; +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.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +static class AggregateRootUtils +{ + + public static void CompareEntityValueCascade(IFreeSql fsql, Type entityType, object entityBefore, object entityAfter, + List> insertLog, + List>> updateLog, + List> deleteLog) + { + if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType(); + var table = fsql.CodeFirst.GetTableByEntity(entityType); + 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(entityBefore, col.CsName); + if (propvalBefore != propvalAfter) changes.Add(col.CsName); + continue; + } + } + if (changes.Any()) + updateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes)); + + foreach (var prop in table.Properties.Values) + { + var tbref = table.GetTableRef(prop.Name, false); + if (tbref == null) continue; + var propvalBefore = table.GetPropertyValue(entityBefore, prop.Name); + var propvalAfter = table.GetPropertyValue(entityBefore, prop.Name); + switch (tbref.RefType) + { + case TableRefType.OneToOne: + if (propvalBefore == null && propvalAfter == null) return; + if (propvalBefore == null && propvalAfter != null) + { + insertLog.Add(NativeTuple.Create(tbref.RefEntityType, propvalAfter)); + return; + } + if (propvalBefore != null && propvalAfter == null) + { + deleteLog.Add(NativeTuple.Create(tbref.RefEntityType, propvalBefore)); + EachNavigateCascade(fsql, tbref.RefEntityType, propvalBefore, (path, tr, ct, stackvs) => + { + deleteLog.Add(NativeTuple.Create(ct, stackvs.First())); + }); + return; + } + CompareEntityValueCascade(fsql, tbref.RefEntityType, propvalBefore, propvalAfter, insertLog, updateLog, deleteLog); + break; + case TableRefType.OneToMany: + 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; + } + } + + 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, item)); + EachNavigateCascade(fsql, elementType, item, (path, tr, ct, stackvs) => + { + deleteLog.Add(NativeTuple.Create(ct, stackvs.First())); + }); + } + return; + } + Dictionary dictBefore = new Dictionary(); + Dictionary dictAfter = new Dictionary(); + foreach (var item in collectionBefore as IEnumerable) + { + var beforeKey = fsql.GetEntityKeyString(elementType, item, false); + dictBefore.Add(beforeKey, item); + } + foreach (var item in collectionAfter as IEnumerable) + { + var afterKey = fsql.GetEntityKeyString(elementType, item, false); + if (afterKey != null) insertLog.Add(NativeTuple.Create(elementType, item)); + else dictBefore.Add(afterKey, item); + } + foreach (var key in dictBefore.Keys.ToArray()) + { + if (dictAfter.ContainsKey(key) == false) + { + var value = dictBefore[key]; + deleteLog.Add(NativeTuple.Create(elementType, value)); + EachNavigateCascade(fsql, elementType, value, (path, tr, ct, stackvs) => + { + deleteLog.Add(NativeTuple.Create(ct, stackvs.First())); + }); + 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) + CompareEntityValueCascade(fsql, elementType, dictBefore[key], dictAfter[key], insertLog, updateLog, deleteLog); + } + } + public static void EachNavigateCascade(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); + LocalEachNavigate(rootType, rootEntity); + ignores.Clear(); + + void LocalEachNavigate(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 prop in table.Properties.Values) + { + var tbref = table.GetTableRef(prop.Name, false); + if (tbref == null) continue; + var idx = 0; + switch (tbref.RefType) + { + case TableRefType.OneToOne: + var propval = table.GetPropertyValue(entity, prop.Name); + statckPath.Push(prop.Name); + stackValues.Add(propval); + callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); + LocalEachNavigate(tbref.RefEntityType, propval); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + break; + case TableRefType.OneToMany: + foreach (var val in table.GetPropertyValue(entity, prop.Name) as IEnumerable) + { + statckPath.Push($"{prop.Name[idx++]}"); + stackValues.Add(val); + callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues); + LocalEachNavigate(tbref.RefEntityType, val); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + } + break; + case TableRefType.ManyToMany: + var middleValues = GetManyToManyObjects(fsql, table, tbref, entity, prop); + foreach (var midval in middleValues) + { + statckPath.Push($"{prop.Name[idx++]}"); + stackValues.Add(midval); + callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefMiddleEntityType, stackValues); + stackValues.RemoveAt(stackValues.Count - 1); + statckPath.Pop(); + } + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //不属于聚合根 + break; + } + } + } + } + + static ConcurrentDictionary> _dicMapEntityValueCascade = new ConcurrentDictionary>(); + public static void MapEntityValueCascade(this 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(); + LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo); + EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo); + break; + case TableRefType.OneToMany: + LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom, prop, true); + break; + case TableRefType.ManyToMany: + LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom, prop, false); + break; + case TableRefType.PgArrayToMany: + case TableRefType.ManyToOne: //不属于聚合根 + break; + } + } + } + void LocalMapEntityValueCollection(Type entityType, object entityFrom, object entityTo, TableRef tbref, object propvalFrom, PropertyInfo prop, bool cascade) + { + var propvalFromEach = propvalFrom as IEnumerable; + var propvalTo = typeof(List<>).MakeGenericType(tbref.RefEntityType).CreateInstanceGetDefaultValue(); + var propvalToIList = propvalTo as IList; + foreach (var fromItem in propvalFromEach) + { + 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); + } + } + } + + 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) + { + 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) + { + if (rightItem == null) return; + switch (tbref.RefType) + { + 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) + 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; + } + } +} diff --git a/FreeSql.Repository/FreeSql.Repository.csproj b/FreeSql.Repository/FreeSql.Repository.csproj index a3acb8e6..2692cf92 100644 --- a/FreeSql.Repository/FreeSql.Repository.csproj +++ b/FreeSql.Repository/FreeSql.Repository.csproj @@ -27,5 +27,9 @@ - + + + net40 + +