mirror of
https://github.com/nsnail/FreeSql.git
synced 2025-04-22 02:32:50 +08:00
v3.2.667 #1237
This commit is contained in:
parent
9eab104259
commit
090abfd36a
@ -11,7 +11,7 @@
|
|||||||
<!--
|
<!--
|
||||||
经常出于版本交叉问题,暂时关闭,在每个项目上设置版本号
|
经常出于版本交叉问题,暂时关闭,在每个项目上设置版本号
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ using Rougamo.Context;
|
|||||||
|
|
||||||
namespace FreeSql
|
namespace FreeSql
|
||||||
{
|
{
|
||||||
[AttributeUsage(AttributeTargets.Method)]
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||||
public class TransactionalAttribute : Rougamo.MoAttribute
|
public class TransactionalAttribute : Rougamo.MoAttribute
|
||||||
{
|
{
|
||||||
public Propagation Propagation { get; set; } = Propagation.Required;
|
public Propagation Propagation { get; set; } = Propagation.Required;
|
||||||
|
@ -3,7 +3,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
public static class FreeSqlRepositoryExtensions
|
public static class FreeSqlAggregateRootRepositoryGlobalExtensions
|
||||||
{
|
{
|
||||||
public static IBaseRepository<TEntity> GetAggregateRootRepository<TEntity>(this IFreeSql that) where TEntity : class
|
public static IBaseRepository<TEntity> GetAggregateRootRepository<TEntity>(this IFreeSql that) where TEntity : class
|
||||||
{
|
{
|
@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netstandard2.0;net60;net50;netcoreapp31;netcoreapp21;net45;net40</TargetFrameworks>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||||
|
<Description>FreeSql 扩展包,聚合根(实现室).</Description>
|
||||||
|
<PackageProjectUrl>https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
<PackageTags>FreeSql;ORM</PackageTags>
|
||||||
|
<PackageId>$(AssemblyName)</PackageId>
|
||||||
|
<PackageIcon>logo.png</PackageIcon>
|
||||||
|
<Title>$(AssemblyName)</Title>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
|
<SignAssembly>true</SignAssembly>
|
||||||
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
|
<DelaySign>false</DelaySign>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="../../logo.png" Pack="true" PackagePath="\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
|
||||||
|
<DocumentationFile>FreeSql.Extensions.AggregateRoot.xml</DocumentationFile>
|
||||||
|
<WarningLevel>3</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\FreeSql\FreeSql.DbContext.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<doc>
|
||||||
|
<assembly>
|
||||||
|
<name>FreeSql.Extensions.JsonMap</name>
|
||||||
|
</assembly>
|
||||||
|
<members>
|
||||||
|
<member name="T:FreeSql.DataAnnotations.JsonMapAttribute">
|
||||||
|
<summary>
|
||||||
|
When the entity class property is <see cref="T:System.Object"/>, map storage in JSON format. <br />
|
||||||
|
当实体类属性为【对象】时,以 JSON 形式映射存储
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:FreeSqlJsonMapCoreExtensions.UseJsonMap(IFreeSql)">
|
||||||
|
<summary>
|
||||||
|
When the entity class property is <see cref="T:System.Object"/> and the attribute is marked as <see cref="T:FreeSql.DataAnnotations.JsonMapAttribute"/>, map storage in JSON format. <br />
|
||||||
|
当实体类属性为【对象】时,并且标记特性 [JsonMap] 时,该属性将以JSON形式映射存储
|
||||||
|
</summary>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
</members>
|
||||||
|
</doc>
|
BIN
Extensions/FreeSql.Extensions.AggregateRoot/key.snk
Normal file
BIN
Extensions/FreeSql.Extensions.AggregateRoot/key.snk
Normal file
Binary file not shown.
@ -19,7 +19,7 @@
|
|||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<Title>$(AssemblyName)</Title>
|
<Title>$(AssemblyName)</Title>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
|
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
|
||||||
<PackageTags>FreeSql DbFirst 实体生成器</PackageTags>
|
<PackageTags>FreeSql DbFirst 实体生成器</PackageTags>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FreeSql.DataAnnotations
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置 AggregateRootRepository 边界范围<para></para>
|
||||||
|
/// 在边界范围之内的规则 :<para></para>
|
||||||
|
/// 1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改<para></para>
|
||||||
|
/// 2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略)<para></para>
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
|
||||||
|
public class AggregateRootBoundaryAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 边界是否终止
|
||||||
|
/// </summary>
|
||||||
|
public bool Break { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 边界是否终止向下探测
|
||||||
|
/// </summary>
|
||||||
|
public bool BreakThen { get; set; }
|
||||||
|
|
||||||
|
public AggregateRootBoundaryAttribute(string name)
|
||||||
|
{
|
||||||
|
this.Name = name;
|
||||||
|
}
|
||||||
|
public AggregateRootBoundaryAttribute()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FreeSql.Internal.Model
|
||||||
|
{
|
||||||
|
|
||||||
|
public class AggregateRootTrackingChangeInfo
|
||||||
|
{
|
||||||
|
|
||||||
|
public List<NativeTuple<Type, object>> InsertLog { get; } = new List<NativeTuple<Type, object>>();
|
||||||
|
|
||||||
|
public List<NativeTuple<Type, object, object, List<string>>> UpdateLog { get; } = new List<NativeTuple<Type, object, object, List<string>>>();
|
||||||
|
|
||||||
|
public List<NativeTuple<Type, object[]>> DeleteLog { get; } = new List<NativeTuple<Type, object[]>>();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,261 @@
|
|||||||
|
using FreeSql.Extensions.EntityUtil;
|
||||||
|
using FreeSql.Internal.Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace FreeSql
|
||||||
|
{
|
||||||
|
public interface IAggregateRootRepository<TEntity>: IBaseRepository<TEntity> where TEntity : class
|
||||||
|
{
|
||||||
|
IBaseRepository<TEntity> ChangeBoundary(string name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class AggregateRootRepository<TEntity> : IAggregateRootRepository<TEntity> where TEntity : class
|
||||||
|
{
|
||||||
|
readonly IBaseRepository<TEntity> _repository;
|
||||||
|
public AggregateRootRepository(IFreeSql fsql)
|
||||||
|
{
|
||||||
|
if (fsql == null) throw new ArgumentNullException(nameof(fsql));
|
||||||
|
_repository = fsql.GetRepository<TEntity>();
|
||||||
|
_repository.DbContextOptions.EnableCascadeSave = false;
|
||||||
|
}
|
||||||
|
public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager) : this(uowManager?.Orm ?? fsql)
|
||||||
|
{
|
||||||
|
uowManager?.Binding(_repository);
|
||||||
|
}
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
_repository.FlushState();
|
||||||
|
_repository.Dispose();
|
||||||
|
FlushState();
|
||||||
|
}
|
||||||
|
|
||||||
|
string _boundaryName = "";
|
||||||
|
public IBaseRepository<TEntity> ChangeBoundary(string name)
|
||||||
|
{
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
_repository.FlushState();
|
||||||
|
FlushState();
|
||||||
|
_boundaryName = string.Concat(name).Trim();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFreeSql Orm => _repository.Orm;
|
||||||
|
public IUnitOfWork UnitOfWork { get => _repository.UnitOfWork; set => _repository.UnitOfWork = value; }
|
||||||
|
public DbContextOptions DbContextOptions
|
||||||
|
{
|
||||||
|
get => _repository.DbContextOptions;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == null) throw new ArgumentNullException(nameof(DbContextOptions));
|
||||||
|
_repository.DbContextOptions = value;
|
||||||
|
_repository.DbContextOptions.EnableCascadeSave = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void AsType(Type entityType) => _repository.AsType(entityType);
|
||||||
|
Func<string, string> _asTableRule;
|
||||||
|
public void AsTable(Func<string, string> rule)
|
||||||
|
{
|
||||||
|
_repository.AsTable(rule);
|
||||||
|
_asTableRule = rule;
|
||||||
|
}
|
||||||
|
public Type EntityType => _repository.EntityType;
|
||||||
|
public IDataFilter<TEntity> DataFilter => _repository.DataFilter;
|
||||||
|
|
||||||
|
public void Attach(TEntity entity)
|
||||||
|
{
|
||||||
|
var state = CreateEntityState(entity);
|
||||||
|
if (_states.ContainsKey(state.Key)) _states[state.Key] = state;
|
||||||
|
else _states.Add(state.Key, state);
|
||||||
|
}
|
||||||
|
public void Attach(IEnumerable<TEntity> entity)
|
||||||
|
{
|
||||||
|
foreach (var item in entity)
|
||||||
|
Attach(item);
|
||||||
|
}
|
||||||
|
public IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data) => _repository.AttachOnlyPrimary(data);
|
||||||
|
public Dictionary<string, object[]> CompareState(TEntity newdata)
|
||||||
|
{
|
||||||
|
if (newdata == null) return null;
|
||||||
|
var _table = Orm.CodeFirst.GetTableByEntity(EntityType);
|
||||||
|
if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.Incomparable_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, newdata)));
|
||||||
|
var key = Orm.GetEntityKeyString(EntityType, newdata, false);
|
||||||
|
if (string.IsNullOrEmpty(key)) throw new Exception(DbContextStrings.Incomparable_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, newdata)));
|
||||||
|
if (_states.TryGetValue(key, out var oldState) == false || oldState == null) throw new Exception($"不可对比,数据未被跟踪:{Orm.GetEntityString(EntityType, newdata)}");
|
||||||
|
AggregateRootTrackingChangeInfo tracking = new AggregateRootTrackingChangeInfo();
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, oldState, newdata, null, tracking);
|
||||||
|
return new Dictionary<string, object[]>
|
||||||
|
{
|
||||||
|
["Insert"] = tracking.InsertLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray(),
|
||||||
|
["Delete"] = tracking.DeleteLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray(),
|
||||||
|
["Update"] = tracking.UpdateLog.Select(a => new object[] { a.Item1, a.Item2, a.Item3, a.Item4 }).ToArray(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public void FlushState()
|
||||||
|
{
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
_repository.FlushState();
|
||||||
|
_states.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IUpdate<TEntity> UpdateDiy => _repository.UpdateDiy;
|
||||||
|
public ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => Select.Where(exp);
|
||||||
|
public ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => Select.WhereIf(condition, exp);
|
||||||
|
|
||||||
|
readonly Dictionary<Type, IBaseRepository<object>> _childRepositorys = new Dictionary<Type, IBaseRepository<object>>();
|
||||||
|
IBaseRepository<object> GetChildRepository(Type type)
|
||||||
|
{
|
||||||
|
if (_childRepositorys.TryGetValue(type, out var repo) == false)
|
||||||
|
{
|
||||||
|
repo = Orm.GetRepository<object>();
|
||||||
|
repo.AsType(type);
|
||||||
|
_childRepositorys.Add(type, repo);
|
||||||
|
}
|
||||||
|
repo.UnitOfWork = UnitOfWork;
|
||||||
|
repo.DbContextOptions = DbContextOptions;
|
||||||
|
repo.DbContextOptions.EnableCascadeSave = false;
|
||||||
|
repo.AsTable(_asTableRule);
|
||||||
|
return repo;
|
||||||
|
}
|
||||||
|
void DisposeChildRepositorys()
|
||||||
|
{
|
||||||
|
foreach (var repo in _childRepositorys.Values)
|
||||||
|
{
|
||||||
|
repo.FlushState();
|
||||||
|
repo.Dispose();
|
||||||
|
}
|
||||||
|
_childRepositorys.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 状态管理
|
||||||
|
protected Dictionary<string, EntityState> _states = new Dictionary<string, EntityState>();
|
||||||
|
protected class EntityState
|
||||||
|
{
|
||||||
|
public EntityState(TEntity value, string key)
|
||||||
|
{
|
||||||
|
this.Value = value;
|
||||||
|
this.Key = key;
|
||||||
|
this.Time = DateTime.Now;
|
||||||
|
}
|
||||||
|
public TEntity OldValue { get; set; }
|
||||||
|
public TEntity Value { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
public DateTime Time { get; set; }
|
||||||
|
}
|
||||||
|
EntityState CreateEntityState(TEntity data)
|
||||||
|
{
|
||||||
|
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||||
|
var key = Orm.GetEntityKeyString(EntityType, data, false);
|
||||||
|
var state = new EntityState((TEntity)EntityType.CreateInstanceGetDefaultValue(), key);
|
||||||
|
AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, data, state.Value);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
bool? ExistsInStates(object data)
|
||||||
|
{
|
||||||
|
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||||
|
var key = Orm.GetEntityKeyString(EntityType, data, false);
|
||||||
|
if (string.IsNullOrEmpty(key)) return null;
|
||||||
|
return _states.ContainsKey(key);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 查询数据
|
||||||
|
/// <summary>
|
||||||
|
/// 默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)<para></para>
|
||||||
|
/// 重写:使用
|
||||||
|
/// </summary>
|
||||||
|
public virtual ISelect<TEntity> Select => SelectAggregateRoot;
|
||||||
|
/// <summary>
|
||||||
|
/// 创建查询对象(纯净)<para></para>
|
||||||
|
/// _<para></para>
|
||||||
|
/// 聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发<para></para>
|
||||||
|
/// string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order))
|
||||||
|
/// </summary>
|
||||||
|
protected ISelect<TEntity> SelectDiy => _repository.Select.TrackToList(SelectAggregateRootTracking);
|
||||||
|
/// <summary>
|
||||||
|
/// 创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected ISelect<TEntity> SelectAggregateRoot
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var query = _repository.Select.TrackToList(SelectAggregateRootTracking);
|
||||||
|
query = AggregateRootUtils.GetAutoIncludeQuery(_boundaryName, query);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// ISelect.TrackToList 委托,数据返回后自动 Attach
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="list"></param>
|
||||||
|
protected void SelectAggregateRootTracking(object list)
|
||||||
|
{
|
||||||
|
if (list == null) return;
|
||||||
|
var ls = list as IEnumerable<TEntity>;
|
||||||
|
if (ls == null)
|
||||||
|
{
|
||||||
|
var ie = list as IEnumerable;
|
||||||
|
if (ie == null) return;
|
||||||
|
var isfirst = true;
|
||||||
|
foreach (var item in ie)
|
||||||
|
{
|
||||||
|
if (item == null) continue;
|
||||||
|
if (isfirst)
|
||||||
|
{
|
||||||
|
isfirst = false;
|
||||||
|
var itemType = item.GetType();
|
||||||
|
if (itemType == typeof(object)) return;
|
||||||
|
if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType;
|
||||||
|
if (Orm.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return;
|
||||||
|
if (itemType.GetConstructor(Type.EmptyTypes) == null) return;
|
||||||
|
}
|
||||||
|
if (item is TEntity item2) Attach(item2);
|
||||||
|
else return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//void SelectAggregateRootNavigateReader<T1>(ISelect<T1> currentQuery, Type entityType, string navigatePath, Stack<Type> ignores)
|
||||||
|
//{
|
||||||
|
// if (ignores.Any(a => a == entityType)) return;
|
||||||
|
// ignores.Push(entityType);
|
||||||
|
// var table = Orm.CodeFirst.GetTableByEntity(entityType);
|
||||||
|
// if (table == null) return;
|
||||||
|
// if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}.";
|
||||||
|
// foreach (var tr in table.GetAllTableRef())
|
||||||
|
// {
|
||||||
|
// var tbref = tr.Value;
|
||||||
|
// if (tbref.Exception != null) continue;
|
||||||
|
// var navigateExpression = $"{navigatePath}{tr.Key}";
|
||||||
|
// switch (tbref.RefType)
|
||||||
|
// {
|
||||||
|
// case TableRefType.OneToOne:
|
||||||
|
// if (ignores.Any(a => a == tbref.RefEntityType)) break;
|
||||||
|
// currentQuery.IncludeByPropertyName(navigateExpression);
|
||||||
|
// SelectAggregateRootNavigateReader(currentQuery, tbref.RefEntityType, navigateExpression, ignores);
|
||||||
|
// break;
|
||||||
|
// case TableRefType.OneToMany:
|
||||||
|
// var ignoresCopy = new Stack<Type>(ignores.ToArray());
|
||||||
|
// currentQuery.IncludeByPropertyName(navigateExpression, then =>
|
||||||
|
// SelectAggregateRootNavigateReader(then, tbref.RefEntityType, "", ignoresCopy)); //variable 'then' of type 'FreeSql.ISelect`1[System.Object]' referenced from scope '', but it is not defined
|
||||||
|
// break;
|
||||||
|
// case TableRefType.ManyToMany:
|
||||||
|
// currentQuery.IncludeByPropertyName(navigateExpression);
|
||||||
|
// break;
|
||||||
|
// case TableRefType.PgArrayToMany:
|
||||||
|
// break;
|
||||||
|
// case TableRefType.ManyToOne:
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ignores.Pop();
|
||||||
|
//}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,297 @@
|
|||||||
|
#if net40
|
||||||
|
#else
|
||||||
|
using FreeSql.Extensions.EntityUtil;
|
||||||
|
using FreeSql.Internal;
|
||||||
|
using FreeSql.Internal.Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FreeSql
|
||||||
|
{
|
||||||
|
partial class AggregateRootRepository<TEntity>
|
||||||
|
{
|
||||||
|
|
||||||
|
#region InsertAsync
|
||||||
|
async public virtual Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => (await InsertAsync(new[] { entity }, cancellationToken)).FirstOrDefault();
|
||||||
|
async public virtual Task<List<TEntity>> InsertAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var repos = new Dictionary<Type, object>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ret = await InsertWithinBoundaryStaticAsync(_boundaryName, _repository, GetChildRepository, entitys, null, cancellationToken);
|
||||||
|
Attach(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
_repository.FlushState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async Task<List<T1>> InsertWithinBoundaryStaticAsync<T1>(string boundaryName, IBaseRepository<T1> rootRepository, Func<Type, IBaseRepository<object>> getChildRepository, IEnumerable<T1> rootEntitys, int[] affrows, CancellationToken cancellationToken) where T1 : class
|
||||||
|
{
|
||||||
|
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||||
|
Dictionary<Type, IBaseRepository<object>> repos = new Dictionary<Type, IBaseRepository<object>>();
|
||||||
|
var localAffrows = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await LocalInsertAsync(rootRepository, rootEntitys, true);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (affrows != null) affrows[0] = localAffrows;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LocalCanInsert(Type entityType, object entity, bool isadd)
|
||||||
|
{
|
||||||
|
var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false);
|
||||||
|
if (stateKey == null) return true;
|
||||||
|
if (ignores.TryGetValue(entityType, out var stateKeys) == false)
|
||||||
|
{
|
||||||
|
if (isadd)
|
||||||
|
{
|
||||||
|
ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||||
|
stateKeys.Add(stateKey, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (stateKeys.ContainsKey(stateKey) == false)
|
||||||
|
{
|
||||||
|
if (isadd) stateKeys.Add(stateKey, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
async Task<List<T2>> LocalInsertAsync<T2>(IBaseRepository<T2> repository, IEnumerable<T2> entitys, bool cascade) where T2 : class
|
||||||
|
{
|
||||||
|
var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType);
|
||||||
|
if (table.Primarys.Any(col => col.Attribute.IsIdentity))
|
||||||
|
{
|
||||||
|
foreach (var entity in entitys)
|
||||||
|
repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity);
|
||||||
|
}
|
||||||
|
var ret = await repository.InsertAsync(entitys, cancellationToken);
|
||||||
|
localAffrows += ret.Count;
|
||||||
|
foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true);
|
||||||
|
if (cascade == false) return ret;
|
||||||
|
|
||||||
|
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||||
|
{
|
||||||
|
var tbref = tr.Value;
|
||||||
|
if (tbref.Exception != null) continue;
|
||||||
|
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||||
|
var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||||
|
if (boundaryAttr?.Break == true) continue;
|
||||||
|
switch (tbref.RefType)
|
||||||
|
{
|
||||||
|
case TableRefType.OneToOne:
|
||||||
|
var otoList = ret.Select(entity =>
|
||||||
|
{
|
||||||
|
var otoItem = table.GetPropertyValue(entity, prop.Name);
|
||||||
|
if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null;
|
||||||
|
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem);
|
||||||
|
return otoItem;
|
||||||
|
}).Where(entity => entity != null).ToArray();
|
||||||
|
if (otoList.Any())
|
||||||
|
{
|
||||||
|
var repo = getChildRepository(tbref.RefEntityType);
|
||||||
|
await LocalInsertAsync(repo, otoList, boundaryAttr?.BreakThen != true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TableRefType.OneToMany:
|
||||||
|
var otmList = ret.Select(entity =>
|
||||||
|
{
|
||||||
|
var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
|
||||||
|
if (otmEach == null) return null;
|
||||||
|
var otmItems = new List<object>();
|
||||||
|
foreach (var otmItem in otmEach)
|
||||||
|
{
|
||||||
|
if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue;
|
||||||
|
otmItems.Add(otmItem);
|
||||||
|
}
|
||||||
|
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems);
|
||||||
|
return otmItems;
|
||||||
|
}).Where(entity => entity != null).SelectMany(entity => entity).ToArray();
|
||||||
|
if (otmList.Any())
|
||||||
|
{
|
||||||
|
var repo = getChildRepository(tbref.RefEntityType);
|
||||||
|
await LocalInsertAsync(repo, otmList, boundaryAttr?.BreakThen != true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToMany:
|
||||||
|
var mtmMidList = new List<object>();
|
||||||
|
ret.ForEach(entity =>
|
||||||
|
{
|
||||||
|
var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop);
|
||||||
|
if (mids != null) mtmMidList.AddRange(mids);
|
||||||
|
});
|
||||||
|
if (mtmMidList.Any())
|
||||||
|
{
|
||||||
|
var repo = getChildRepository(tbref.RefMiddleEntityType);
|
||||||
|
await LocalInsertAsync(repo, mtmMidList, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TableRefType.PgArrayToMany:
|
||||||
|
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
async public virtual Task<TEntity> InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||||
|
if (entity == null) throw new ArgumentNullException(nameof(entity));
|
||||||
|
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
|
||||||
|
if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity)));
|
||||||
|
|
||||||
|
var flagExists = ExistsInStates(entity);
|
||||||
|
if (flagExists == false)
|
||||||
|
{
|
||||||
|
var olddata = await Select.WhereDynamic(entity).FirstAsync(cancellationToken);
|
||||||
|
flagExists = olddata != null;
|
||||||
|
}
|
||||||
|
if (flagExists == true)
|
||||||
|
{
|
||||||
|
var affrows = await UpdateAsync(entity, cancellationToken);
|
||||||
|
if (affrows > 0) return entity;
|
||||||
|
}
|
||||||
|
if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length)
|
||||||
|
{
|
||||||
|
Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity);
|
||||||
|
return await InsertAsync(entity, cancellationToken);
|
||||||
|
}
|
||||||
|
throw new Exception(DbContextStrings.CannotAdd_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, entity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => UpdateAsync(new[] { entity }, cancellationToken);
|
||||||
|
public virtual Task<int> UpdateAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var tracking = new AggregateRootTrackingChangeInfo();
|
||||||
|
foreach (var entity in entitys)
|
||||||
|
{
|
||||||
|
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||||
|
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}");
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking);
|
||||||
|
}
|
||||||
|
foreach (var entity in entitys)
|
||||||
|
Attach(entity);
|
||||||
|
|
||||||
|
return SaveTrackingChangeAsync(tracking, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual Task<int> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(new[] { entity }, null, cancellationToken);
|
||||||
|
public virtual Task<int> DeleteAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(entitys, null, cancellationToken);
|
||||||
|
async public virtual Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) => await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), null, cancellationToken);
|
||||||
|
async public virtual Task<List<object>> DeleteCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var deletedOutput = new List<object>();
|
||||||
|
await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), deletedOutput, cancellationToken);
|
||||||
|
return deletedOutput;
|
||||||
|
}
|
||||||
|
async Task<int> DeleteWithinBoundaryAsync(IEnumerable<TEntity> entitys, List<object> deletedOutput, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tracking = new AggregateRootTrackingChangeInfo();
|
||||||
|
foreach (var entity in entitys)
|
||||||
|
{
|
||||||
|
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking);
|
||||||
|
_states.Remove(stateKey);
|
||||||
|
}
|
||||||
|
var affrows = 0;
|
||||||
|
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
|
||||||
|
{
|
||||||
|
affrows += await Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule)
|
||||||
|
.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken);
|
||||||
|
if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2);
|
||||||
|
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
|
||||||
|
new DbContext.EntityChangeReport.ChangeInfo
|
||||||
|
{
|
||||||
|
Type = DbContext.EntityChangeType.Delete,
|
||||||
|
EntityType = tracking.DeleteLog[a].Item1,
|
||||||
|
Object = x
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return affrows;
|
||||||
|
}
|
||||||
|
|
||||||
|
async public virtual Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var tracking = new AggregateRootTrackingChangeInfo();
|
||||||
|
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||||
|
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}");
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking);
|
||||||
|
Attach(entity); //应该只存储 propertyName 内容
|
||||||
|
await SaveTrackingChangeAsync(tracking, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async Task<int> SaveTrackingChangeAsync(AggregateRootTrackingChangeInfo tracking, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var affrows = 0;
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray());
|
||||||
|
foreach (var il in insertLogDict)
|
||||||
|
{
|
||||||
|
var repo = GetChildRepository(il.Key);
|
||||||
|
var affrowsOut = new int[1];
|
||||||
|
await InsertWithinBoundaryStaticAsync(_boundaryName, repo, GetChildRepository, il.Value, affrowsOut, cancellationToken);
|
||||||
|
affrows += affrowsOut[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
|
||||||
|
{
|
||||||
|
affrows += await Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule)
|
||||||
|
.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken);
|
||||||
|
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
|
||||||
|
new DbContext.EntityChangeReport.ChangeInfo
|
||||||
|
{
|
||||||
|
Type = DbContext.EntityChangeType.Delete,
|
||||||
|
EntityType = tracking.DeleteLog[a].Item1,
|
||||||
|
Object = x
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => new
|
||||||
|
{
|
||||||
|
BeforeObject = b.Item2,
|
||||||
|
AfterObject = b.Item3,
|
||||||
|
UpdateColumns = b.Item4,
|
||||||
|
UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c))
|
||||||
|
}).ToArray());
|
||||||
|
var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.UpdateColumnsString, b => a.Value.Where(c => c.UpdateColumnsString == b.UpdateColumnsString).ToArray()));
|
||||||
|
foreach (var dl in updateLogDict2)
|
||||||
|
{
|
||||||
|
foreach (var dl2 in dl.Value)
|
||||||
|
{
|
||||||
|
affrows += await Orm.Update<object>().AsType(dl.Key).AsTable(_asTableRule)
|
||||||
|
.SetSource(dl2.Value.Select(a => a.AfterObject).ToArray())
|
||||||
|
.UpdateColumns(dl2.Value.First().UpdateColumns.ToArray())
|
||||||
|
.ExecuteAffrowsAsync(cancellationToken);
|
||||||
|
UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x =>
|
||||||
|
new DbContext.EntityChangeReport.ChangeInfo
|
||||||
|
{
|
||||||
|
Type = DbContext.EntityChangeType.Update,
|
||||||
|
EntityType = dl.Key,
|
||||||
|
Object = x.AfterObject,
|
||||||
|
BeforeObject = x.BeforeObject
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
return affrows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -0,0 +1,347 @@
|
|||||||
|
using FreeSql.Extensions.EntityUtil;
|
||||||
|
using FreeSql.Internal;
|
||||||
|
using FreeSql.Internal.Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FreeSql
|
||||||
|
{
|
||||||
|
partial class AggregateRootRepository<TEntity>
|
||||||
|
{
|
||||||
|
|
||||||
|
#region BeginEdit/EndEdit
|
||||||
|
List<TEntity> _dataEditing;
|
||||||
|
ConcurrentDictionary<string, EntityState> _statesEditing = new ConcurrentDictionary<string, EntityState>();
|
||||||
|
public virtual void BeginEdit(List<TEntity> data)
|
||||||
|
{
|
||||||
|
if (data == null) return;
|
||||||
|
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
|
||||||
|
if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotEdit_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, data.First())));
|
||||||
|
_statesEditing.Clear();
|
||||||
|
_dataEditing = data;
|
||||||
|
foreach (var item in data)
|
||||||
|
{
|
||||||
|
var key = Orm.GetEntityKeyString(EntityType, item, false);
|
||||||
|
if (string.IsNullOrEmpty(key)) continue;
|
||||||
|
|
||||||
|
_statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) =>
|
||||||
|
{
|
||||||
|
AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, item, ov.Value);
|
||||||
|
ov.Time = DateTime.Now;
|
||||||
|
return ov;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public virtual int EndEdit(List<TEntity> data = null)
|
||||||
|
{
|
||||||
|
if (data == null) data = _dataEditing;
|
||||||
|
if (data == null) return 0;
|
||||||
|
var tracking = new AggregateRootTrackingChangeInfo();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var addList = new List<TEntity>();
|
||||||
|
var ediList = new List<TEntity>();
|
||||||
|
foreach (var item in data)
|
||||||
|
{
|
||||||
|
var key = Orm.GetEntityKeyString(EntityType, item, false);
|
||||||
|
if (_statesEditing.TryRemove(key, out var state) == false)
|
||||||
|
{
|
||||||
|
tracking.InsertLog.Add(NativeTuple.Create(EntityType, (object)item));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_states[key] = state;
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, item, null, tracking);
|
||||||
|
}
|
||||||
|
foreach (var item in _statesEditing.Values.OrderBy(a => a.Time))
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, item, null, null, tracking);
|
||||||
|
|
||||||
|
return SaveTrackingChange(tracking);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_dataEditing = null;
|
||||||
|
_statesEditing.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Insert
|
||||||
|
public virtual TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault();
|
||||||
|
public virtual List<TEntity> Insert(IEnumerable<TEntity> entitys)
|
||||||
|
{
|
||||||
|
var repos = new Dictionary<Type, object>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ret = InsertWithinBoundaryStatic(_boundaryName, _repository, GetChildRepository, entitys, out var affrows);
|
||||||
|
Attach(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
_repository.FlushState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static List<T1> InsertWithinBoundaryStatic<T1>(string boundaryName, IBaseRepository<T1> rootRepository, Func<Type, IBaseRepository<object>> getChildRepository, IEnumerable<T1> rootEntitys, out int affrows) where T1 : class {
|
||||||
|
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||||
|
Dictionary<Type, IBaseRepository<object>> repos = new Dictionary<Type, IBaseRepository<object>>();
|
||||||
|
var localAffrows = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return LocalInsert(rootRepository, rootEntitys, true);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
affrows = localAffrows;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LocalCanInsert(Type entityType, object entity, bool isadd)
|
||||||
|
{
|
||||||
|
var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false);
|
||||||
|
if (stateKey == null) return true;
|
||||||
|
if (ignores.TryGetValue(entityType, out var stateKeys) == false)
|
||||||
|
{
|
||||||
|
if (isadd)
|
||||||
|
{
|
||||||
|
ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||||
|
stateKeys.Add(stateKey, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (stateKeys.ContainsKey(stateKey) == false)
|
||||||
|
{
|
||||||
|
if (isadd) stateKeys.Add(stateKey, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<T2> LocalInsert<T2>(IBaseRepository<T2> repository, IEnumerable<T2> entitys, bool cascade) where T2 : class
|
||||||
|
{
|
||||||
|
var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType);
|
||||||
|
if (table.Primarys.Any(col => col.Attribute.IsIdentity))
|
||||||
|
{
|
||||||
|
foreach (var entity in entitys)
|
||||||
|
repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity);
|
||||||
|
}
|
||||||
|
var ret = repository.Insert(entitys);
|
||||||
|
localAffrows += ret.Count;
|
||||||
|
foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true);
|
||||||
|
if (cascade == false) return ret;
|
||||||
|
|
||||||
|
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||||
|
{
|
||||||
|
var tbref = tr.Value;
|
||||||
|
if (tbref.Exception != null) continue;
|
||||||
|
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||||
|
var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||||
|
if (boundaryAttr?.Break == true) continue;
|
||||||
|
switch (tbref.RefType)
|
||||||
|
{
|
||||||
|
case TableRefType.OneToOne:
|
||||||
|
var otoList = ret.Select(entity =>
|
||||||
|
{
|
||||||
|
var otoItem = table.GetPropertyValue(entity, prop.Name);
|
||||||
|
if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null;
|
||||||
|
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem);
|
||||||
|
return otoItem;
|
||||||
|
}).Where(entity => entity != null).ToArray();
|
||||||
|
if (otoList.Any())
|
||||||
|
{
|
||||||
|
var repo = getChildRepository(tbref.RefEntityType);
|
||||||
|
LocalInsert(repo, otoList, boundaryAttr?.BreakThen != true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TableRefType.OneToMany:
|
||||||
|
var otmList = ret.Select(entity =>
|
||||||
|
{
|
||||||
|
var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
|
||||||
|
if (otmEach == null) return null;
|
||||||
|
var otmItems = new List<object>();
|
||||||
|
foreach (var otmItem in otmEach)
|
||||||
|
{
|
||||||
|
if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue;
|
||||||
|
otmItems.Add(otmItem);
|
||||||
|
}
|
||||||
|
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems);
|
||||||
|
return otmItems;
|
||||||
|
}).Where(entity => entity != null).SelectMany(entity => entity).ToArray();
|
||||||
|
if (otmList.Any())
|
||||||
|
{
|
||||||
|
var repo = getChildRepository(tbref.RefEntityType);
|
||||||
|
LocalInsert(repo, otmList, boundaryAttr?.BreakThen != true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToMany:
|
||||||
|
var mtmMidList = new List<object>();
|
||||||
|
ret.ForEach(entity =>
|
||||||
|
{
|
||||||
|
var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop);
|
||||||
|
if (mids != null) mtmMidList.AddRange(mids);
|
||||||
|
});
|
||||||
|
if (mtmMidList.Any())
|
||||||
|
{
|
||||||
|
var repo = getChildRepository(tbref.RefMiddleEntityType);
|
||||||
|
LocalInsert(repo, mtmMidList, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TableRefType.PgArrayToMany:
|
||||||
|
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public virtual TEntity InsertOrUpdate(TEntity entity)
|
||||||
|
{
|
||||||
|
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||||
|
if (entity == null) throw new ArgumentNullException(nameof(entity));
|
||||||
|
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
|
||||||
|
if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity)));
|
||||||
|
|
||||||
|
var flagExists = ExistsInStates(entity);
|
||||||
|
if (flagExists == false)
|
||||||
|
{
|
||||||
|
var olddata = Select.WhereDynamic(entity).First();
|
||||||
|
flagExists = olddata != null;
|
||||||
|
}
|
||||||
|
if (flagExists == true)
|
||||||
|
{
|
||||||
|
var affrows = Update(entity);
|
||||||
|
if (affrows > 0) return entity;
|
||||||
|
}
|
||||||
|
if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length)
|
||||||
|
{
|
||||||
|
Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity);
|
||||||
|
return Insert(entity);
|
||||||
|
}
|
||||||
|
throw new Exception(DbContextStrings.CannotAdd_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, entity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int Update(TEntity entity) => Update(new[] { entity });
|
||||||
|
public virtual int Update(IEnumerable<TEntity> entitys)
|
||||||
|
{
|
||||||
|
var tracking = new AggregateRootTrackingChangeInfo();
|
||||||
|
foreach(var entity in entitys)
|
||||||
|
{
|
||||||
|
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||||
|
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}");
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking);
|
||||||
|
}
|
||||||
|
foreach (var entity in entitys)
|
||||||
|
Attach(entity);
|
||||||
|
|
||||||
|
return SaveTrackingChange(tracking);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int Delete(TEntity entity) => DeleteWithinBoundary(new[] { entity }, null);
|
||||||
|
public virtual int Delete(IEnumerable<TEntity> entitys) => DeleteWithinBoundary(entitys, null);
|
||||||
|
public virtual int Delete(Expression<Func<TEntity, bool>> predicate) => DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), null);
|
||||||
|
public virtual List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate)
|
||||||
|
{
|
||||||
|
var deletedOutput = new List<object>();
|
||||||
|
DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), deletedOutput);
|
||||||
|
return deletedOutput;
|
||||||
|
}
|
||||||
|
int DeleteWithinBoundary(IEnumerable<TEntity> entitys, List<object> deletedOutput)
|
||||||
|
{
|
||||||
|
var tracking = new AggregateRootTrackingChangeInfo();
|
||||||
|
foreach (var entity in entitys)
|
||||||
|
{
|
||||||
|
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking);
|
||||||
|
_states.Remove(stateKey);
|
||||||
|
}
|
||||||
|
var affrows = 0;
|
||||||
|
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
|
||||||
|
{
|
||||||
|
affrows += Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule)
|
||||||
|
.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows();
|
||||||
|
if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2);
|
||||||
|
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
|
||||||
|
new DbContext.EntityChangeReport.ChangeInfo
|
||||||
|
{
|
||||||
|
Type = DbContext.EntityChangeType.Delete,
|
||||||
|
EntityType = tracking.DeleteLog[a].Item1,
|
||||||
|
Object = x
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return affrows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SaveMany(TEntity entity, string propertyName)
|
||||||
|
{
|
||||||
|
var tracking = new AggregateRootTrackingChangeInfo();
|
||||||
|
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||||
|
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}");
|
||||||
|
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking);
|
||||||
|
Attach(entity); //应该只存储 propertyName 内容
|
||||||
|
SaveTrackingChange(tracking);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int SaveTrackingChange(AggregateRootTrackingChangeInfo tracking)
|
||||||
|
{
|
||||||
|
var affrows = 0;
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray());
|
||||||
|
foreach (var il in insertLogDict)
|
||||||
|
{
|
||||||
|
var repo = GetChildRepository(il.Key);
|
||||||
|
InsertWithinBoundaryStatic(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut);
|
||||||
|
affrows += affrowsOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
|
||||||
|
{
|
||||||
|
affrows += Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1).AsTable(_asTableRule)
|
||||||
|
.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows();
|
||||||
|
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
|
||||||
|
new DbContext.EntityChangeReport.ChangeInfo
|
||||||
|
{
|
||||||
|
Type = DbContext.EntityChangeType.Delete,
|
||||||
|
EntityType = tracking.DeleteLog[a].Item1,
|
||||||
|
Object = x
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key).Select(b => new
|
||||||
|
{
|
||||||
|
BeforeObject = b.Item2,
|
||||||
|
AfterObject = b.Item3,
|
||||||
|
UpdateColumns = b.Item4,
|
||||||
|
UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c))
|
||||||
|
}).ToArray());
|
||||||
|
var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a => a.Value.ToDictionary(b => b.UpdateColumnsString, b => a.Value.Where(c => c.UpdateColumnsString == b.UpdateColumnsString).ToArray()));
|
||||||
|
foreach (var dl in updateLogDict2)
|
||||||
|
{
|
||||||
|
foreach (var dl2 in dl.Value)
|
||||||
|
{
|
||||||
|
affrows += Orm.Update<object>().AsType(dl.Key).AsTable(_asTableRule)
|
||||||
|
.SetSource(dl2.Value.Select(a => a.AfterObject).ToArray())
|
||||||
|
.UpdateColumns(dl2.Value.First().UpdateColumns.ToArray())
|
||||||
|
.ExecuteAffrows();
|
||||||
|
UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x =>
|
||||||
|
new DbContext.EntityChangeReport.ChangeInfo
|
||||||
|
{
|
||||||
|
Type = DbContext.EntityChangeType.Update,
|
||||||
|
EntityType = dl.Key,
|
||||||
|
Object = x.AfterObject,
|
||||||
|
BeforeObject = x.BeforeObject
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DisposeChildRepositorys();
|
||||||
|
return affrows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
652
FreeSql.DbContext/AggregateRootRepository/AggregateRootUtils.cs
Normal file
652
FreeSql.DbContext/AggregateRootRepository/AggregateRootUtils.cs
Normal file
@ -0,0 +1,652 @@
|
|||||||
|
using FreeSql;
|
||||||
|
using FreeSql.DataAnnotations;
|
||||||
|
using FreeSql.Extensions.EntityUtil;
|
||||||
|
using FreeSql.Internal;
|
||||||
|
using FreeSql.Internal.CommonProvider;
|
||||||
|
using FreeSql.Internal.Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace FreeSql
|
||||||
|
{
|
||||||
|
public class AggregateRootUtils
|
||||||
|
{
|
||||||
|
static ConcurrentDictionary<PropertyInfo, ConcurrentDictionary<string, AggregateRootBoundaryAttribute>> _dicGetPropertyBoundaryAttribute = new ConcurrentDictionary<PropertyInfo, ConcurrentDictionary<string, AggregateRootBoundaryAttribute>>();
|
||||||
|
public static AggregateRootBoundaryAttribute GetPropertyBoundaryAttribute(PropertyInfo prop, string boundaryName)
|
||||||
|
{
|
||||||
|
if (boundaryName == null) return null;
|
||||||
|
return _dicGetPropertyBoundaryAttribute.GetOrAdd(prop, tp => new ConcurrentDictionary<string, AggregateRootBoundaryAttribute>())
|
||||||
|
.GetOrAdd(boundaryName, bn =>
|
||||||
|
{
|
||||||
|
var attrs = prop.GetCustomAttributes(typeof(AggregateRootBoundaryAttribute), false);
|
||||||
|
if (attrs == null || attrs.Any() == false) return null;
|
||||||
|
return attrs.Select(a => a as AggregateRootBoundaryAttribute).Where(a => a.Name == bn).FirstOrDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CompareEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityBefore, object rootEntityAfter, string rootNavigatePropertyName, AggregateRootTrackingChangeInfo tracking)
|
||||||
|
{
|
||||||
|
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||||
|
LocalCompareEntityValue(rootEntityType, rootEntityBefore, rootEntityAfter, rootNavigatePropertyName, true);
|
||||||
|
ignores.Clear();
|
||||||
|
|
||||||
|
void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName, bool cascade)
|
||||||
|
{
|
||||||
|
if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType();
|
||||||
|
|
||||||
|
if (entityBefore != null)
|
||||||
|
{
|
||||||
|
var stateKey = $":before://{fsql.GetEntityKeyString(entityType, entityBefore, false)}";
|
||||||
|
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||||
|
if (stateKeys.ContainsKey(stateKey)) return;
|
||||||
|
stateKeys.Add(stateKey, true);
|
||||||
|
}
|
||||||
|
if (entityAfter != null)
|
||||||
|
{
|
||||||
|
var stateKey = $":after://{fsql.GetEntityKeyString(entityType, entityAfter, false)}";
|
||||||
|
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||||
|
if (stateKeys.ContainsKey(stateKey)) return;
|
||||||
|
stateKeys.Add(stateKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||||
|
if (table == null) return;
|
||||||
|
if (entityBefore == null && entityAfter == null) return;
|
||||||
|
if (entityBefore == null && entityAfter != null)
|
||||||
|
{
|
||||||
|
tracking.InsertLog.Add(NativeTuple.Create(entityType, entityAfter));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entityBefore != null && entityAfter == null)
|
||||||
|
{
|
||||||
|
tracking.DeleteLog.Add(NativeTuple.Create(entityType, new[] { entityBefore }));
|
||||||
|
NavigateReader(boundaryName, fsql, entityType, entityBefore, (path, tr, ct, stackvs) =>
|
||||||
|
{
|
||||||
|
var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() };
|
||||||
|
tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var changes = new List<string>();
|
||||||
|
foreach (var col in table.ColumnsByCs.Values)
|
||||||
|
{
|
||||||
|
if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue;
|
||||||
|
if (table.ColumnsByCs.ContainsKey(col.CsName))
|
||||||
|
{
|
||||||
|
if (col.Attribute.IsVersion) continue;
|
||||||
|
var propvalBefore = table.GetPropertyValue(entityBefore, col.CsName);
|
||||||
|
var propvalAfter = table.GetPropertyValue(entityAfter, col.CsName);
|
||||||
|
//if (object.Equals(propvalBefore, propvalAfter) == false) changes.Add(col.CsName);
|
||||||
|
if (CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) changes.Add(col.CsName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changes.Any()) tracking.UpdateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes));
|
||||||
|
if (cascade == false) return;
|
||||||
|
|
||||||
|
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||||
|
{
|
||||||
|
var tbref = tr.Value;
|
||||||
|
if (tbref.Exception != null) continue;
|
||||||
|
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||||
|
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||||
|
if (boundaryAttr?.Break == true) continue;
|
||||||
|
if (navigatePropertyName != null && prop.Name != navigatePropertyName) continue;
|
||||||
|
var propvalBefore = table.GetPropertyValue(entityBefore, prop.Name);
|
||||||
|
var propvalAfter = table.GetPropertyValue(entityAfter, prop.Name);
|
||||||
|
switch (tbref.RefType)
|
||||||
|
{
|
||||||
|
case TableRefType.OneToOne:
|
||||||
|
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore);
|
||||||
|
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter);
|
||||||
|
LocalCompareEntityValue(tbref.RefEntityType, propvalBefore, propvalAfter, null, boundaryAttr?.BreakThen != true);
|
||||||
|
break;
|
||||||
|
case TableRefType.OneToMany:
|
||||||
|
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore);
|
||||||
|
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter);
|
||||||
|
LocalCompareEntityValueCollection(tbref.RefEntityType, propvalBefore as IEnumerable, propvalAfter as IEnumerable, boundaryAttr?.BreakThen != true);
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToMany:
|
||||||
|
var middleValuesBefore = GetManyToManyObjects(fsql, table, tbref, entityBefore, prop);
|
||||||
|
var middleValuesAfter = GetManyToManyObjects(fsql, table, tbref, entityAfter, prop);
|
||||||
|
LocalCompareEntityValueCollection(tbref.RefMiddleEntityType, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable, false);
|
||||||
|
break;
|
||||||
|
case TableRefType.PgArrayToMany:
|
||||||
|
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void LocalCompareEntityValueCollection(Type elementType, IEnumerable collectionBefore, IEnumerable collectionAfter, bool cascade)
|
||||||
|
{
|
||||||
|
if (collectionBefore == null && collectionAfter == null) return;
|
||||||
|
if (collectionBefore == null && collectionAfter != null)
|
||||||
|
{
|
||||||
|
foreach (var item in collectionAfter)
|
||||||
|
tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (collectionBefore != null && collectionAfter == null)
|
||||||
|
{
|
||||||
|
//foreach (var item in collectionBefore as IEnumerable)
|
||||||
|
//{
|
||||||
|
// changelog.DeleteLog.Add(NativeTuple.Create(elementType, new[] { item }));
|
||||||
|
// NavigateReader(boundaryName, fsql, elementType, item, (path, tr, ct, stackvs) =>
|
||||||
|
// {
|
||||||
|
// var dellist = stackvs.Last() as object[] ?? new [] { stackvs.Last() };
|
||||||
|
// changelog.DeleteLog.Add(NativeTuple.Create(ct, dellist));
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Dictionary<string, object> dictBefore = new Dictionary<string, object>();
|
||||||
|
Dictionary<string, object> dictAfter = new Dictionary<string, object>();
|
||||||
|
foreach (var item in collectionBefore as IEnumerable)
|
||||||
|
{
|
||||||
|
var key = fsql.GetEntityKeyString(elementType, item, false);
|
||||||
|
if (key != null) dictBefore.Add(key, item);
|
||||||
|
}
|
||||||
|
foreach (var item in collectionAfter as IEnumerable)
|
||||||
|
{
|
||||||
|
var key = fsql.GetEntityKeyString(elementType, item, false);
|
||||||
|
if (key != null)
|
||||||
|
{
|
||||||
|
if (dictAfter.ContainsKey(key) == false)
|
||||||
|
dictAfter.Add(key, item);
|
||||||
|
}
|
||||||
|
else tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
|
||||||
|
}
|
||||||
|
foreach (var key in dictBefore.Keys.ToArray())
|
||||||
|
{
|
||||||
|
if (dictAfter.ContainsKey(key) == false)
|
||||||
|
{
|
||||||
|
var value = dictBefore[key];
|
||||||
|
tracking.DeleteLog.Add(NativeTuple.Create(elementType, new[] { value }));
|
||||||
|
NavigateReader(boundaryName, fsql, elementType, value, (path, tr, ct, stackvs) =>
|
||||||
|
{
|
||||||
|
var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() };
|
||||||
|
tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist));
|
||||||
|
});
|
||||||
|
dictBefore.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var key in dictAfter.Keys.ToArray())
|
||||||
|
{
|
||||||
|
if (dictBefore.ContainsKey(key) == false)
|
||||||
|
{
|
||||||
|
tracking.InsertLog.Add(NativeTuple.Create(elementType, dictAfter[key]));
|
||||||
|
dictAfter.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var key in dictBefore.Keys)
|
||||||
|
LocalCompareEntityValue(elementType, dictBefore[key], dictAfter[key], null, cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConcurrentDictionary<Type, bool> _dicCompareEntityPropertyValue = new ConcurrentDictionary<Type, bool>
|
||||||
|
{
|
||||||
|
[typeof(string)] = true,
|
||||||
|
[typeof(DateTime)] = true,
|
||||||
|
[typeof(DateTime?)] = true,
|
||||||
|
[typeof(DateTimeOffset)] = true,
|
||||||
|
[typeof(DateTimeOffset?)] = true,
|
||||||
|
[typeof(TimeSpan)] = true,
|
||||||
|
[typeof(TimeSpan?)] = true,
|
||||||
|
};
|
||||||
|
public static bool CompareEntityPropertyValue(Type type, object propvalBefore, object propvalAfter)
|
||||||
|
{
|
||||||
|
if (propvalBefore == null && propvalAfter == null) return true;
|
||||||
|
if (type.IsNumberType() ||
|
||||||
|
_dicCompareEntityPropertyValue.ContainsKey(type) ||
|
||||||
|
type.IsEnum ||
|
||||||
|
type.IsValueType ||
|
||||||
|
type.NullableTypeOrThis().IsEnum) return object.Equals(propvalBefore, propvalAfter);
|
||||||
|
if (propvalBefore == null && propvalAfter != null) return false;
|
||||||
|
if (propvalBefore != null && propvalAfter == null) return false;
|
||||||
|
|
||||||
|
if (FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(type)) {
|
||||||
|
if (type.FullName.StartsWith("Newtonsoft."))
|
||||||
|
return object.Equals(propvalBefore.ToString(), propvalAfter.ToString());
|
||||||
|
|
||||||
|
if (typeof(IDictionary).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
var dictBefore = (propvalBefore as IDictionary);
|
||||||
|
var dictAfter = (propvalAfter as IDictionary);
|
||||||
|
if (dictBefore.Count != dictAfter.Count) return false;
|
||||||
|
foreach (var key in dictBefore.Keys)
|
||||||
|
{
|
||||||
|
if (dictAfter.Contains(key) == false) return false;
|
||||||
|
var valBefore = dictBefore[key];
|
||||||
|
var valAfter = dictAfter[key];
|
||||||
|
if (valBefore == null && valAfter == null) continue;
|
||||||
|
if (valBefore == null && valAfter != null) return false;
|
||||||
|
if (valBefore != null && valAfter == null) return false;
|
||||||
|
if (CompareEntityPropertyValue(valBefore.GetType(), valBefore, valAfter) == false) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.IsArrayOrList())
|
||||||
|
{
|
||||||
|
var enumableBefore = propvalBefore as IEnumerable;
|
||||||
|
var enumableAfter = propvalAfter as IEnumerable;
|
||||||
|
var itorBefore = enumableBefore.GetEnumerator();
|
||||||
|
var itorAfter = enumableAfter.GetEnumerator();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var moveNextBefore = itorBefore.MoveNext();
|
||||||
|
var moveNextAfter = itorAfter.MoveNext();
|
||||||
|
if (moveNextBefore != moveNextAfter) return false;
|
||||||
|
if (moveNextBefore == false) break;
|
||||||
|
var currentBefore = itorBefore.Current;
|
||||||
|
var currentAfter = itorAfter.Current;
|
||||||
|
if (currentBefore == null && enumableAfter == null) continue;
|
||||||
|
if (currentBefore == null && currentAfter != null) return false;
|
||||||
|
if (currentBefore != null && currentAfter == null) return false;
|
||||||
|
if (CompareEntityPropertyValue(currentBefore.GetType(), currentBefore, currentAfter) == false) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.FullName.StartsWith("System.") ||
|
||||||
|
type.FullName.StartsWith("Npgsql.") ||
|
||||||
|
type.FullName.StartsWith("NetTopologySuite."))
|
||||||
|
return object.Equals(propvalBefore, propvalAfter);
|
||||||
|
|
||||||
|
if (type.IsClass)
|
||||||
|
{
|
||||||
|
foreach (var prop in type.GetProperties())
|
||||||
|
{
|
||||||
|
var valBefore = prop.GetValue(propvalBefore, new object[0]);
|
||||||
|
var valAfter = prop.GetValue(propvalAfter, new object[0]);
|
||||||
|
if (CompareEntityPropertyValue(prop.PropertyType, valBefore, valAfter) == false) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object.Equals(propvalBefore, propvalAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NavigateReader(string boundaryName, IFreeSql fsql, Type rootType, object rootEntity, Action<string, TableRef, Type, List<object>> callback)
|
||||||
|
{
|
||||||
|
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||||
|
var statckPath = new Stack<string>();
|
||||||
|
var stackValues = new List<object>();
|
||||||
|
statckPath.Push("_");
|
||||||
|
stackValues.Add(rootEntity);
|
||||||
|
LocalNavigateReader(rootType, rootEntity);
|
||||||
|
ignores.Clear();
|
||||||
|
|
||||||
|
void LocalNavigateReader(Type entityType, object entity)
|
||||||
|
{
|
||||||
|
if (entity == null) return;
|
||||||
|
if (entityType == null) entityType = entity.GetType();
|
||||||
|
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||||
|
if (table == null) return;
|
||||||
|
|
||||||
|
var stateKey = fsql.GetEntityKeyString(entityType, entity, false);
|
||||||
|
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||||
|
if (stateKeys.ContainsKey(stateKey)) return;
|
||||||
|
stateKeys.Add(stateKey, true);
|
||||||
|
|
||||||
|
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||||
|
{
|
||||||
|
var tbref = tr.Value;
|
||||||
|
if (tbref.Exception != null) continue;
|
||||||
|
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||||
|
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||||
|
if (boundaryAttr?.Break == true) continue;
|
||||||
|
switch (tbref.RefType)
|
||||||
|
{
|
||||||
|
case TableRefType.OneToOne:
|
||||||
|
var propval = table.GetPropertyValue(entity, prop.Name);
|
||||||
|
if (propval == null) continue;
|
||||||
|
statckPath.Push(prop.Name);
|
||||||
|
stackValues.Add(propval);
|
||||||
|
SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propval);
|
||||||
|
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues);
|
||||||
|
if (boundaryAttr?.BreakThen != true)
|
||||||
|
LocalNavigateReader(tbref.RefEntityType, propval);
|
||||||
|
stackValues.RemoveAt(stackValues.Count - 1);
|
||||||
|
statckPath.Pop();
|
||||||
|
break;
|
||||||
|
case TableRefType.OneToMany:
|
||||||
|
var propvalOtm = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
|
||||||
|
if (propvalOtm == null) continue;
|
||||||
|
SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propvalOtm);
|
||||||
|
var propvalOtmList = new List<object>();
|
||||||
|
foreach (var val in propvalOtm)
|
||||||
|
propvalOtmList.Add(val);
|
||||||
|
statckPath.Push($"{prop.Name}[]");
|
||||||
|
stackValues.Add(propvalOtmList.ToArray());
|
||||||
|
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues);
|
||||||
|
if (boundaryAttr?.BreakThen != true)
|
||||||
|
foreach (var val in propvalOtm)
|
||||||
|
LocalNavigateReader(tbref.RefEntityType, val);
|
||||||
|
stackValues.RemoveAt(stackValues.Count - 1);
|
||||||
|
statckPath.Pop();
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToMany:
|
||||||
|
var middleValues = GetManyToManyObjects(fsql, table, tbref, entity, prop).ToArray();
|
||||||
|
if (middleValues == null) continue;
|
||||||
|
statckPath.Push($"{prop.Name}[]");
|
||||||
|
stackValues.Add(middleValues);
|
||||||
|
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefMiddleEntityType, stackValues);
|
||||||
|
stackValues.RemoveAt(stackValues.Count - 1);
|
||||||
|
statckPath.Pop();
|
||||||
|
break;
|
||||||
|
case TableRefType.PgArrayToMany:
|
||||||
|
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MapEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo)
|
||||||
|
{
|
||||||
|
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||||
|
LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo, true);
|
||||||
|
ignores.Clear();
|
||||||
|
|
||||||
|
void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo, bool cascade)
|
||||||
|
{
|
||||||
|
if (entityFrom == null || entityTo == null) return;
|
||||||
|
if (entityType == null) entityType = entityFrom?.GetType() ?? entityTo?.GetType();
|
||||||
|
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||||
|
if (table == null) return;
|
||||||
|
|
||||||
|
var stateKey = fsql.GetEntityKeyString(entityType, entityFrom, false);
|
||||||
|
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||||
|
if (stateKeys.ContainsKey(stateKey)) return;
|
||||||
|
stateKeys.Add(stateKey, true);
|
||||||
|
|
||||||
|
foreach (var prop in table.Properties.Values)
|
||||||
|
{
|
||||||
|
if (table.ColumnsByCsIgnore.ContainsKey(prop.Name)) continue;
|
||||||
|
if (table.ColumnsByCs.ContainsKey(prop.Name))
|
||||||
|
{
|
||||||
|
table.SetPropertyValue(entityTo, prop.Name, table.GetPropertyValue(entityFrom, prop.Name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cascade == false) continue;
|
||||||
|
var tbref = table.GetTableRef(prop.Name, false);
|
||||||
|
if (tbref == null) continue;
|
||||||
|
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||||
|
if (boundaryAttr?.Break == true) continue;
|
||||||
|
var propvalFrom = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, entityType, entityFrom, prop.Name);
|
||||||
|
if (propvalFrom == null)
|
||||||
|
{
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (tbref.RefType)
|
||||||
|
{
|
||||||
|
case TableRefType.OneToOne:
|
||||||
|
var propvalTo = tbref.RefEntityType.CreateInstanceGetDefaultValue();
|
||||||
|
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom);
|
||||||
|
LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo, boundaryAttr?.BreakThen != true);
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo);
|
||||||
|
break;
|
||||||
|
case TableRefType.OneToMany:
|
||||||
|
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom);
|
||||||
|
LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, boundaryAttr?.BreakThen != true);
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToMany:
|
||||||
|
LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, false);
|
||||||
|
break;
|
||||||
|
case TableRefType.PgArrayToMany:
|
||||||
|
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void LocalMapEntityValueCollection(Type entityType, object entityFrom, object entityTo, TableRef tbref, IEnumerable propvalFrom, PropertyInfo prop, bool cascade)
|
||||||
|
{
|
||||||
|
var propvalTo = typeof(List<>).MakeGenericType(tbref.RefEntityType).CreateInstanceGetDefaultValue();
|
||||||
|
var propvalToIList = propvalTo as IList;
|
||||||
|
foreach (var fromItem in propvalFrom)
|
||||||
|
{
|
||||||
|
var toItem = tbref.RefEntityType.CreateInstanceGetDefaultValue();
|
||||||
|
LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem, cascade);
|
||||||
|
propvalToIList.Add(toItem);
|
||||||
|
}
|
||||||
|
var propvalType = prop.PropertyType.GetGenericTypeDefinition();
|
||||||
|
if (propvalType == typeof(List<>) || propvalType == typeof(ICollection<>))
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo);
|
||||||
|
else if (propvalType == typeof(ObservableCollection<>))
|
||||||
|
{
|
||||||
|
//var propvalTypeOcCtor = typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType).GetConstructor(new[] { typeof(List<>).MakeGenericType(tbref.RefEntityType) });
|
||||||
|
var propvalTypeOc = Activator.CreateInstance(typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType), new object[] { propvalTo });
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTypeOc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConcurrentDictionary<string, ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>> _dicGetAutoIncludeQuery = new ConcurrentDictionary<string, ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>>();
|
||||||
|
public static ISelect<TEntity> GetAutoIncludeQuery<TEntity>(string boundaryName, ISelect<TEntity> select)
|
||||||
|
{
|
||||||
|
var select0p = select as Select0Provider;
|
||||||
|
var table0Type = select0p._tables[0].Table.Type;
|
||||||
|
var func = _dicGetAutoIncludeQuery.GetOrAdd(boundaryName ?? "", bn => new ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>())
|
||||||
|
.GetOrAdd(typeof(TEntity), t => new ConcurrentDictionary<Type, Action<ISelect0>>())
|
||||||
|
.GetOrAdd(table0Type, t =>
|
||||||
|
{
|
||||||
|
var parmExp1 = Expression.Parameter(typeof(ISelect0));
|
||||||
|
var parmNavigateParameterExp = Expression.Parameter(typeof(TEntity), "a");
|
||||||
|
var parmQueryExp = Expression.Convert(parmExp1, typeof(ISelect<>).MakeGenericType(typeof(TEntity)));
|
||||||
|
var exp = LocalGetAutoIncludeQuery(parmQueryExp, 1, t, parmNavigateParameterExp, parmNavigateParameterExp, new Stack<Type>());
|
||||||
|
return Expression.Lambda<Action<ISelect0>>(exp, parmExp1).Compile();
|
||||||
|
});
|
||||||
|
func(select);
|
||||||
|
return select;
|
||||||
|
Expression LocalGetAutoIncludeQuery(Expression queryExp, int depth, Type entityType, ParameterExpression navigateParameterExp, Expression navigatePathExp, Stack<Type> ignores)
|
||||||
|
{
|
||||||
|
if (ignores.Any(a => a == entityType)) return queryExp;
|
||||||
|
ignores.Push(entityType);
|
||||||
|
var table = select0p._orm.CodeFirst.GetTableByEntity(entityType);
|
||||||
|
if (table == null) return queryExp;
|
||||||
|
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||||
|
{
|
||||||
|
var tbref = tr.Value;
|
||||||
|
if (tbref.Exception != null) continue;
|
||||||
|
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||||
|
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||||
|
if (boundaryAttr?.Break == true) continue;
|
||||||
|
Expression navigateExp = Expression.MakeMemberAccess(navigatePathExp, prop);
|
||||||
|
//var lambdaAlias = (char)((byte)'a' + (depth - 1));
|
||||||
|
switch (tbref.RefType)
|
||||||
|
{
|
||||||
|
case TableRefType.OneToOne:
|
||||||
|
if (ignores.Any(a => a == tbref.RefEntityType)) break;
|
||||||
|
LocalInclude(tbref, navigateExp);
|
||||||
|
if (boundaryAttr?.BreakThen != true)
|
||||||
|
queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores);
|
||||||
|
break;
|
||||||
|
case TableRefType.OneToMany:
|
||||||
|
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen != true);
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToMany:
|
||||||
|
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false);
|
||||||
|
break;
|
||||||
|
case TableRefType.PgArrayToMany:
|
||||||
|
if (boundaryAttr?.Break == false)
|
||||||
|
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false);
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||||
|
if (boundaryAttr?.Break == false)
|
||||||
|
{
|
||||||
|
LocalInclude(tbref, navigateExp);
|
||||||
|
if (boundaryAttr?.BreakThen == false)
|
||||||
|
queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ignores.Pop();
|
||||||
|
return queryExp;
|
||||||
|
void LocalInclude(TableRef tbref, Expression exp)
|
||||||
|
{
|
||||||
|
var incMethod = queryExp.Type.GetMethod("Include");
|
||||||
|
if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany.Replace("IncludeMany", "Include"));
|
||||||
|
queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType),
|
||||||
|
Expression.Lambda(typeof(Func<,>).MakeGenericType(entityType, tbref.RefEntityType), exp, navigateParameterExp));
|
||||||
|
}
|
||||||
|
void LocalIncludeMany(TableRef tbref, Expression exp, bool isthen)
|
||||||
|
{
|
||||||
|
var funcType = typeof(Func<,>).MakeGenericType(entityType, typeof(IEnumerable<>).MakeGenericType(tbref.RefEntityType));
|
||||||
|
var navigateSelector = Expression.Lambda(funcType, exp, navigateParameterExp);
|
||||||
|
var incMethod = queryExp.Type.GetMethod("IncludeMany");
|
||||||
|
if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany);
|
||||||
|
LambdaExpression navigateThen = null;
|
||||||
|
var navigateThenType = typeof(Action<>).MakeGenericType(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType));
|
||||||
|
var thenParameter = Expression.Parameter(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType), "then");
|
||||||
|
Expression paramQueryExp = thenParameter;
|
||||||
|
var paramNavigateParameterExp = Expression.Parameter(tbref.RefEntityType, string.Concat((char)((byte)'a' + (depth - 1))));
|
||||||
|
if (isthen) paramQueryExp = LocalGetAutoIncludeQuery(paramQueryExp, depth + 1, tbref.RefEntityType, paramNavigateParameterExp, paramNavigateParameterExp, ignores);
|
||||||
|
navigateThen = Expression.Lambda(navigateThenType, paramQueryExp, thenParameter);
|
||||||
|
queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType), navigateSelector, navigateThen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetAutoIncludeQueryStaicCode(string boundaryName, IFreeSql fsql, Type rootEntityType)
|
||||||
|
{
|
||||||
|
return $"//fsql.Select<{rootEntityType.Name}>()\r\nSelectDiy{LocalGetAutoIncludeQueryStaicCode(1, rootEntityType, "", new Stack<Type>())}";
|
||||||
|
string LocalGetAutoIncludeQueryStaicCode(int depth, Type entityType, string navigatePath, Stack<Type> ignores)
|
||||||
|
{
|
||||||
|
var code = new StringBuilder();
|
||||||
|
if (ignores.Any(a => a == entityType)) return null;
|
||||||
|
ignores.Push(entityType);
|
||||||
|
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||||
|
if (table == null) return null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}.";
|
||||||
|
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||||
|
{
|
||||||
|
var tbref = tr.Value;
|
||||||
|
if (tbref.Exception != null) continue;
|
||||||
|
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||||
|
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||||
|
if (boundaryAttr?.Break == true) continue;
|
||||||
|
var navigateExpression = $"{navigatePath}{tr.Key}";
|
||||||
|
var depthTab = "".PadLeft(depth * 4);
|
||||||
|
var lambdaAlias = (char)((byte)'a' + (depth - 1));
|
||||||
|
var lambdaStr = $"{lambdaAlias} => {lambdaAlias}.";
|
||||||
|
switch (tbref.RefType)
|
||||||
|
{
|
||||||
|
case TableRefType.OneToOne:
|
||||||
|
if (ignores.Any(a => a == tbref.RefEntityType)) break;
|
||||||
|
code.Append("\r\n").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")");
|
||||||
|
if (boundaryAttr?.BreakThen != true)
|
||||||
|
code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores));
|
||||||
|
break;
|
||||||
|
case TableRefType.OneToMany:
|
||||||
|
code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
|
||||||
|
if (boundaryAttr?.BreakThen != true)
|
||||||
|
{
|
||||||
|
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
|
||||||
|
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
|
||||||
|
}
|
||||||
|
code.Append(")");
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToMany:
|
||||||
|
code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
|
||||||
|
if (boundaryAttr?.BreakThen == false)
|
||||||
|
{
|
||||||
|
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
|
||||||
|
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
|
||||||
|
}
|
||||||
|
code.Append(")");
|
||||||
|
break;
|
||||||
|
case TableRefType.PgArrayToMany:
|
||||||
|
code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
|
||||||
|
if (boundaryAttr?.BreakThen == false)
|
||||||
|
{
|
||||||
|
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
|
||||||
|
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
|
||||||
|
}
|
||||||
|
code.Append(")");
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||||
|
code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")");
|
||||||
|
if (boundaryAttr?.BreakThen == false)
|
||||||
|
code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ignores.Pop();
|
||||||
|
return code.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<object> GetManyToManyObjects(IFreeSql fsql, TableInfo table, TableRef tbref, object entity, PropertyInfo prop)
|
||||||
|
{
|
||||||
|
if (tbref.RefType != TableRefType.ManyToMany) return null;
|
||||||
|
var rights = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
|
||||||
|
if (rights == null) return null;
|
||||||
|
var middles = new List<object>();
|
||||||
|
var leftpkvals = new object[tbref.Columns.Count];
|
||||||
|
for (var x = 0; x < tbref.Columns.Count; x++)
|
||||||
|
leftpkvals[x] = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, table.Type, entity, tbref.Columns[x].CsName));
|
||||||
|
foreach (var right in rights)
|
||||||
|
{
|
||||||
|
var midval = tbref.RefMiddleEntityType.CreateInstanceGetDefaultValue();
|
||||||
|
for (var x = 0; x < tbref.Columns.Count; x++)
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, leftpkvals[x]);
|
||||||
|
|
||||||
|
for (var x = tbref.Columns.Count; x < tbref.MiddleColumns.Count; x++)
|
||||||
|
{
|
||||||
|
var refcol = tbref.RefColumns[x - tbref.Columns.Count];
|
||||||
|
var refval = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, tbref.RefEntityType, right, refcol.CsName);
|
||||||
|
if (refval == refcol.CsType.CreateInstanceGetDefaultValue()) throw new Exception($"ManyToMany 关联对象的主键属性({tbref.RefEntityType.DisplayCsharp()}.{refcol.CsName})不能为空");
|
||||||
|
refval = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, refval);
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, refval);
|
||||||
|
}
|
||||||
|
middles.Add(midval);
|
||||||
|
}
|
||||||
|
return middles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem)
|
||||||
|
{
|
||||||
|
switch (tbref.RefType)
|
||||||
|
{
|
||||||
|
case TableRefType.OneToOne:
|
||||||
|
if (rightItem == null) return;
|
||||||
|
for (var idx = 0; idx < tbref.Columns.Count; idx++)
|
||||||
|
{
|
||||||
|
var colval = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName));
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName, colval);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TableRefType.OneToMany:
|
||||||
|
if (rightItem == null) return;
|
||||||
|
var rightEachOtm = rightItem as IEnumerable;
|
||||||
|
if (rightEachOtm == null) break;
|
||||||
|
var leftColValsOtm = new object[tbref.Columns.Count];
|
||||||
|
for (var idx = 0; idx < tbref.Columns.Count; idx++)
|
||||||
|
leftColValsOtm[idx] = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName));
|
||||||
|
foreach (var rightEle in rightEachOtm)
|
||||||
|
for (var idx = 0; idx < tbref.Columns.Count; idx++)
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightEle, tbref.RefColumns[idx].CsName, leftColValsOtm[idx]);
|
||||||
|
break;
|
||||||
|
case TableRefType.ManyToOne:
|
||||||
|
for (var idx = 0; idx < tbref.RefColumns.Count; idx++)
|
||||||
|
{
|
||||||
|
var colval = rightItem == null ?
|
||||||
|
tbref.Columns[idx].CsType.CreateInstanceGetDefaultValue() :
|
||||||
|
Utils.GetDataReaderValue(tbref.Columns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName));
|
||||||
|
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName, colval);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
using FreeSql;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
public static class FreeSqlAggregateRootRepositoryGlobalExtensions
|
||||||
|
{
|
||||||
|
public static IBaseRepository<TEntity> GetAggregateRootRepository<TEntity>(this IFreeSql that) where TEntity : class
|
||||||
|
{
|
||||||
|
return new AggregateRootRepository<TEntity>(that);
|
||||||
|
}
|
||||||
|
}
|
84
FreeSql.DbContext/FreeSql - Backup.DbContext.csproj
Normal file
84
FreeSql.DbContext/FreeSql - Backup.DbContext.csproj
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netstandard2.0;net60;net50;netcoreapp31;netcoreapp21;net45;net40</TargetFrameworks>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||||
|
<Description>FreeSql is the ORM in .NetCore, .NetFramework, And Xamarin. It supports Mysql, Postgresql, SqlServer, Oracle, Sqlite, Firebird, Odbc, 达梦, 人大金仓, 神舟通用, 南大通用, 翰高, And Access</Description>
|
||||||
|
<PackageProjectUrl>https://github.com/2881099/FreeSql/wiki/DbContext</PackageProjectUrl>
|
||||||
|
<PackageTags>FreeSql ORM DbContext</PackageTags>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
<PackageId>$(AssemblyName)</PackageId>
|
||||||
|
<PackageIcon>logo.png</PackageIcon>
|
||||||
|
<Title>$(AssemblyName)</Title>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
|
<SignAssembly>true</SignAssembly>
|
||||||
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
|
<DelaySign>false</DelaySign>
|
||||||
|
<Version>3.2.667</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="../logo.png" Pack="true" PackagePath="\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DocumentationFile>FreeSql.DbContext.xml</DocumentationFile>
|
||||||
|
<WarningLevel>3</WarningLevel>
|
||||||
|
<NoWarn>1701;1702;1591</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
|
||||||
|
<DefineConstants>net40</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net60' or '$(TargetFramework)' == 'net50' or '$(TargetFramework)' == 'netcoreapp31' or '$(TargetFramework)' == 'netcoreapp21'">
|
||||||
|
<DefineConstants>netcoreapp</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net60'">
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net50'">
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp31'">
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.10" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp21'">
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FreeSql\FreeSql.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Properties\DbContextStrings.Designer.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>DbContextStrings.Designer.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Properties\DbContextStrings.Designer.tt">
|
||||||
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
<LastGenOutput>DbContextStrings.Designer.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<EmbeddedResource Update="Properties\DbContextStrings.resx">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<CustomToolNamespace>FreeSql</CustomToolNamespace>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Update="Properties\DbContextStrings.zh-Hans.resx">
|
||||||
|
<CustomToolNamespace>FreeSql</CustomToolNamespace>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -4,6 +4,50 @@
|
|||||||
<name>FreeSql.DbContext</name>
|
<name>FreeSql.DbContext</name>
|
||||||
</assembly>
|
</assembly>
|
||||||
<members>
|
<members>
|
||||||
|
<member name="T:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute">
|
||||||
|
<summary>
|
||||||
|
设置 AggregateRootRepository 边界范围<para></para>
|
||||||
|
在边界范围之内的规则 :<para></para>
|
||||||
|
1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改<para></para>
|
||||||
|
2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略)<para></para>
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute.Break">
|
||||||
|
<summary>
|
||||||
|
边界是否终止
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute.BreakThen">
|
||||||
|
<summary>
|
||||||
|
边界是否终止向下探测
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:FreeSql.AggregateRootRepository`1.Select">
|
||||||
|
<summary>
|
||||||
|
默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)<para></para>
|
||||||
|
重写:使用
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:FreeSql.AggregateRootRepository`1.SelectDiy">
|
||||||
|
<summary>
|
||||||
|
创建查询对象(纯净)<para></para>
|
||||||
|
_<para></para>
|
||||||
|
聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发<para></para>
|
||||||
|
string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order))
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:FreeSql.AggregateRootRepository`1.SelectAggregateRoot">
|
||||||
|
<summary>
|
||||||
|
创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)
|
||||||
|
</summary>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:FreeSql.AggregateRootRepository`1.SelectAggregateRootTracking(System.Object)">
|
||||||
|
<summary>
|
||||||
|
ISelect.TrackToList 委托,数据返回后自动 Attach
|
||||||
|
</summary>
|
||||||
|
<param name="list"></param>
|
||||||
|
</member>
|
||||||
<member name="P:FreeSql.DbContext.Orm">
|
<member name="P:FreeSql.DbContext.Orm">
|
||||||
<summary>
|
<summary>
|
||||||
该对象 Select/Delete/Insert/Update/InsertOrUpdate 与 DbContext 事务保持一致,可省略传递 WithTransaction
|
该对象 Select/Delete/Insert/Update/InsertOrUpdate 与 DbContext 事务保持一致,可省略传递 WithTransaction
|
||||||
@ -733,6 +777,15 @@
|
|||||||
<param name="modelBuilder"></param>
|
<param name="modelBuilder"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:FreeSqlDbContextExtensions.ApplyConfigurationsFromAssembly(FreeSql.ICodeFirst,System.Reflection.Assembly,System.Func{System.Type,System.Boolean})">
|
||||||
|
<summary>
|
||||||
|
根据Assembly扫描所有继承IEntityTypeConfiguration<T>的配置类
|
||||||
|
</summary>
|
||||||
|
<param name="codeFirst"></param>
|
||||||
|
<param name="assembly"></param>
|
||||||
|
<param name="predicate"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
<member name="M:FreeSqlDbContextExtensions.CreateDbContext(IFreeSql)">
|
<member name="M:FreeSqlDbContextExtensions.CreateDbContext(IFreeSql)">
|
||||||
<summary>
|
<summary>
|
||||||
创建普通数据上下文档对象
|
创建普通数据上下文档对象
|
||||||
@ -791,5 +844,14 @@
|
|||||||
<param name="that"></param>
|
<param name="that"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:Microsoft.Extensions.DependencyInjection.FreeSqlRepositoryDependencyInjection.AddFreeRepository(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Action{FreeSql.FluentDataFilter},System.Reflection.Assembly[])">
|
||||||
|
<summary>
|
||||||
|
批量注入 Repository,可以参考代码自行调整
|
||||||
|
</summary>
|
||||||
|
<param name="services"></param>
|
||||||
|
<param name="globalDataFilter"></param>
|
||||||
|
<param name="assemblies"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
</members>
|
</members>
|
||||||
</doc>
|
</doc>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
66
FreeSql.Tests/FreeSql.Tests/Issues/1237.cs
Normal file
66
FreeSql.Tests/FreeSql.Tests/Issues/1237.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using FreeSql.DataAnnotations;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace FreeSql.Tests.Issues
|
||||||
|
{
|
||||||
|
public class _1237
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void WithTempQuery()
|
||||||
|
{
|
||||||
|
var fsql = g.sqlite;
|
||||||
|
var people1 = fsql.Select<people>()
|
||||||
|
.GroupBy(x => x.Name.Replace(" ", "").Replace(" ", ""))
|
||||||
|
.Having(x => x.Count() > 1)
|
||||||
|
.WithTempQuery(x => new { xm = x.Key })
|
||||||
|
.From<people>()
|
||||||
|
.InnerJoin((a, b) => a.xm == b.Name.Replace(" ", "").Replace(" ", ""))
|
||||||
|
.OrderBy((a, b) => b.Name)
|
||||||
|
.OrderBy((a, b) => b.ID)
|
||||||
|
.ToSql();
|
||||||
|
Assert.Equal(@"SELECT *
|
||||||
|
FROM (
|
||||||
|
SELECT replace(replace(a.""Name"", ' ', ''), ' ', '') ""xm""
|
||||||
|
FROM ""people_issues_1237"" a
|
||||||
|
GROUP BY replace(replace(a.""Name"", ' ', ''), ' ', '')
|
||||||
|
HAVING (count(1) > 1) ) a
|
||||||
|
INNER JOIN ""people_issues_1237"" b ON a.""xm"" = replace(replace(b.""Name"", ' ', ''), ' ', '')
|
||||||
|
ORDER BY b.""Name"", b.""ID""", people1);
|
||||||
|
|
||||||
|
var people2 = fsql.Select<people>()
|
||||||
|
.GroupBy(x => new { xm_new = x.Name.Replace(" ", "").Replace(" ", ""), csny = x.CSNY })
|
||||||
|
.Having(x => x.Count() > 1).WithTempQuery(x => new { xm = x.Key.xm_new, csny = x.Key.csny })
|
||||||
|
.From<people>()
|
||||||
|
.InnerJoin((a, b) => a.xm == b.Name.Replace(" ", "").Replace(" ", "") && a.csny == b.CSNY)
|
||||||
|
.OrderBy((a, b) => b.Name).OrderBy((a, b) => b.ID)
|
||||||
|
.ToSql();
|
||||||
|
Assert.Equal(@"SELECT *
|
||||||
|
FROM (
|
||||||
|
SELECT replace(replace(a.""Name"", ' ', ''), ' ', ''), a.""CSNY""
|
||||||
|
FROM ""people_issues_1237"" a
|
||||||
|
GROUP BY replace(replace(a.""Name"", ' ', ''), ' ', ''), a.""CSNY""
|
||||||
|
HAVING (count(1) > 1) ) a
|
||||||
|
INNER JOIN ""people_issues_1237"" b ON a.""xm_new"" = replace(replace(b.""Name"", ' ', ''), ' ', '') AND a.""csny"" = b.""CSNY""
|
||||||
|
ORDER BY b.""Name"", b.""ID""", people2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table(Name = "people_issues_1237")]
|
||||||
|
public partial class people
|
||||||
|
{
|
||||||
|
|
||||||
|
[Column(IsPrimary = true, IsIdentity = true)]
|
||||||
|
public int ID { get; set; }
|
||||||
|
|
||||||
|
[Column(DbType = "varchar(255)")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Column(DbType = "varchar(255)")]
|
||||||
|
public string CSNY { get; set; }
|
||||||
|
|
||||||
|
[Column(DbType = "varchar(255)")]
|
||||||
|
public string Sex { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,7 +17,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<SignAssembly>False</SignAssembly>
|
<SignAssembly>False</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<Title>$(AssemblyName)</Title>
|
<Title>$(AssemblyName)</Title>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<Title>$(AssemblyName)</Title>
|
<Title>$(AssemblyName)</Title>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<Title>$(AssemblyName)</Title>
|
<Title>$(AssemblyName)</Title>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<SignAssembly>true</SignAssembly>
|
<SignAssembly>true</SignAssembly>
|
||||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||||
<DelaySign>false</DelaySign>
|
<DelaySign>false</DelaySign>
|
||||||
<Version>3.2.666</Version>
|
<Version>3.2.667</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user