mirror of
https://github.com/nsnail/FreeSql.git
synced 2025-04-22 02:32:50 +08:00
AggregateRootRepository
This commit is contained in:
parent
a2b8b1c324
commit
ca2df947e8
370
FreeSql.Repository/AggregateRootRepository.cs
Normal file
370
FreeSql.Repository/AggregateRootRepository.cs
Normal file
@ -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<TEntity> : IBaseRepository<TEntity> where TEntity : class
|
||||||
|
{
|
||||||
|
readonly IBaseRepository<TEntity> _repositoryPriv;
|
||||||
|
readonly Dictionary<Type, IBaseRepository<object>> _repositorys = new Dictionary<Type, IBaseRepository<object>>();
|
||||||
|
protected IBaseRepository<TEntity> MainRepository
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_repositoryPriv.DbContextOptions.EnableCascadeSave = false;
|
||||||
|
return _repositoryPriv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected IBaseRepository<object> GetOrAddRepository(Type otherEntityType)
|
||||||
|
{
|
||||||
|
if (_repositorys.TryGetValue(otherEntityType, out var repo) == false)
|
||||||
|
{
|
||||||
|
repo = Orm.GetRepository<object>();
|
||||||
|
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<TEntity>();
|
||||||
|
}
|
||||||
|
public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager)
|
||||||
|
{
|
||||||
|
_repositoryPriv = (uowManager?.Orm ?? fsql).GetRepository<TEntity>();
|
||||||
|
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<string, string> _asTableRule;
|
||||||
|
public void AsTable(Func<string, string> rule) => _repositoryPriv.AsTable(_asTableRule = rule);
|
||||||
|
public Type EntityType => _repositoryPriv.EntityType;
|
||||||
|
public IDataFilter<TEntity> DataFilter => _repositoryPriv.DataFilter;
|
||||||
|
|
||||||
|
public void Attach(TEntity entity) => AttachCascade(entity);
|
||||||
|
public void Attach(IEnumerable<TEntity> entity)
|
||||||
|
{
|
||||||
|
foreach (var item in entity)
|
||||||
|
AttachCascade(item);
|
||||||
|
}
|
||||||
|
public IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data) => _repositoryPriv.AttachOnlyPrimary(data);
|
||||||
|
public Dictionary<string, object[]> CompareState(TEntity newdata) => _repositoryPriv.CompareState(newdata);
|
||||||
|
public void FlushState() => _repositoryPriv.FlushState();
|
||||||
|
|
||||||
|
public IFreeSql Orm => _repositoryPriv.Orm;
|
||||||
|
public IUpdate<TEntity> UpdateDiy => _repositoryPriv.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);
|
||||||
|
|
||||||
|
public TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault();
|
||||||
|
public List<TEntity> Insert(IEnumerable<TEntity> entitys)
|
||||||
|
{
|
||||||
|
var ret = InsertCascade(MainRepository, GetOrAddRepository, entitys, new Dictionary<string, object>());
|
||||||
|
DisposeRepositorys();
|
||||||
|
foreach (var item in ret)
|
||||||
|
AttachCascade(item);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Update(TEntity entity) => Update(new[] { entity });
|
||||||
|
public int Update(IEnumerable<TEntity> entitys)
|
||||||
|
{
|
||||||
|
var ret = UpdateCascade(MainRepository, GetOrAddRepository, entitys, new Dictionary<string, object>());
|
||||||
|
DisposeRepositorys();
|
||||||
|
foreach (var item in entitys)
|
||||||
|
AttachCascade(item);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Delete(TEntity entity) => Delete(new[] { entity });
|
||||||
|
|
||||||
|
#if net40
|
||||||
|
#else
|
||||||
|
public Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.InsertAsync(entity, cancellationToken);
|
||||||
|
public Task<List<TEntity>> InsertAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default) => _repositoryPriv.InsertAsync(entitys, cancellationToken);
|
||||||
|
public Task<TEntity> 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<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.UpdateAsync(entity, cancellationToken);
|
||||||
|
public Task<int> UpdateAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default) => _repositoryPriv.UpdateAsync(entitys, cancellationToken);
|
||||||
|
|
||||||
|
public Task<int> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(entity, cancellationToken);
|
||||||
|
public Task<int> DeleteAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(entitys, cancellationToken);
|
||||||
|
public Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteAsync(predicate, cancellationToken);
|
||||||
|
public Task<List<object>> DeleteCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) => _repositoryPriv.DeleteCascadeByDatabaseAsync(predicate, cancellationToken);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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.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<TEntity> Select
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var query = MainRepository.Select.TrackToList(SelectTrackingAggregateRootNavigate);
|
||||||
|
SelectFetchAggregateRootNavigate(query, EntityType, "", new Stack<Type>());
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void SelectFetchAggregateRootNavigate<T1>(ISelect<T1> currentQuery, Type entityType, string navigatePath, Stack<Type> 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<Type>(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<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;
|
||||||
|
}
|
||||||
|
AttachCascade(item as TEntity);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
protected static List<T1> InsertCascade<T1>(IBaseRepository<T1> repository, Func<Type, IBaseRepository<object>> getOrAddRepository, IEnumerable<T1> entitys, Dictionary<string, object> 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<object>();
|
||||||
|
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<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 = 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<T1>(IBaseRepository<T1> repository, Func<Type, IBaseRepository<object>> getOrAddRepository, IEnumerable<T1> entitys, Dictionary<string, object> states) where T1 : class
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
public int Delete(IEnumerable<TEntity> entitys) => MainRepository.Delete(entitys);
|
||||||
|
public int Delete(Expression<Func<TEntity, bool>> predicate) => MainRepository.Delete(predicate);
|
||||||
|
public List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate) => MainRepository.DeleteCascadeByDatabase(predicate);
|
||||||
|
|
||||||
|
public void SaveMany(TEntity entity, string propertyName) => MainRepository.SaveMany(entity, propertyName);
|
||||||
|
|
||||||
|
public void BeginEdit(List<TEntity> data) => MainRepository.BeginEdit(data);
|
||||||
|
public int EndEdit(List<TEntity> data = null) => MainRepository.EndEdit(data);
|
||||||
|
|
||||||
|
protected static void FetchAggregateRootNavigate<T1>(IFreeSql orm, Type entityType, Func<Type, TableRef, T1> callback, Dictionary<Type, bool> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
345
FreeSql.Repository/AggregateRootUtils.cs
Normal file
345
FreeSql.Repository/AggregateRootUtils.cs
Normal file
@ -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<NativeTuple<Type, object>> insertLog,
|
||||||
|
List<NativeTuple<Type, object, object, List<string>>> updateLog,
|
||||||
|
List<NativeTuple<Type, object>> deleteLog)
|
||||||
|
{
|
||||||
|
if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType();
|
||||||
|
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||||
|
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(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<string, object> dictBefore = new Dictionary<string, object>();
|
||||||
|
Dictionary<string, object> dictAfter = new Dictionary<string, object>();
|
||||||
|
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<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);
|
||||||
|
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<string, bool>());
|
||||||
|
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<Type, Action<IFreeSql, object, object>> _dicMapEntityValueCascade = new ConcurrentDictionary<Type, Action<IFreeSql, object, object>>();
|
||||||
|
public static void MapEntityValueCascade(this IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo)
|
||||||
|
{
|
||||||
|
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||||
|
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<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;
|
||||||
|
}
|
||||||
|
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<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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,4 +28,8 @@
|
|||||||
<ProjectReference Include="..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
|
<ProjectReference Include="..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
|
||||||
|
<DefineConstants>net40</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user