mirror of
https://github.com/nsnail/FreeSql.git
synced 2025-08-01 17:35:58 +08:00
initial commit
This commit is contained in:
335
FreeSql.DbContext/DbContext/DbContext.cs
Normal file
335
FreeSql.DbContext/DbContext/DbContext.cs
Normal file
@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using FreeSql.Internal.Model;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public abstract partial class DbContext : IDisposable
|
||||
{
|
||||
internal DbContextScopedFreeSql _ormScoped;
|
||||
internal IFreeSql OrmOriginal => _ormScoped?._originalFsql ?? throw new ArgumentNullException(DbContextStrings.ConfigureUseFreeSql);
|
||||
|
||||
/// <summary>
|
||||
/// 该对象 Select/Delete/Insert/Update/InsertOrUpdate 与 DbContext 事务保持一致,可省略传递 WithTransaction
|
||||
/// </summary>
|
||||
public IFreeSql Orm => _ormScoped ?? throw new ArgumentNullException(DbContextStrings.ConfigureUseFreeSql);
|
||||
|
||||
#region Property UnitOfWork
|
||||
internal bool _isUseUnitOfWork = true; //是否创建工作单元事务
|
||||
IUnitOfWork _uowPriv;
|
||||
public IUnitOfWork UnitOfWork
|
||||
{
|
||||
set => _uowPriv = value;
|
||||
get
|
||||
{
|
||||
if (_uowPriv != null) return _uowPriv;
|
||||
if (_isUseUnitOfWork == false) return null;
|
||||
return _uowPriv = new UnitOfWork(OrmOriginal);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Property Options
|
||||
internal DbContextOptions _optionsPriv;
|
||||
public DbContextOptions Options
|
||||
{
|
||||
set => _optionsPriv = value;
|
||||
get
|
||||
{
|
||||
if (_optionsPriv == null)
|
||||
{
|
||||
_optionsPriv = new DbContextOptions();
|
||||
if (FreeSqlDbContextExtensions._dicSetDbContextOptions.TryGetValue(OrmOriginal.Ado.Identifier, out var opt))
|
||||
{
|
||||
_optionsPriv.EnableCascadeSave = opt.EnableCascadeSave;
|
||||
_optionsPriv.EnableGlobalFilter = opt.EnableGlobalFilter;
|
||||
_optionsPriv.NoneParameter = opt.NoneParameter;
|
||||
_optionsPriv.OnEntityChange = opt.OnEntityChange;
|
||||
}
|
||||
}
|
||||
return _optionsPriv;
|
||||
}
|
||||
}
|
||||
internal void EmitOnEntityChange(List<EntityChangeReport.ChangeInfo> report)
|
||||
{
|
||||
var oec = UnitOfWork?.EntityChangeReport?.OnChange ?? Options.OnEntityChange;
|
||||
if (oec == null || report == null || report.Any() == false) return;
|
||||
oec(report);
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected DbContext() : this(null, null) { }
|
||||
protected DbContext(IFreeSql fsql, DbContextOptions options)
|
||||
{
|
||||
_ormScoped = DbContextScopedFreeSql.Create(fsql, () => this, () => UnitOfWork);
|
||||
_optionsPriv = options;
|
||||
|
||||
if (_ormScoped == null)
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder();
|
||||
OnConfiguring(builder);
|
||||
_ormScoped = DbContextScopedFreeSql.Create(builder._fsql, () => this, () => UnitOfWork);
|
||||
_optionsPriv = builder._options;
|
||||
}
|
||||
if (_ormScoped != null) InitPropSets();
|
||||
}
|
||||
protected virtual void OnConfiguring(DbContextOptionsBuilder options) { }
|
||||
protected virtual void OnModelCreating(ICodeFirst codefirst) { }
|
||||
|
||||
#region Set
|
||||
static ConcurrentDictionary<Type, NativeTuple<PropertyInfo[], bool>> _dicGetDbSetProps = new ConcurrentDictionary<Type, NativeTuple<PropertyInfo[], bool>>();
|
||||
static object _lockOnModelCreating = new object();
|
||||
internal void InitPropSets()
|
||||
{
|
||||
var thisType = this.GetType();
|
||||
var isOnModelCreating = false;
|
||||
if (_dicGetDbSetProps.TryGetValue(thisType, out var dicval) == false)
|
||||
{
|
||||
lock (_lockOnModelCreating)
|
||||
{
|
||||
if (_dicGetDbSetProps.TryGetValue(thisType, out dicval) == false)
|
||||
{
|
||||
dicval = NativeTuple.Create(
|
||||
thisType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(a => a.PropertyType.IsGenericType &&
|
||||
a.PropertyType == typeof(DbSet<>).MakeGenericType(a.PropertyType.GetGenericArguments()[0])).ToArray(),
|
||||
false);
|
||||
_dicGetDbSetProps.TryAdd(thisType, dicval);
|
||||
isOnModelCreating = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isOnModelCreating)
|
||||
OnModelCreating(OrmOriginal.CodeFirst);
|
||||
|
||||
foreach (var prop in dicval.Item1)
|
||||
{
|
||||
var set = this.Set(prop.PropertyType.GetGenericArguments()[0]);
|
||||
|
||||
prop.SetValue(this, set, null);
|
||||
AllSets.Add(prop.Name, set);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<IDbSet> _listSet = new List<IDbSet>();
|
||||
protected Dictionary<Type, IDbSet> _dicSet = new Dictionary<Type, IDbSet>();
|
||||
internal Dictionary<Type, IDbSet> InternalDicSet => _dicSet;
|
||||
public DbSet<TEntity> Set<TEntity>() where TEntity : class => this.Set(typeof(TEntity)) as DbSet<TEntity>;
|
||||
public virtual IDbSet Set(Type entityType)
|
||||
{
|
||||
if (_dicSet.ContainsKey(entityType)) return _dicSet[entityType];
|
||||
var sd = Activator.CreateInstance(typeof(DbContextDbSet<>).MakeGenericType(entityType), this) as IDbSet;
|
||||
_listSet.Add(sd);
|
||||
if (entityType != typeof(object)) _dicSet.Add(entityType, sd);
|
||||
return sd;
|
||||
}
|
||||
protected Dictionary<string, IDbSet> AllSets { get; } = new Dictionary<string, IDbSet>();
|
||||
#endregion
|
||||
|
||||
#region DbSet 快速代理
|
||||
void CheckEntityTypeOrThrow(Type entityType)
|
||||
{
|
||||
if (OrmOriginal.CodeFirst.GetTableByEntity(entityType) == null)
|
||||
throw new ArgumentException(DbContextStrings.ParameterDataTypeError(entityType.FullName));
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="data"></param>
|
||||
public void Add<TEntity>(TEntity data) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
this.Set<TEntity>().Add(data);
|
||||
}
|
||||
public void AddRange<TEntity>(IEnumerable<TEntity> data) where TEntity : class => this.Set<TEntity>().AddRange(data);
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="data"></param>
|
||||
public void Update<TEntity>(TEntity data) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
this.Set<TEntity>().Update(data);
|
||||
}
|
||||
public void UpdateRange<TEntity>(IEnumerable<TEntity> data) where TEntity : class => this.Set<TEntity>().UpdateRange(data);
|
||||
|
||||
/// <summary>
|
||||
/// 删除
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="data"></param>
|
||||
public void Remove<TEntity>(TEntity data) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
this.Set<TEntity>().Remove(data);
|
||||
}
|
||||
public void RemoveRange<TEntity>(IEnumerable<TEntity> data) where TEntity : class => this.Set<TEntity>().RemoveRange(data);
|
||||
|
||||
/// <summary>
|
||||
/// 添加或更新
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="data"></param>
|
||||
public void AddOrUpdate<TEntity>(TEntity data) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
this.Set<TEntity>().AddOrUpdate(data);
|
||||
}
|
||||
/// <summary>
|
||||
/// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
|
||||
/// 场景:在关闭级联保存功能之后,手工使用本方法<para></para>
|
||||
/// 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus")<para></para>
|
||||
/// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
|
||||
/// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
|
||||
/// </summary>
|
||||
/// <param name="data">实体对象</param>
|
||||
/// <param name="propertyName">属性名</param>
|
||||
public void SaveMany<TEntity>(TEntity data, string propertyName) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
this.Set<TEntity>().SaveMany(data, propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 附加实体,可用于不查询就更新或删除
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="data"></param>
|
||||
public void Attach<TEntity>(TEntity data) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
this.Set<TEntity>().Attach(data);
|
||||
}
|
||||
public void AttachRange<TEntity>(IEnumerable<TEntity> data) where TEntity : class => this.Set<TEntity>().AttachRange(data);
|
||||
|
||||
/// <summary>
|
||||
/// 附加实体,并且只附加主键值,可用于不更新属性值为null或默认值的字段
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="data"></param>
|
||||
public DbContext AttachOnlyPrimary<TEntity>(TEntity data) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
this.Set<TEntity>().AttachOnlyPrimary(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 比较实体,计算出值发生变化的属性,以及属性变化的前后值
|
||||
/// </summary>
|
||||
/// <param name="newdata">最新的实体对象,它将与附加实体的状态对比</param>
|
||||
/// <returns>key: 属性名, value: [旧值, 新值]</returns>
|
||||
public Dictionary<string, object[]> CompareState<TEntity>(TEntity newdata) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
return this.Set<TEntity>().CompareState(newdata);
|
||||
}
|
||||
#if net40
|
||||
#else
|
||||
public Task AddAsync<TEntity>(TEntity data, CancellationToken cancellationToken = default) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
return this.Set<TEntity>().AddAsync(data, cancellationToken);
|
||||
}
|
||||
public Task AddRangeAsync<TEntity>(IEnumerable<TEntity> data, CancellationToken cancellationToken = default) where TEntity : class => this.Set<TEntity>().AddRangeAsync(data, cancellationToken);
|
||||
|
||||
public Task UpdateAsync<TEntity>(TEntity data, CancellationToken cancellationToken = default) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
return this.Set<TEntity>().UpdateAsync(data, cancellationToken);
|
||||
}
|
||||
public Task UpdateRangeAsync<TEntity>(IEnumerable<TEntity> data, CancellationToken cancellationToken = default) where TEntity : class => this.Set<TEntity>().UpdateRangeAsync(data, cancellationToken);
|
||||
|
||||
public Task AddOrUpdateAsync<TEntity>(TEntity data, CancellationToken cancellationToken = default) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
return this.Set<TEntity>().AddOrUpdateAsync(data, cancellationToken);
|
||||
}
|
||||
public Task SaveManyAsync<TEntity>(TEntity data, string propertyName, CancellationToken cancellationToken = default) where TEntity : class
|
||||
{
|
||||
CheckEntityTypeOrThrow(typeof(TEntity));
|
||||
return this.Set<TEntity>().SaveManyAsync(data, propertyName, cancellationToken);
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Queue PreCommand
|
||||
public class EntityChangeReport
|
||||
{
|
||||
public class ChangeInfo
|
||||
{
|
||||
public object Object { get; set; }
|
||||
/// <summary>
|
||||
/// Type = Update 的时候,获取更新之前的对象
|
||||
/// </summary>
|
||||
public object BeforeObject { get; set; }
|
||||
public EntityChangeType Type { get; set; }
|
||||
/// <summary>
|
||||
/// 实体类型
|
||||
/// </summary>
|
||||
public Type EntityType { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 实体变化记录
|
||||
/// </summary>
|
||||
public List<ChangeInfo> Report { get; } = new List<ChangeInfo>();
|
||||
/// <summary>
|
||||
/// 实体变化事件
|
||||
/// </summary>
|
||||
public Action<List<ChangeInfo>> OnChange { get; set; }
|
||||
}
|
||||
internal List<EntityChangeReport.ChangeInfo> _entityChangeReport = new List<EntityChangeReport.ChangeInfo>();
|
||||
public enum EntityChangeType { Insert, Update, Delete, SqlRaw }
|
||||
internal class PrevCommandInfo
|
||||
{
|
||||
public EntityChangeType changeType { get; set; }
|
||||
public IDbSet dbSet { get; set; }
|
||||
public Type stateType { get; set; }
|
||||
public Type entityType { get; set; }
|
||||
public object state { get; set; }
|
||||
}
|
||||
Queue<PrevCommandInfo> _prevCommands = new Queue<PrevCommandInfo>();
|
||||
internal int _affrows = 0;
|
||||
|
||||
internal void EnqueuePreCommand(EntityChangeType changeType, IDbSet dbSet, Type stateType, Type entityType, object state) =>
|
||||
_prevCommands.Enqueue(new PrevCommandInfo { changeType = changeType, dbSet = dbSet, stateType = stateType, entityType = entityType, state = state });
|
||||
#endregion
|
||||
|
||||
~DbContext() => this.Dispose();
|
||||
int _disposeCounter;
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Increment(ref _disposeCounter) != 1) return;
|
||||
try
|
||||
{
|
||||
_prevCommands.Clear();
|
||||
|
||||
foreach (var set in _listSet)
|
||||
try
|
||||
{
|
||||
set.Dispose();
|
||||
}
|
||||
catch { }
|
||||
|
||||
_listSet.Clear();
|
||||
_dicSet.Clear();
|
||||
AllSets.Clear();
|
||||
|
||||
if (_isUseUnitOfWork)
|
||||
UnitOfWork?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
162
FreeSql.DbContext/DbContext/DbContextAsync.cs
Normal file
162
FreeSql.DbContext/DbContext/DbContextAsync.cs
Normal file
@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#if net40
|
||||
#else
|
||||
namespace FreeSql
|
||||
{
|
||||
partial class DbContext
|
||||
{
|
||||
public virtual async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await FlushCommandAsync(cancellationToken);
|
||||
return SaveChangesSuccess();
|
||||
}
|
||||
|
||||
static ConcurrentDictionary<Type, ConcurrentDictionary<string, Func<object, object[], CancellationToken, Task<int>>>> _dicFlushCommandDbSetBatchAsync = new ConcurrentDictionary<Type, ConcurrentDictionary<string, Func<object, object[], CancellationToken, Task<int>>>>();
|
||||
internal async Task FlushCommandAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (isFlushCommanding) return;
|
||||
if (_prevCommands.Any() == false) return;
|
||||
isFlushCommanding = true;
|
||||
|
||||
PrevCommandInfo oldinfo = null;
|
||||
var states = new List<object>();
|
||||
var flagFuncUpdateLaststate = false;
|
||||
|
||||
Task<int> dbsetBatch(string method)
|
||||
{
|
||||
var tryfunc = _dicFlushCommandDbSetBatchAsync
|
||||
.GetOrAdd(oldinfo.stateType, stateType => new ConcurrentDictionary<string, Func<object, object[], CancellationToken, Task<int>>>())
|
||||
.GetOrAdd(method, methodName =>
|
||||
{
|
||||
var arrType = oldinfo.stateType.MakeArrayType();
|
||||
var dbsetType = oldinfo.dbSet.GetType().BaseType;
|
||||
var dbsetTypeMethod = dbsetType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType, typeof(CancellationToken) }, null);
|
||||
|
||||
var returnTarget = Expression.Label(typeof(Task<int>));
|
||||
var parm1DbSet = Expression.Parameter(typeof(object));
|
||||
var parm2Vals = Expression.Parameter(typeof(object[]));
|
||||
var parm3CancelToken = Expression.Parameter(typeof(CancellationToken));
|
||||
var var1Vals = Expression.Variable(arrType);
|
||||
return Expression.Lambda<Func<object, object[], CancellationToken, Task<int>>>(Expression.Block(
|
||||
new[] { var1Vals },
|
||||
Expression.Assign(var1Vals, Expression.Convert(global::FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(arrType, parm2Vals), arrType)),
|
||||
Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals, parm3CancelToken)),
|
||||
Expression.Label(returnTarget, Expression.Default(typeof(Task<int>)))
|
||||
), new[] { parm1DbSet, parm2Vals, parm3CancelToken }).Compile();
|
||||
});
|
||||
return tryfunc(oldinfo.dbSet, states.ToArray(), cancellationToken);
|
||||
}
|
||||
async Task funcDelete()
|
||||
{
|
||||
_affrows += await dbsetBatch("DbContextBatchRemoveAsync");
|
||||
states.Clear();
|
||||
}
|
||||
async Task funcInsert()
|
||||
{
|
||||
_affrows += await dbsetBatch("DbContextBatchAddAsync");
|
||||
states.Clear();
|
||||
};
|
||||
async Task funcUpdate(bool isLiveUpdate)
|
||||
{
|
||||
var affrows = 0;
|
||||
if (isLiveUpdate) affrows = await dbsetBatch("DbContextBatchUpdateNowAsync");
|
||||
else affrows = await dbsetBatch("DbContextBatchUpdateAsync");
|
||||
if (affrows == -999)
|
||||
{ //最后一个元素已被删除
|
||||
states.RemoveAt(states.Count - 1);
|
||||
return;
|
||||
}
|
||||
if (affrows == -998 || affrows == -997)
|
||||
{ //没有执行更新
|
||||
var laststate = states[states.Count - 1];
|
||||
states.Clear();
|
||||
if (affrows == -997)
|
||||
{
|
||||
flagFuncUpdateLaststate = true;
|
||||
states.Add(laststate); //保留最后一个
|
||||
}
|
||||
}
|
||||
if (affrows > 0)
|
||||
{
|
||||
_affrows += affrows;
|
||||
var islastNotUpdated = states.Count != affrows;
|
||||
var laststate = states[states.Count - 1];
|
||||
states.Clear();
|
||||
if (islastNotUpdated)
|
||||
{
|
||||
flagFuncUpdateLaststate = true;
|
||||
states.Add(laststate); //保留最后一个
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
while (_prevCommands.Any() || states.Any())
|
||||
{
|
||||
var info = _prevCommands.Any() ? _prevCommands.Dequeue() : null;
|
||||
if (oldinfo == null) oldinfo = info;
|
||||
var isLiveUpdate = false;
|
||||
flagFuncUpdateLaststate = false;
|
||||
|
||||
if (_prevCommands.Any() == false && states.Any() ||
|
||||
info != null && oldinfo.changeType != info.changeType ||
|
||||
info != null && oldinfo.stateType != info.stateType ||
|
||||
info != null && oldinfo.entityType != info.entityType)
|
||||
{
|
||||
|
||||
if (info != null && oldinfo.changeType == info.changeType && oldinfo.stateType == info.stateType && oldinfo.entityType == info.entityType)
|
||||
{
|
||||
//最后一个,合起来发送
|
||||
states.Add(info.state);
|
||||
info = null;
|
||||
}
|
||||
|
||||
switch (oldinfo.changeType)
|
||||
{
|
||||
case EntityChangeType.Insert:
|
||||
await funcInsert();
|
||||
break;
|
||||
case EntityChangeType.Delete:
|
||||
await funcDelete();
|
||||
break;
|
||||
}
|
||||
isLiveUpdate = true;
|
||||
}
|
||||
|
||||
if (isLiveUpdate || oldinfo.changeType == EntityChangeType.Update)
|
||||
{
|
||||
if (states.Any())
|
||||
{
|
||||
await funcUpdate(isLiveUpdate);
|
||||
if (info?.changeType == EntityChangeType.Update)
|
||||
flagFuncUpdateLaststate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
states.Add(info.state);
|
||||
oldinfo = info;
|
||||
|
||||
if (flagFuncUpdateLaststate && oldinfo.changeType == EntityChangeType.Update) //马上与上个元素比较
|
||||
await funcUpdate(isLiveUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
isFlushCommanding = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
80
FreeSql.DbContext/DbContext/DbContextOptions.cs
Normal file
80
FreeSql.DbContext/DbContext/DbContextOptions.cs
Normal file
@ -0,0 +1,80 @@
|
||||
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public class DbContextOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否开启 一对一(OneToOne)、一对多(OneToMany)、多对多(ManyToMany) 级联保存功能<para></para>
|
||||
/// <para></para>
|
||||
/// 【一对一】模型下,保存时级联保存 OneToOne 属性。
|
||||
/// <para></para>
|
||||
/// 【一对多】模型下,保存时级联保存 OneToMany 集合属性。出于安全考虑我们没做完整对比,只针对实体属性集合的添加或更新操作,因此不会删除数据库表已有的数据。<para></para>
|
||||
/// 完整对比的功能使用起来太危险,试想下面的场景:<para></para>
|
||||
/// - 保存的时候,实体的属性集合为空时(!=null),表记录全部删除?<para></para>
|
||||
/// - 保存的时候,由于数据库子表的记录很多,只想保存子表的部分数据,又或者只需要添加,如何操作?
|
||||
/// <para></para>
|
||||
/// 【多对多】模型下,对中间表的保存是完整对比操作,对外部实体的只作新增操作(*注意不会更新)<para></para>
|
||||
/// - 属性集合为空时(!=null),删除他们的所有关联数据(中间表)<para></para>
|
||||
/// - 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录
|
||||
/// </summary>
|
||||
public bool EnableCascadeSave { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 因增加支持 OneToOne 级联保存,和基于内存的级联删除,已改名为 EnableCascadeSave
|
||||
/// </summary>
|
||||
[Obsolete("因增加支持 OneToOne 级联保存,和基于内存的级联删除,已改名为 EnableCascadeSave")]
|
||||
public bool EnableAddOrUpdateNavigateList
|
||||
{
|
||||
get => EnableCascadeSave;
|
||||
set => EnableCascadeSave = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用无参数化设置(对应 IInsert/IUpdate)
|
||||
/// </summary>
|
||||
public bool? NoneParameter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否开启 IFreeSql GlobalFilter 功能(默认:true)
|
||||
/// </summary>
|
||||
public bool EnableGlobalFilter { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 实体变化事件
|
||||
/// </summary>
|
||||
public Action<List<DbContext.EntityChangeReport.ChangeInfo>> OnEntityChange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DbContext/Repository 审计值,适合 Scoped IOC 中获取登陆信息
|
||||
/// </summary>
|
||||
public Action<DbContextAuditValueEventArgs> AuditValue;
|
||||
}
|
||||
|
||||
public class DbContextAuditValueEventArgs : EventArgs
|
||||
{
|
||||
public DbContextAuditValueEventArgs(Aop.AuditValueType auditValueType, Type entityType, object obj)
|
||||
{
|
||||
this.AuditValueType = auditValueType;
|
||||
this.EntityType = entityType;
|
||||
this.Object = obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类型
|
||||
/// </summary>
|
||||
public Aop.AuditValueType AuditValueType { get; }
|
||||
/// <summary>
|
||||
/// 类型
|
||||
/// </summary>
|
||||
public Type EntityType { get; }
|
||||
/// <summary>
|
||||
/// 实体对象
|
||||
/// </summary>
|
||||
public object Object { get; }
|
||||
}
|
||||
}
|
22
FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs
Normal file
22
FreeSql.DbContext/DbContext/DbContextOptionsBuilder.cs
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public class DbContextOptionsBuilder
|
||||
{
|
||||
|
||||
internal IFreeSql _fsql;
|
||||
internal DbContextOptions _options;
|
||||
|
||||
public DbContextOptionsBuilder UseFreeSql(IFreeSql orm)
|
||||
{
|
||||
_fsql = orm;
|
||||
return this;
|
||||
}
|
||||
public DbContextOptionsBuilder UseOptions(DbContextOptions options)
|
||||
{
|
||||
_options = options;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
123
FreeSql.DbContext/DbContext/DbContextScopedFreeSql.cs
Normal file
123
FreeSql.DbContext/DbContext/DbContextScopedFreeSql.cs
Normal file
@ -0,0 +1,123 @@
|
||||
using FreeSql;
|
||||
using FreeSql.Internal;
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
using FreeSql.Internal.Model;
|
||||
using FreeSql.Internal.ObjectPool;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Text;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
class DbContextScopedFreeSql : IFreeSql
|
||||
{
|
||||
public IFreeSql _originalFsql;
|
||||
Func<DbContext> _resolveDbContext;
|
||||
Func<IUnitOfWork> _resolveUnitOfWork;
|
||||
DbContextScopedFreeSql() { }
|
||||
|
||||
public static DbContextScopedFreeSql Create(IFreeSql fsql, Func<DbContext> resolveDbContext, Func<IUnitOfWork> resolveUnitOfWork)
|
||||
{
|
||||
if (fsql == null) return null;
|
||||
var scopedfsql = fsql as DbContextScopedFreeSql;
|
||||
if (scopedfsql == null)
|
||||
return new DbContextScopedFreeSql
|
||||
{
|
||||
_originalFsql = fsql,
|
||||
_resolveDbContext = resolveDbContext,
|
||||
_resolveUnitOfWork = resolveUnitOfWork,
|
||||
Ado = new ScopeTransactionAdo(fsql.Ado as AdoProvider, () =>
|
||||
{
|
||||
var db = resolveDbContext?.Invoke();
|
||||
db?.FlushCommand();
|
||||
return resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction();
|
||||
})
|
||||
};
|
||||
return Create(scopedfsql._originalFsql, resolveDbContext, resolveUnitOfWork);
|
||||
}
|
||||
|
||||
class ScopeTransactionAdo : AdoProvider
|
||||
{
|
||||
AdoProvider _ado;
|
||||
public ScopeTransactionAdo(AdoProvider ado, Func<DbTransaction> resolveTran) : base(ado.DataType, null, null)
|
||||
{
|
||||
_ado = ado;
|
||||
base.ResolveTransaction = resolveTran;
|
||||
base.ConnectionString = ado.ConnectionString;
|
||||
base.SlaveConnectionStrings = ado.SlaveConnectionStrings;
|
||||
base.Identifier = ado.Identifier;
|
||||
base.MasterPool = ado.MasterPool;
|
||||
base._util = ado._util;
|
||||
}
|
||||
public override object AddslashesProcessParam(object param, Type mapType, ColumnInfo mapColumn) => _ado.AddslashesProcessParam(param, mapType, mapColumn);
|
||||
public override DbCommand CreateCommand() => _ado.CreateCommand();
|
||||
public override DbParameter[] GetDbParamtersByObject(string sql, object obj) => _ado.GetDbParamtersByObject(sql, obj);
|
||||
public override void ReturnConnection(IObjectPool<DbConnection> pool, Object<DbConnection> conn, Exception ex) => _ado.ReturnConnection(pool, conn, ex);
|
||||
}
|
||||
public IAdo Ado { get; private set; }
|
||||
public IAop Aop => _originalFsql.Aop;
|
||||
public ICodeFirst CodeFirst => _originalFsql.CodeFirst;
|
||||
public IDbFirst DbFirst => _originalFsql.DbFirst;
|
||||
public GlobalFilter GlobalFilter => _originalFsql.GlobalFilter;
|
||||
public void Dispose() { }
|
||||
|
||||
public void Transaction(Action handler) => _originalFsql.Transaction(handler);
|
||||
public void Transaction(IsolationLevel isolationLevel, Action handler) => _originalFsql.Transaction(isolationLevel, handler);
|
||||
|
||||
public ISelect<T1> Select<T1>() where T1 : class
|
||||
{
|
||||
var db = _resolveDbContext?.Invoke();
|
||||
db?.FlushCommand();
|
||||
var uow = _resolveUnitOfWork?.Invoke();
|
||||
var uowIsolationLevel = uow?.IsolationLevel ?? IsolationLevel.Unspecified;
|
||||
var select = _originalFsql.Select<T1>().WithTransaction(uow?.GetOrBeginTransaction(uowIsolationLevel != IsolationLevel.Unspecified));
|
||||
(select as Select0Provider)._resolveHookTransaction = () => uow?.GetOrBeginTransaction();
|
||||
if (db?.Options.EnableGlobalFilter == false) select.DisableGlobalFilter();
|
||||
return select;
|
||||
}
|
||||
public ISelect<T1> Select<T1>(object dywhere) where T1 : class => Select<T1>().WhereDynamic(dywhere);
|
||||
|
||||
public IDelete<T1> Delete<T1>() where T1 : class
|
||||
{
|
||||
var db = _resolveDbContext?.Invoke();
|
||||
db?.FlushCommand();
|
||||
var delete = _originalFsql.Delete<T1>().WithTransaction(_resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction());
|
||||
if (db?.Options.EnableGlobalFilter == false) delete.DisableGlobalFilter();
|
||||
return delete;
|
||||
}
|
||||
public IDelete<T1> Delete<T1>(object dywhere) where T1 : class => Delete<T1>().WhereDynamic(dywhere);
|
||||
|
||||
public IUpdate<T1> Update<T1>() where T1 : class
|
||||
{
|
||||
var db = _resolveDbContext?.Invoke();
|
||||
db?.FlushCommand();
|
||||
var update = _originalFsql.Update<T1>().WithTransaction(_resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction());
|
||||
if (db?.Options.NoneParameter != null) update.NoneParameter(db.Options.NoneParameter.Value);
|
||||
if (db?.Options.EnableGlobalFilter == false) update.DisableGlobalFilter();
|
||||
return update;
|
||||
}
|
||||
public IUpdate<T1> Update<T1>(object dywhere) where T1 : class => Update<T1>().WhereDynamic(dywhere);
|
||||
|
||||
public IInsert<T1> Insert<T1>() where T1 : class
|
||||
{
|
||||
var db = _resolveDbContext?.Invoke();
|
||||
db?.FlushCommand();
|
||||
var insert = _originalFsql.Insert<T1>().WithTransaction(_resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction());
|
||||
if (db?.Options.NoneParameter != null) insert.NoneParameter(db.Options.NoneParameter.Value);
|
||||
return insert;
|
||||
}
|
||||
public IInsert<T1> Insert<T1>(T1 source) where T1 : class => Insert<T1>().AppendData(source);
|
||||
public IInsert<T1> Insert<T1>(T1[] source) where T1 : class => Insert<T1>().AppendData(source);
|
||||
public IInsert<T1> Insert<T1>(List<T1> source) where T1 : class => Insert<T1>().AppendData(source);
|
||||
public IInsert<T1> Insert<T1>(IEnumerable<T1> source) where T1 : class => Insert<T1>().AppendData(source);
|
||||
|
||||
public IInsertOrUpdate<T1> InsertOrUpdate<T1>() where T1 : class
|
||||
{
|
||||
var db = _resolveDbContext?.Invoke();
|
||||
db?.FlushCommand();
|
||||
return _originalFsql.InsertOrUpdate<T1>().WithTransaction(_resolveUnitOfWork?.Invoke()?.GetOrBeginTransaction());
|
||||
}
|
||||
}
|
||||
}
|
176
FreeSql.DbContext/DbContext/DbContextSync.cs
Normal file
176
FreeSql.DbContext/DbContext/DbContextSync.cs
Normal file
@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
partial class DbContext
|
||||
{
|
||||
int SaveChangesSuccess()
|
||||
{
|
||||
UnitOfWork?.Commit();
|
||||
int ret;
|
||||
try
|
||||
{
|
||||
EmitOnEntityChange(_entityChangeReport);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_entityChangeReport.Clear();
|
||||
ret = _affrows;
|
||||
_affrows = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
public virtual int SaveChanges()
|
||||
{
|
||||
FlushCommand();
|
||||
return SaveChangesSuccess();
|
||||
}
|
||||
|
||||
static ConcurrentDictionary<Type, ConcurrentDictionary<string, Func<object, object[], int>>> _dicFlushCommandDbSetBatch = new ConcurrentDictionary<Type, ConcurrentDictionary<string, Func<object, object[], int>>>();
|
||||
bool isFlushCommanding = false;
|
||||
/// <summary>
|
||||
/// 刷新队列中的命令
|
||||
/// </summary>
|
||||
internal void FlushCommand()
|
||||
{
|
||||
if (isFlushCommanding) return;
|
||||
if (_prevCommands.Any() == false) return;
|
||||
isFlushCommanding = true;
|
||||
|
||||
PrevCommandInfo oldinfo = null;
|
||||
var states = new List<object>();
|
||||
var flagFuncUpdateLaststate = false;
|
||||
|
||||
int dbsetBatch(string method)
|
||||
{
|
||||
var tryfunc = _dicFlushCommandDbSetBatch
|
||||
.GetOrAdd(oldinfo.stateType, stateType => new ConcurrentDictionary<string, Func<object, object[], int>>())
|
||||
.GetOrAdd(method, methodName =>
|
||||
{
|
||||
var arrType = oldinfo.stateType.MakeArrayType();
|
||||
var dbsetType = oldinfo.dbSet.GetType().BaseType;
|
||||
var dbsetTypeMethod = dbsetType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { arrType }, null);
|
||||
|
||||
var returnTarget = Expression.Label(typeof(int));
|
||||
var parm1DbSet = Expression.Parameter(typeof(object));
|
||||
var parm2Vals = Expression.Parameter(typeof(object[]));
|
||||
var var1Vals = Expression.Variable(arrType);
|
||||
return Expression.Lambda<Func<object, object[], int>>(Expression.Block(
|
||||
new[] { var1Vals },
|
||||
Expression.Assign(var1Vals, Expression.Convert(global::FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(arrType, parm2Vals), arrType)),
|
||||
Expression.Return(returnTarget, Expression.Call(Expression.Convert(parm1DbSet, dbsetType), dbsetTypeMethod, var1Vals)),
|
||||
Expression.Label(returnTarget, Expression.Default(typeof(int)))
|
||||
), new[] { parm1DbSet, parm2Vals }).Compile();
|
||||
});
|
||||
return tryfunc(oldinfo.dbSet, states.ToArray());
|
||||
}
|
||||
void funcDelete()
|
||||
{
|
||||
_affrows += dbsetBatch("DbContextBatchRemove");
|
||||
states.Clear();
|
||||
}
|
||||
void funcInsert()
|
||||
{
|
||||
_affrows += dbsetBatch("DbContextBatchAdd");
|
||||
states.Clear();
|
||||
};
|
||||
void funcUpdate(bool isLiveUpdate)
|
||||
{
|
||||
var affrows = 0;
|
||||
if (isLiveUpdate) affrows = dbsetBatch("DbContextBatchUpdateNow");
|
||||
else affrows = dbsetBatch("DbContextBatchUpdate");
|
||||
if (affrows == -999)
|
||||
{ //最后一个元素已被删除
|
||||
states.RemoveAt(states.Count - 1);
|
||||
return;
|
||||
}
|
||||
if (affrows == -998 || affrows == -997)
|
||||
{ //没有执行更新
|
||||
var laststate = states[states.Count - 1];
|
||||
states.Clear();
|
||||
if (affrows == -997)
|
||||
{
|
||||
flagFuncUpdateLaststate = true;
|
||||
states.Add(laststate); //保留最后一个
|
||||
}
|
||||
}
|
||||
if (affrows > 0)
|
||||
{
|
||||
_affrows += affrows;
|
||||
var islastNotUpdated = states.Count != affrows;
|
||||
var laststate = states[states.Count - 1];
|
||||
states.Clear();
|
||||
if (islastNotUpdated)
|
||||
{
|
||||
flagFuncUpdateLaststate = true;
|
||||
states.Add(laststate); //保留最后一个
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
while (_prevCommands.Any() || states.Any())
|
||||
{
|
||||
var info = _prevCommands.Any() ? _prevCommands.Dequeue() : null;
|
||||
if (oldinfo == null) oldinfo = info;
|
||||
var isLiveUpdate = false;
|
||||
flagFuncUpdateLaststate = false;
|
||||
|
||||
if (_prevCommands.Any() == false && states.Any() ||
|
||||
info != null && oldinfo.changeType != info.changeType ||
|
||||
info != null && oldinfo.stateType != info.stateType ||
|
||||
info != null && oldinfo.entityType != info.entityType)
|
||||
{
|
||||
|
||||
if (info != null && oldinfo.changeType == info.changeType && oldinfo.stateType == info.stateType && oldinfo.entityType == info.entityType)
|
||||
{
|
||||
//最后一个,合起来发送
|
||||
states.Add(info.state);
|
||||
info = null;
|
||||
}
|
||||
|
||||
switch (oldinfo.changeType)
|
||||
{
|
||||
case EntityChangeType.Insert:
|
||||
funcInsert();
|
||||
break;
|
||||
case EntityChangeType.Delete:
|
||||
funcDelete();
|
||||
break;
|
||||
}
|
||||
isLiveUpdate = true;
|
||||
}
|
||||
|
||||
if (isLiveUpdate || oldinfo.changeType == EntityChangeType.Update)
|
||||
{
|
||||
if (states.Any())
|
||||
{
|
||||
funcUpdate(isLiveUpdate);
|
||||
if (info?.changeType == EntityChangeType.Update)
|
||||
flagFuncUpdateLaststate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
states.Add(info.state);
|
||||
oldinfo = info;
|
||||
|
||||
if (flagFuncUpdateLaststate && oldinfo.changeType == EntityChangeType.Update) //马上与上个元素比较
|
||||
funcUpdate(isLiveUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
isFlushCommanding = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
FreeSql.DbContext/DbContext/FreeContext.cs
Normal file
13
FreeSql.DbContext/DbContext/FreeContext.cs
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public class FreeContext : DbContext
|
||||
{
|
||||
|
||||
public FreeContext(IFreeSql orm)
|
||||
{
|
||||
_ormScoped = DbContextScopedFreeSql.Create(orm, () => this, () => UnitOfWork);
|
||||
}
|
||||
}
|
||||
}
|
462
FreeSql.DbContext/DbSet/DbSet.cs
Normal file
462
FreeSql.DbContext/DbSet/DbSet.cs
Normal file
@ -0,0 +1,462 @@
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
using System.Data;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
|
||||
class DbContextDbSet<TEntity> : DbSet<TEntity> where TEntity : class
|
||||
{
|
||||
public DbContextDbSet(DbContext ctx)
|
||||
{
|
||||
_db = ctx;
|
||||
_uow = ctx.UnitOfWork;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDbSet : IDisposable
|
||||
{
|
||||
Type EntityType { get; }
|
||||
}
|
||||
public abstract partial class DbSet<TEntity> : IDbSet where TEntity : class
|
||||
{
|
||||
internal DbContext _db { get; set; }
|
||||
internal virtual IUnitOfWork _uow { get; set; }
|
||||
|
||||
protected virtual ISelect<TEntity> OrmSelect(object dywhere)
|
||||
{
|
||||
DbContextFlushCommand(); //查询前先提交,否则会出脏读
|
||||
var uowIsolationLevel = _uow?.IsolationLevel ?? IsolationLevel.Unspecified;
|
||||
var select = _db.OrmOriginal.Select<TEntity>().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction(uowIsolationLevel != IsolationLevel.Unspecified)).TrackToList(TrackToList).WhereDynamic(dywhere);
|
||||
(select as Select0Provider)._resolveHookTransaction = () => _uow?.GetOrBeginTransaction();
|
||||
if (_db.Options.EnableGlobalFilter == false) select.DisableGlobalFilter();
|
||||
return select;
|
||||
}
|
||||
|
||||
~DbSet() => this.Dispose();
|
||||
int _disposeCounter;
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Increment(ref _disposeCounter) != 1) return;
|
||||
try
|
||||
{
|
||||
this._dicUpdateTimes.Clear();
|
||||
this._states.Clear();
|
||||
this._statesEditing.Clear();
|
||||
this._dataEditing = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IInsert<TEntity> OrmInsert()
|
||||
{
|
||||
var insert = _db.OrmOriginal.Insert<TEntity>().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction());
|
||||
if (_db.Options.NoneParameter != null) insert.NoneParameter(_db.Options.NoneParameter.Value);
|
||||
return insert;
|
||||
}
|
||||
protected virtual IInsert<TEntity> OrmInsert(TEntity entity)
|
||||
{
|
||||
var insert = OrmInsert();
|
||||
if (entity != null)
|
||||
{
|
||||
(insert as InsertProvider<TEntity>)._source.Add(entity); //防止 Aop.AuditValue 触发两次
|
||||
if (_db.Options.AuditValue != null)
|
||||
_db.Options.AuditValue(new DbContextAuditValueEventArgs(Aop.AuditValueType.Insert, _table.Type, entity));
|
||||
}
|
||||
return insert;
|
||||
}
|
||||
protected virtual IInsert<TEntity> OrmInsert(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
var insert = OrmInsert();
|
||||
if (entitys != null)
|
||||
{
|
||||
(insert as InsertProvider<TEntity>)._source.AddRange(entitys.Where(a => a != null)); //防止 Aop.AuditValue 触发两次
|
||||
if (_db.Options.AuditValue != null)
|
||||
foreach (var item in entitys)
|
||||
_db.Options.AuditValue(new DbContextAuditValueEventArgs(Aop.AuditValueType.Insert, _table.Type, item));
|
||||
}
|
||||
return insert;
|
||||
}
|
||||
|
||||
protected virtual IUpdate<TEntity> OrmUpdate(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
var update = _db.OrmOriginal.Update<TEntity>().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction());
|
||||
if (_db.Options.NoneParameter != null) update.NoneParameter(_db.Options.NoneParameter.Value);
|
||||
if (_db.Options.EnableGlobalFilter == false) update.DisableGlobalFilter();
|
||||
if (entitys != null)
|
||||
{
|
||||
(update as UpdateProvider<TEntity>)._source.AddRange(entitys.Where(a => a != null)); //防止 Aop.AuditValue 触发两次
|
||||
if (_db.Options.AuditValue != null)
|
||||
foreach (var item in entitys)
|
||||
_db.Options.AuditValue(new DbContextAuditValueEventArgs(Aop.AuditValueType.Update, _table.Type, item));
|
||||
}
|
||||
return update;
|
||||
}
|
||||
protected virtual IDelete<TEntity> OrmDelete(object dywhere)
|
||||
{
|
||||
var delete = _db.OrmOriginal.Delete<TEntity>().AsType(_entityType).WithTransaction(_uow?.GetOrBeginTransaction()).WhereDynamic(dywhere);
|
||||
if (_db.Options.EnableGlobalFilter == false) delete.DisableGlobalFilter();
|
||||
return delete;
|
||||
}
|
||||
protected virtual IDelete<object> OrmDeleteAsType(Type entityType)
|
||||
{
|
||||
var delete = _db.OrmOriginal.Delete<object>().AsType(entityType).WithTransaction(_uow?.GetOrBeginTransaction());
|
||||
if (_db.Options.EnableGlobalFilter == false) delete.DisableGlobalFilter();
|
||||
return delete;
|
||||
}
|
||||
|
||||
internal void EnqueueToDbContext(DbContext.EntityChangeType changeType, EntityState state) =>
|
||||
_db.EnqueuePreCommand(changeType, this, typeof(EntityState), _entityType, state);
|
||||
|
||||
internal void IncrAffrows(int affrows) =>
|
||||
_db._affrows += affrows;
|
||||
|
||||
internal void TrackToList(object list)
|
||||
{
|
||||
if (list == null) return;
|
||||
var ls = list as IEnumerable<TEntity>;
|
||||
if (ls == null)
|
||||
{
|
||||
var ie = list as IEnumerable;
|
||||
if (ie == null) return;
|
||||
foreach (var item in ie)
|
||||
{
|
||||
if (item == null) return;
|
||||
var itemType = item.GetType();
|
||||
if (itemType == typeof(object)) return;
|
||||
if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType;
|
||||
if (_db.OrmOriginal.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return;
|
||||
if (itemType.GetConstructor(Type.EmptyTypes) == null) return;
|
||||
var dbset = _db.Set(itemType);
|
||||
dbset?.GetType().GetMethod("TrackToList", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(dbset, new object[] { list });
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_table?.Primarys.Any() != true) return;
|
||||
foreach (var item in ls)
|
||||
{
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, item, false);
|
||||
if (key == null) continue;
|
||||
_states.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) =>
|
||||
{
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, item, ov.Value);
|
||||
ov.Time = DateTime.Now;
|
||||
return ov;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public ISelect<TEntity> Select => this.OrmSelect(null);
|
||||
public ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => this.OrmSelect(null).Where(exp);
|
||||
public ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => this.OrmSelect(null).WhereIf(condition, exp);
|
||||
|
||||
protected ConcurrentDictionary<string, EntityState> _states = new ConcurrentDictionary<string, EntityState>();
|
||||
TableInfo _tablePriv;
|
||||
protected TableInfo _table => _tablePriv ?? (_tablePriv = _db.OrmOriginal.CodeFirst.GetTableByEntity(_entityType));
|
||||
ColumnInfo[] _tableIdentitysPriv, _tableReturnColumnsPriv;
|
||||
protected ColumnInfo[] _tableIdentitys => _tableIdentitysPriv ?? (_tableIdentitysPriv = _table.ColumnsByPosition.Where(a => a.Attribute.IsIdentity).ToArray());
|
||||
protected ColumnInfo[] _tableReturnColumns => _tableReturnColumnsPriv ?? (_tableReturnColumnsPriv = _table.ColumnsByPosition.Where(a => a.Attribute.IsIdentity || string.IsNullOrWhiteSpace(a.DbInsertValue) == false).ToArray());
|
||||
protected Type _entityType = typeof(TEntity);
|
||||
public Type EntityType => _entityType;
|
||||
|
||||
/// <summary>
|
||||
/// 动态Type,在使用 DbSet<object> 后使用本方法,指定实体类型
|
||||
/// </summary>
|
||||
/// <param name="entityType"></param>
|
||||
/// <returns></returns>
|
||||
public DbSet<TEntity> AsType(Type entityType)
|
||||
{
|
||||
if (entityType == typeof(object)) throw new Exception(CoreStrings.TypeAsType_NotSupport_Object("DbSet"));
|
||||
if (entityType == _entityType) return this;
|
||||
var newtb = _db.OrmOriginal.CodeFirst.GetTableByEntity(entityType);
|
||||
_tablePriv = newtb ?? throw new Exception(CoreStrings.Type_AsType_Parameter_Error("DbSet"));
|
||||
_tableIdentitysPriv = null;
|
||||
_tableReturnColumnsPriv = null;
|
||||
_entityType = entityType;
|
||||
return this;
|
||||
}
|
||||
|
||||
internal Dictionary<Type, DbSet<object>> _dicDbSetObjects = new Dictionary<Type, DbSet<object>>();
|
||||
DbSet<object> GetDbSetObject(Type et)
|
||||
{
|
||||
if (_dicDbSetObjects.TryGetValue(et, out var tryds)) return tryds;
|
||||
_dicDbSetObjects.Add(et, tryds = _db.Set<object>().AsType(et));
|
||||
if (_db.InternalDicSet.TryGetValue(et, out var tryds2))
|
||||
{
|
||||
var copyTo = typeof(DbSet<>).MakeGenericType(et).GetMethod("StatesCopyToDbSetObject", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(DbSet<object>) }, null);
|
||||
copyTo?.Invoke(tryds2, new object[] { tryds });
|
||||
}
|
||||
return tryds;
|
||||
}
|
||||
void StatesCopyToDbSetObject(DbSet<object> ds)
|
||||
{
|
||||
ds.AttachRange(_states.Values.OrderBy(a => a.Time).Select(a => a.Value).ToArray());
|
||||
}
|
||||
void StatesRemoveByObjects(IEnumerable<object> data)
|
||||
{
|
||||
if (data == null) return;
|
||||
foreach (var item in data)
|
||||
{
|
||||
var stateKey = _db.OrmOriginal.GetEntityKeyString(_entityType, item, false);
|
||||
_states.TryRemove(stateKey, out var trystate);
|
||||
}
|
||||
}
|
||||
|
||||
public 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; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 附加实体,可用于不查询就更新或删除
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void Attach(TEntity data) => AttachRange(new[] { data });
|
||||
public void AttachRange(IEnumerable<TEntity> data)
|
||||
{
|
||||
if (data == null || data.Any() == false) return;
|
||||
if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAttach_EntityHasNo_PrimaryKey(_db.OrmOriginal.GetEntityString(_entityType, data.First())));
|
||||
foreach (var item in data)
|
||||
{
|
||||
FreeSql.Internal.CommonProvider.UpdateProvider<TEntity>.AuditDataValue(this, item, _db.OrmOriginal, _table, null); //与 CanUpdate 同步
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, item, false);
|
||||
if (string.IsNullOrEmpty(key)) throw new Exception(DbContextStrings.CannotAttach_PrimaryKey_NotSet(_db.OrmOriginal.GetEntityString(_entityType, item)));
|
||||
|
||||
_states.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) =>
|
||||
{
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, item, ov.Value);
|
||||
ov.Time = DateTime.Now;
|
||||
return ov;
|
||||
});
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 附加实体,并且只附加主键值,可用于不更新属性值为null或默认值的字段
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public DbSet<TEntity> AttachOnlyPrimary(TEntity data)
|
||||
{
|
||||
if (data == null) return this;
|
||||
var pkitem = (TEntity)_entityType.CreateInstanceGetDefaultValue();
|
||||
foreach (var pk in _db.OrmOriginal.CodeFirst.GetTableByEntity(_entityType).Primarys)
|
||||
{
|
||||
var colVal = _db.OrmOriginal.GetEntityValueWithPropertyName(_entityType, data, pk.CsName);
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(_entityType, pkitem, pk.CsName, colVal);
|
||||
}
|
||||
this.Attach(pkitem);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 比较实体,计算出值发生变化的属性,以及属性变化的前后值
|
||||
/// </summary>
|
||||
/// <param name="newdata">最新的实体对象,它将与附加实体的状态对比</param>
|
||||
/// <returns>key: 属性名, value: [旧值, 新值]</returns>
|
||||
public Dictionary<string, object[]> CompareState(TEntity newdata)
|
||||
{
|
||||
if (newdata == null) return null;
|
||||
if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.Incomparable_EntityHasNo_PrimaryKey(_db.OrmOriginal.GetEntityString(_entityType, newdata)));
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, newdata, false);
|
||||
if (string.IsNullOrEmpty(key)) throw new Exception(DbContextStrings.Incomparable_PrimaryKey_NotSet(_db.OrmOriginal.GetEntityString(_entityType, newdata)));
|
||||
if (_states.TryGetValue(key, out var oldState) == false || oldState == null)
|
||||
return _table.ColumnsByCs.ToDictionary(a => a.Key, a => new object[]
|
||||
{
|
||||
_db.OrmOriginal.GetEntityValueWithPropertyName(_entityType, newdata, a.Key),
|
||||
null
|
||||
});
|
||||
|
||||
return _db.OrmOriginal.CompareEntityValueReturnColumns(_entityType, oldState.Value, newdata, false).ToDictionary(a => a, a => new object[]
|
||||
{
|
||||
_db.OrmOriginal.GetEntityValueWithPropertyName(_entityType, newdata, a),
|
||||
_db.OrmOriginal.GetEntityValueWithPropertyName(_entityType, oldState.Value, a)
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空状态数据
|
||||
/// </summary>
|
||||
public void FlushState()
|
||||
{
|
||||
_states.Clear();
|
||||
}
|
||||
|
||||
#region Utils
|
||||
EntityState CreateEntityState(TEntity data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, data, false);
|
||||
var state = new EntityState((TEntity)_entityType.CreateInstanceGetDefaultValue(), key);
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, data, state.Value);
|
||||
return state;
|
||||
}
|
||||
bool? ExistsInStates(TEntity data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, data, false);
|
||||
if (string.IsNullOrEmpty(key)) return null;
|
||||
return _states.ContainsKey(key);
|
||||
}
|
||||
|
||||
bool CanAdd(IEnumerable<TEntity> data, bool isThrow)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
if (isThrow) throw new ArgumentNullException(nameof(data));
|
||||
return false;
|
||||
}
|
||||
if (data.Any() == false) return false;
|
||||
foreach (var s in data) if (CanAdd(s, isThrow) == false) return false;
|
||||
return true;
|
||||
}
|
||||
bool CanAdd(TEntity data, bool isThrow)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
if (isThrow) throw new ArgumentNullException(nameof(data));
|
||||
return false;
|
||||
}
|
||||
if (_table.Primarys.Any() == false)
|
||||
{
|
||||
if (isThrow) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
return false;
|
||||
}
|
||||
FreeSql.Internal.CommonProvider.InsertProvider<TEntity>.AuditDataValue(this, data, _db.OrmOriginal, _table, null);
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, data, true);
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
switch (_db.OrmOriginal.Ado.DataType)
|
||||
{
|
||||
case DataType.SqlServer:
|
||||
case DataType.OdbcSqlServer:
|
||||
case DataType.CustomSqlServer:
|
||||
case DataType.PostgreSQL:
|
||||
case DataType.OdbcPostgreSQL:
|
||||
case DataType.CustomPostgreSQL:
|
||||
case DataType.KingbaseES:
|
||||
case DataType.OdbcKingbaseES:
|
||||
case DataType.ShenTong:
|
||||
case DataType.ClickHouse:
|
||||
return true;
|
||||
default:
|
||||
if (_tableIdentitys.Length == 1 && _table.Primarys.Length == 1)
|
||||
return true;
|
||||
if (isThrow) throw new Exception(DbContextStrings.CannotAdd_PrimaryKey_NotSet(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//不可添加,已存在于状态管理
|
||||
//if (_states.ContainsKey(key))
|
||||
//{
|
||||
// if (isThrow) throw new Exception(DbContextStrings.CannotAdd_AlreadyExistsInStateManagement(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
// return false;
|
||||
//}
|
||||
if (_db.OrmOriginal.Ado.DataType == DataType.ClickHouse) return true;
|
||||
var idval = _db.OrmOriginal.GetEntityIdentityValueWithPrimary(_entityType, data);
|
||||
if (idval > 0)
|
||||
{
|
||||
if (isThrow) throw new Exception(DbContextStrings.CannotAdd_SelfIncreasingHasValue(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CanUpdate(IEnumerable<TEntity> data, bool isThrow)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
if (isThrow) throw new ArgumentNullException(nameof(data));
|
||||
return false;
|
||||
}
|
||||
if (data.Any() == false) return false;
|
||||
foreach (var s in data) if (CanUpdate(s, isThrow) == false) return false;
|
||||
return true;
|
||||
}
|
||||
bool CanUpdate(TEntity data, bool isThrow)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
if (isThrow) throw new ArgumentNullException(nameof(data));
|
||||
return false;
|
||||
}
|
||||
if (_table.Primarys.Any() == false)
|
||||
{
|
||||
if (isThrow) throw new Exception(DbContextStrings.CannotUpdate_EntityHasNo_PrimaryKey(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
return false;
|
||||
}
|
||||
FreeSql.Internal.CommonProvider.UpdateProvider<TEntity>.AuditDataValue(this, data, _db.OrmOriginal, _table, null);
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, data, false);
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
if (isThrow) throw new Exception(DbContextStrings.CannotUpdate_PrimaryKey_NotSet(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
return false;
|
||||
}
|
||||
if (_states.TryGetValue(key, out var tryval) == false)
|
||||
{
|
||||
if (isThrow) throw new Exception(DbContextStrings.CannotUpdate_DataShouldQueryOrAttach(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CanRemove(IEnumerable<TEntity> data, bool isThrow)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
if (isThrow) throw new ArgumentNullException(nameof(data));
|
||||
return false;
|
||||
}
|
||||
if (data.Any() == false) return false;
|
||||
foreach (var s in data) if (CanRemove(s, isThrow) == false) return false;
|
||||
return true;
|
||||
}
|
||||
bool CanRemove(TEntity data, bool isThrow)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
if (isThrow) throw new ArgumentNullException(nameof(data));
|
||||
return false;
|
||||
}
|
||||
if (_table.Primarys.Any() == false)
|
||||
{
|
||||
if (isThrow) throw new Exception(DbContextStrings.CannotDelete_EntityHasNo_PrimaryKey(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
return false;
|
||||
}
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, data, false);
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
if (isThrow) throw new Exception(DbContextStrings.CannotDelete_PrimaryKey_NotSet(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
return false;
|
||||
}
|
||||
//if (_states.TryGetValue(key, out var tryval) == false) {
|
||||
// if (isThrow) throw new Exception($"不可删除,数据未被跟踪,应该先查询:{_fsql.GetEntityString(_entityType, data)}");
|
||||
// return false;
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
530
FreeSql.DbContext/DbSet/DbSetAsync.cs
Normal file
530
FreeSql.DbContext/DbSet/DbSetAsync.cs
Normal file
@ -0,0 +1,530 @@
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
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.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#if net40
|
||||
#else
|
||||
namespace FreeSql
|
||||
{
|
||||
partial class DbSet<TEntity>
|
||||
{
|
||||
Task DbContextFlushCommandAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_dicUpdateTimes.Clear();
|
||||
return _db.FlushCommandAsync(cancellationToken);
|
||||
}
|
||||
|
||||
async Task<int> DbContextBatchAddAsync(EntityState[] adds, CancellationToken cancellationToken)
|
||||
{
|
||||
if (adds.Any() == false) return 0;
|
||||
var affrows = await this.OrmInsert(adds.Select(a => a.Value)).ExecuteAffrowsAsync(cancellationToken);
|
||||
_db._entityChangeReport.AddRange(adds.Select(a => new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = a.Value, Type = DbContext.EntityChangeType.Insert }));
|
||||
return affrows;
|
||||
}
|
||||
|
||||
#region Add
|
||||
async Task AddPrivAsync(TEntity data, bool isCheck, CancellationToken cancellationToken)
|
||||
{
|
||||
if (isCheck && CanAdd(data, true) == false) return;
|
||||
if (_tableReturnColumns.Length > 0)
|
||||
{
|
||||
//有自增,马上执行
|
||||
switch (_db.OrmOriginal.Ado.DataType)
|
||||
{
|
||||
case DataType.SqlServer:
|
||||
case DataType.OdbcSqlServer:
|
||||
case DataType.CustomSqlServer:
|
||||
case DataType.PostgreSQL:
|
||||
case DataType.OdbcPostgreSQL:
|
||||
case DataType.CustomPostgreSQL:
|
||||
case DataType.KingbaseES:
|
||||
case DataType.OdbcKingbaseES:
|
||||
case DataType.ShenTong:
|
||||
case DataType.Firebird: //firebird 只支持单条插入 returning
|
||||
if (_tableIdentitys.Length == 1 && _tableReturnColumns.Length == 1)
|
||||
{
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
var idtval = await this.OrmInsert(data).ExecuteIdentityAsync(cancellationToken);
|
||||
IncrAffrows(1);
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(_entityType, data, _tableIdentitys[0].CsName, idtval);
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = data, Type = DbContext.EntityChangeType.Insert });
|
||||
Attach(data);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
await AddOrUpdateNavigateAsync(data, true, null, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
var newval = (await this.OrmInsert(data).ExecuteInsertedAsync(cancellationToken)).First();
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = newval, Type = DbContext.EntityChangeType.Insert });
|
||||
IncrAffrows(1);
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, newval, data);
|
||||
Attach(newval);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
await AddOrUpdateNavigateAsync(data, true, null, cancellationToken);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
if (_tableIdentitys.Length == 1)
|
||||
{
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
var idtval = await this.OrmInsert(data).ExecuteIdentityAsync(cancellationToken);
|
||||
IncrAffrows(1);
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(_entityType, data, _tableIdentitys[0].CsName, idtval);
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = data, Type = DbContext.EntityChangeType.Insert });
|
||||
Attach(data);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
await AddOrUpdateNavigateAsync(data, true, null, cancellationToken);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Insert, CreateEntityState(data));
|
||||
Attach(data);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
await AddOrUpdateNavigateAsync(data, true, null, cancellationToken);
|
||||
}
|
||||
public Task AddAsync(TEntity data, CancellationToken cancellationToken = default) => AddPrivAsync(data, true, cancellationToken);
|
||||
async public Task AddRangeAsync(IEnumerable<TEntity> data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (data is List<TEntity> == false) data = data?.ToList();
|
||||
if (CanAdd(data, true) == false) return;
|
||||
if (data.ElementAtOrDefault(1) == default(TEntity))
|
||||
{
|
||||
await AddAsync(data.First(), cancellationToken);
|
||||
return;
|
||||
}
|
||||
if (_tableReturnColumns.Length > 0)
|
||||
{
|
||||
//有自增,马上执行
|
||||
switch (_db.OrmOriginal.Ado.DataType)
|
||||
{
|
||||
case DataType.SqlServer:
|
||||
case DataType.OdbcSqlServer:
|
||||
case DataType.CustomSqlServer:
|
||||
case DataType.PostgreSQL:
|
||||
case DataType.OdbcPostgreSQL:
|
||||
case DataType.CustomPostgreSQL:
|
||||
case DataType.KingbaseES:
|
||||
case DataType.OdbcKingbaseES:
|
||||
case DataType.ShenTong:
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
var rets = await this.OrmInsert(data).ExecuteInsertedAsync(cancellationToken);
|
||||
if (rets.Count != data.Count()) throw new Exception(DbContextStrings.SpecialError_BatchAdditionFailed(_db.OrmOriginal.Ado.DataType));
|
||||
_db._entityChangeReport.AddRange(rets.Select(a => new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = a, Type = DbContext.EntityChangeType.Insert }));
|
||||
var idx = 0;
|
||||
foreach (var s in data)
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, rets[idx++], s);
|
||||
IncrAffrows(rets.Count);
|
||||
AttachRange(rets);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
foreach (var item in data)
|
||||
await AddOrUpdateNavigateAsync(item, true, null, cancellationToken);
|
||||
return;
|
||||
default:
|
||||
if (_tableIdentitys.Length == 1)
|
||||
{
|
||||
foreach (var s in data)
|
||||
await AddPrivAsync(s, false, cancellationToken);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
//进入队列,等待 SaveChanges 时执行
|
||||
foreach (var item in data)
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Insert, CreateEntityState(item));
|
||||
AttachRange(data);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
foreach (var item in data)
|
||||
await AddOrUpdateNavigateAsync(item, true, null, cancellationToken);
|
||||
}
|
||||
|
||||
async public Task SaveManyAsync(TEntity item, string propertyName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (item == null) return;
|
||||
if (string.IsNullOrEmpty(propertyName)) return;
|
||||
if (_table.Properties.TryGetValue(propertyName, out var prop) == false) throw new KeyNotFoundException(DbContextStrings.NotFound_Property(_table.Type.FullName, propertyName));
|
||||
if (_table.ColumnsByCsIgnore.ContainsKey(propertyName)) throw new ArgumentException(DbContextStrings.TypeHasSetProperty_IgnoreAttribute(_table.Type.FullName, propertyName));
|
||||
|
||||
var tref = _table.GetTableRef(propertyName, true, false);
|
||||
if (tref == null) return;
|
||||
switch (tref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
case TableRefType.ManyToOne:
|
||||
case TableRefType.PgArrayToMany:
|
||||
throw new ArgumentException(DbContextStrings.PropertyOfType_IsNot_OneToManyOrManyToMany(_table.Type.FullName, propertyName));
|
||||
}
|
||||
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
var oldEnable = _db.Options.EnableCascadeSave;
|
||||
_db.Options.EnableCascadeSave = false;
|
||||
try
|
||||
{
|
||||
await AddOrUpdateNavigateAsync(item, false, propertyName, cancellationToken);
|
||||
if (tref.RefType == TableRefType.OneToMany)
|
||||
{
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
//删除没有保存的数据,求出主体的条件
|
||||
var deleteWhereParentParam = Expression.Parameter(typeof(object), "a");
|
||||
Expression whereParentExp = null;
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
{
|
||||
var whereExp = Expression.Equal(
|
||||
Expression.MakeMemberAccess(Expression.Convert(deleteWhereParentParam, tref.RefEntityType), tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName]),
|
||||
Expression.Constant(
|
||||
FreeSql.Internal.Utils.GetDataReaderValue(
|
||||
tref.Columns[colidx].CsType,
|
||||
_db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName)), tref.RefColumns[colidx].CsType)
|
||||
);
|
||||
if (whereParentExp == null) whereParentExp = whereExp;
|
||||
else whereParentExp = Expression.AndAlso(whereParentExp, whereExp);
|
||||
}
|
||||
var propValEach = GetItemValue(item, prop) as IEnumerable;
|
||||
var subDelete = _db.OrmOriginal.Delete<object>().AsType(tref.RefEntityType)
|
||||
.WithTransaction(_uow?.GetOrBeginTransaction())
|
||||
.Where(Expression.Lambda<Func<object, bool>>(whereParentExp, deleteWhereParentParam));
|
||||
foreach (var propValItem in propValEach)
|
||||
{
|
||||
subDelete.WhereDynamic(propValEach, true);
|
||||
break;
|
||||
}
|
||||
await subDelete.ExecuteAffrowsAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_db.Options.EnableCascadeSave = oldEnable;
|
||||
}
|
||||
}
|
||||
async Task AddOrUpdateNavigateAsync(TEntity item, bool isAdd, string propertyName, CancellationToken cancellationToken)
|
||||
{
|
||||
Func<PropertyInfo, Task> action = async prop =>
|
||||
{
|
||||
if (_table.ColumnsByCsIgnore.ContainsKey(prop.Name)) return;
|
||||
if (_table.ColumnsByCs.ContainsKey(prop.Name)) return;
|
||||
|
||||
var tref = _table.GetTableRef(prop.Name, false, false); //防止非正常的导航属性报错
|
||||
if (tref == null) return;
|
||||
DbSet<object> refSet = null;
|
||||
switch (tref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
refSet = GetDbSetObject(tref.RefEntityType);
|
||||
var propValItem = GetItemValue(item, prop);
|
||||
if (propValItem == null) return;
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
{
|
||||
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
|
||||
}
|
||||
if (isAdd) await refSet.AddAsync(propValItem);
|
||||
else await refSet.AddOrUpdateAsync(propValItem);
|
||||
return;
|
||||
case TableRefType.ManyToOne:
|
||||
case TableRefType.PgArrayToMany:
|
||||
return;
|
||||
}
|
||||
|
||||
var propValEach = GetItemValue(item, prop) as IEnumerable;
|
||||
if (propValEach == null) return;
|
||||
refSet = GetDbSetObject(tref.RefEntityType);
|
||||
switch (tref.RefType)
|
||||
{
|
||||
case TableRefType.ManyToMany:
|
||||
var curList = new List<object>();
|
||||
foreach (var propValItem in propValEach)
|
||||
{
|
||||
curList.Add(propValItem);
|
||||
var flagExists = refSet.ExistsInStates(propValItem);
|
||||
if (flagExists == false)
|
||||
flagExists = await refSet.Select.WhereDynamic(propValItem).AnyAsync(cancellationToken);
|
||||
if (refSet.CanAdd(propValItem, false) && flagExists != true)
|
||||
await refSet.AddAsync(propValItem, cancellationToken);
|
||||
}
|
||||
var midSelectParam = Expression.Parameter(typeof(object), "a");
|
||||
var midWheres = new List<Expression<Func<object, bool>>>();
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
midWheres.Add(Expression.Lambda<Func<object, bool>>(Expression.Equal(
|
||||
Expression.MakeMemberAccess(Expression.Convert(midSelectParam, tref.RefMiddleEntityType), tref.MiddleColumns[colidx].Table.Properties[tref.MiddleColumns[colidx].CsName]),
|
||||
Expression.Constant(
|
||||
FreeSql.Internal.Utils.GetDataReaderValue(
|
||||
tref.MiddleColumns[colidx].CsType,
|
||||
_db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName)), tref.MiddleColumns[colidx].CsType)
|
||||
), midSelectParam));
|
||||
|
||||
if (curList.Any() == false) //全部删除
|
||||
{
|
||||
var delall = OrmDeleteAsType(tref.RefMiddleEntityType);
|
||||
foreach (var midWhere in midWheres) delall.Where(midWhere);
|
||||
var sql = delall.ToSql();
|
||||
await delall.ExecuteAffrowsAsync(cancellationToken);
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = sql, Type = DbContext.EntityChangeType.SqlRaw });
|
||||
}
|
||||
else //保存
|
||||
{
|
||||
var midSet = GetDbSetObject(tref.RefMiddleEntityType);
|
||||
List<object> midList = null;
|
||||
if (isAdd == false)
|
||||
{
|
||||
var midSelect = midSet.Select;
|
||||
foreach (var midWhere in midWheres) midSelect.Where(midWhere);
|
||||
midList = await midSelect.ToListAsync(false, cancellationToken);
|
||||
}
|
||||
else
|
||||
midList = new List<object>();
|
||||
var midListDel = new List<object>();
|
||||
var midListAdd = new List<object>();
|
||||
|
||||
foreach (var midItem in midList)
|
||||
{
|
||||
var curContains = new List<int>();
|
||||
for (var curIdx = 0; curIdx < curList.Count; curIdx++)
|
||||
{
|
||||
var isEquals = true;
|
||||
for (var midcolidx = tref.Columns.Count; midcolidx < tref.MiddleColumns.Count; midcolidx++)
|
||||
{
|
||||
var refcol = tref.RefColumns[midcolidx - tref.Columns.Count];
|
||||
var midval = FreeSql.Internal.Utils.GetDataReaderValue(refcol.CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(tref.RefMiddleEntityType, midItem, tref.MiddleColumns[midcolidx].CsName));
|
||||
var refval = FreeSql.Internal.Utils.GetDataReaderValue(refcol.CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(tref.RefEntityType, curList[curIdx], refcol.CsName));
|
||||
if (object.Equals(midval, refval) == false)
|
||||
{
|
||||
isEquals = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isEquals)
|
||||
curContains.Add(curIdx);
|
||||
}
|
||||
if (curContains.Any())
|
||||
for (var delIdx = curContains.Count - 1; delIdx >= 0; delIdx--)
|
||||
curList.RemoveAt(curContains[delIdx]);
|
||||
else
|
||||
midListDel.Add(midItem);
|
||||
}
|
||||
midSet.RemoveRange(midListDel); //删除未保存的项
|
||||
foreach (var curItem in curList)
|
||||
{
|
||||
var newItem = tref.RefMiddleEntityType.CreateInstanceGetDefaultValue();
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
{
|
||||
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.MiddleColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefMiddleEntityType, newItem, tref.MiddleColumns[colidx].CsName, val);
|
||||
}
|
||||
for (var midcolidx = tref.Columns.Count; midcolidx < tref.MiddleColumns.Count; midcolidx++)
|
||||
{
|
||||
var refcol = tref.RefColumns[midcolidx - tref.Columns.Count];
|
||||
var refval = FreeSql.Internal.Utils.GetDataReaderValue(tref.MiddleColumns[midcolidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(tref.RefEntityType, curItem, refcol.CsName));
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefMiddleEntityType, newItem, tref.MiddleColumns[midcolidx].CsName, refval);
|
||||
}
|
||||
midListAdd.Add(newItem);
|
||||
}
|
||||
await midSet.AddRangeAsync(midListAdd, cancellationToken);
|
||||
}
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
var addList = new List<object>();
|
||||
var addOrUpdateList = new List<object>();
|
||||
foreach (var propValItem in propValEach)
|
||||
{
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
{
|
||||
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
|
||||
}
|
||||
if (isAdd) addList.Add(propValItem);
|
||||
else
|
||||
{
|
||||
var flagExists = refSet.ExistsInStates(propValItem);
|
||||
if (flagExists == null) addList.Add(propValItem); //自增/Guid
|
||||
else addOrUpdateList.Add(propValItem); //统一状态管理
|
||||
}
|
||||
}
|
||||
if (addList.Any()) await refSet.AddRangeAsync(addList, cancellationToken);
|
||||
if (addOrUpdateList.Any())
|
||||
{
|
||||
var existsList = await refSet.Select.WhereDynamic(addOrUpdateList).ToListAsync(false, cancellationToken);
|
||||
foreach (var aouItem in addOrUpdateList) await refSet.AddOrUpdateAsync(aouItem, cancellationToken);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
foreach (var prop in _table.Properties)
|
||||
await action(prop.Value);
|
||||
else if (_table.Properties.TryGetValue(propertyName, out var prop))
|
||||
await action(prop);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region UpdateAsync
|
||||
Task<int> DbContextBatchUpdateAsync(EntityState[] ups, CancellationToken cancellationToken) => DbContextBatchUpdatePrivAsync(ups, false, cancellationToken);
|
||||
Task<int> DbContextBatchUpdateNowAsync(EntityState[] ups, CancellationToken cancellationToken) => DbContextBatchUpdatePrivAsync(ups, true, cancellationToken);
|
||||
async Task<int> DbContextBatchUpdatePrivAsync(EntityState[] ups, bool isLiveUpdate, CancellationToken cancellationToken)
|
||||
{
|
||||
if (ups.Any() == false) return 0;
|
||||
var uplst1 = ups[ups.Length - 1];
|
||||
var uplst2 = ups.Length > 1 ? ups[ups.Length - 2] : null;
|
||||
|
||||
if (_states.TryGetValue(uplst1.Key, out var lstval1) == false) return -999;
|
||||
var lstval2 = default(EntityState);
|
||||
if (uplst2 != null && _states.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception(DbContextStrings.SpecialError_UpdateFailedDataNotTracked(_db.OrmOriginal.GetEntityString(_entityType, uplst2.Value)));
|
||||
|
||||
var cuig1 = _db.OrmOriginal.CompareEntityValueReturnColumns(_entityType, uplst1.Value, lstval1.Value, true);
|
||||
var cuig2 = uplst2 != null ? _db.OrmOriginal.CompareEntityValueReturnColumns(_entityType, uplst2.Value, lstval2.Value, true) : null;
|
||||
|
||||
List<EntityState> data = null;
|
||||
string[] cuig = null;
|
||||
if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0)
|
||||
{
|
||||
//最后一个不保存
|
||||
data = ups.ToList();
|
||||
data.RemoveAt(ups.Length - 1);
|
||||
cuig = cuig2;
|
||||
}
|
||||
else if (isLiveUpdate)
|
||||
{
|
||||
//立即保存
|
||||
data = ups.ToList();
|
||||
cuig = cuig1;
|
||||
}
|
||||
|
||||
if (data?.Count > 0)
|
||||
{
|
||||
if (cuig.Length == _table.Columns.Count)
|
||||
return ups.Length == data.Count ? -998 : -997;
|
||||
|
||||
var update = this.OrmUpdate(data.Select(a => a.Value)).IgnoreColumns(cuig);
|
||||
var affrows = await update.ExecuteAffrowsAsync(cancellationToken);
|
||||
_db._entityChangeReport.AddRange(data.Select(a => new DbContext.EntityChangeReport.ChangeInfo
|
||||
{
|
||||
EntityType = _entityType,
|
||||
Object = a.Value,
|
||||
BeforeObject = _states.TryGetValue(a.Key, out var beforeVal) ? CreateEntityState(beforeVal.Value).Value : null,
|
||||
Type = DbContext.EntityChangeType.Update
|
||||
}));
|
||||
|
||||
foreach (var newval in data)
|
||||
{
|
||||
if (_states.TryGetValue(newval.Key, out var tryold))
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, newval.Value, tryold.Value);
|
||||
if (newval.OldValue != null)
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, newval.Value, newval.OldValue);
|
||||
}
|
||||
return affrows;
|
||||
}
|
||||
|
||||
//等待下次对比再保存
|
||||
return 0;
|
||||
}
|
||||
async public Task UpdateAsync(TEntity data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var exists = ExistsInStates(data);
|
||||
if (exists == null) throw new Exception(DbContextStrings.CannotUpdate_PrimaryKey_NotSet(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
if (exists == false)
|
||||
{
|
||||
var olddata = await OrmSelect(data).FirstAsync(cancellationToken);
|
||||
if (olddata == null) throw new Exception(DbContextStrings.CannotUpdate_RecordDoesNotExist(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
}
|
||||
|
||||
await UpdateRangePrivAsync(new[] { data }, true, cancellationToken);
|
||||
}
|
||||
public Task UpdateRangeAsync(IEnumerable<TEntity> data, CancellationToken cancellationToken = default) => UpdateRangePrivAsync(data, true, cancellationToken);
|
||||
async Task UpdateRangePrivAsync(IEnumerable<TEntity> data, bool isCheck, CancellationToken cancellationToken)
|
||||
{
|
||||
if (data is List<TEntity> == false) data = data?.ToList();
|
||||
if (CanUpdate(data, true) == false) return;
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (_dicUpdateTimes.ContainsKey(item))
|
||||
{
|
||||
var itemCopy = CreateEntityState(item).Value;
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
if (_table.VersionColumn != null)
|
||||
{
|
||||
var itemVersion = _db.OrmOriginal.GetEntityValueWithPropertyName(_entityType, item, _table.VersionColumn.CsName);
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, itemCopy, item);
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(_entityType, item, _table.VersionColumn.CsName, itemVersion);
|
||||
}
|
||||
else
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, itemCopy, item);
|
||||
}
|
||||
_dicUpdateTimes.Add(item, 1);
|
||||
|
||||
var state = CreateEntityState(item);
|
||||
state.OldValue = item;
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Update, state);
|
||||
}
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
foreach (var item in data)
|
||||
await AddOrUpdateNavigateAsync(item, false, null, cancellationToken);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RemoveAsync
|
||||
async Task<int> DbContextBatchRemoveAsync(EntityState[] dels, CancellationToken cancellationToken)
|
||||
{
|
||||
if (dels.Any() == false) return 0;
|
||||
var affrows = await this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrowsAsync(cancellationToken);
|
||||
_db._entityChangeReport.AddRange(dels.Select(a => new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = a.Value, Type = DbContext.EntityChangeType.Delete }));
|
||||
return affrows;
|
||||
}
|
||||
async public Task<int> RemoveAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
return await this.OrmDelete(null).Where(predicate).ExecuteAffrowsAsync(cancellationToken);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AddOrUpdateAsync
|
||||
async public Task AddOrUpdateAsync(TEntity data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
|
||||
var flagExists = ExistsInStates(data);
|
||||
if (flagExists == false)
|
||||
{
|
||||
var olddata = await OrmSelect(data).FirstAsync(cancellationToken);
|
||||
flagExists = olddata != null;
|
||||
}
|
||||
|
||||
if (flagExists == true && CanUpdate(data, false))
|
||||
{
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
var affrows = _db._affrows;
|
||||
await UpdateRangePrivAsync(new[] { data }, false, cancellationToken);
|
||||
await DbContextFlushCommandAsync(cancellationToken);
|
||||
affrows = _db._affrows - affrows;
|
||||
return;
|
||||
}
|
||||
if (CanAdd(data, false))
|
||||
{
|
||||
_db.OrmOriginal.ClearEntityPrimaryValueWithIdentity(_entityType, data);
|
||||
await AddPrivAsync(data, false, cancellationToken);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RemoveCascadeAsync
|
||||
public Task<List<object>> RemoveCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) => RemoveRangeCascadeByMemoryOrDatabaseAsync(Select.Where(predicate).ToList(), false, cancellationToken);
|
||||
async internal protected Task<List<object>> RemoveRangeCascadeByMemoryOrDatabaseAsync(IEnumerable<TEntity> data, bool inMemory, CancellationToken cancellationToken = default)
|
||||
{
|
||||
//临时调用同步方法,后续会改
|
||||
return await Task.FromResult(RemoveRangeCascadeByMemoryOrDatabase(data, inMemory));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
910
FreeSql.DbContext/DbSet/DbSetSync.cs
Normal file
910
FreeSql.DbContext/DbSet/DbSetSync.cs
Normal file
@ -0,0 +1,910 @@
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
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.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
partial class DbSet<TEntity>
|
||||
{
|
||||
|
||||
void DbContextFlushCommand()
|
||||
{
|
||||
_dicUpdateTimes.Clear();
|
||||
_db.FlushCommand();
|
||||
}
|
||||
|
||||
int DbContextBatchAdd(EntityState[] adds)
|
||||
{
|
||||
if (adds.Any() == false) return 0;
|
||||
var affrows = this.OrmInsert(adds.Select(a => a.Value)).ExecuteAffrows();
|
||||
_db._entityChangeReport.AddRange(adds.Select(a => new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = a.Value, Type = DbContext.EntityChangeType.Insert }));
|
||||
return affrows;
|
||||
}
|
||||
|
||||
#region Add
|
||||
void AddPriv(TEntity data, bool isCheck)
|
||||
{
|
||||
if (isCheck && CanAdd(data, true) == false) return;
|
||||
if (_tableReturnColumns.Length > 0)
|
||||
{
|
||||
//有自增,马上执行
|
||||
switch (_db.OrmOriginal.Ado.DataType)
|
||||
{
|
||||
case DataType.SqlServer:
|
||||
case DataType.OdbcSqlServer:
|
||||
case DataType.CustomSqlServer:
|
||||
case DataType.PostgreSQL:
|
||||
case DataType.OdbcPostgreSQL:
|
||||
case DataType.CustomPostgreSQL:
|
||||
case DataType.KingbaseES:
|
||||
case DataType.OdbcKingbaseES:
|
||||
case DataType.ShenTong:
|
||||
case DataType.Firebird: //firebird 只支持单条插入 returning
|
||||
if (_tableIdentitys.Length == 1 && _tableReturnColumns.Length == 1)
|
||||
{
|
||||
DbContextFlushCommand();
|
||||
var idtval = this.OrmInsert(data).ExecuteIdentity();
|
||||
IncrAffrows(1);
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(_entityType, data, _tableIdentitys[0].CsName, idtval);
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = data, Type = DbContext.EntityChangeType.Insert });
|
||||
Attach(data);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
AddOrUpdateNavigate(data, true, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
DbContextFlushCommand();
|
||||
var newval = this.OrmInsert(data).ExecuteInserted().First();
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = newval, Type = DbContext.EntityChangeType.Insert });
|
||||
IncrAffrows(1);
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, newval, data);
|
||||
Attach(newval);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
AddOrUpdateNavigate(data, true, null);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
if (_tableIdentitys.Length == 1)
|
||||
{
|
||||
DbContextFlushCommand();
|
||||
var idtval = this.OrmInsert(data).ExecuteIdentity();
|
||||
IncrAffrows(1);
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(_entityType, data, _tableIdentitys[0].CsName, idtval);
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = data, Type = DbContext.EntityChangeType.Insert });
|
||||
Attach(data);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
AddOrUpdateNavigate(data, true, null);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Insert, CreateEntityState(data));
|
||||
Attach(data);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
AddOrUpdateNavigate(data, true, null);
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void Add(TEntity data) => AddPriv(data, true);
|
||||
public void AddRange(IEnumerable<TEntity> data)
|
||||
{
|
||||
if (data is List<TEntity> == false) data = data?.ToList();
|
||||
if (CanAdd(data, true) == false) return;
|
||||
if (data.ElementAtOrDefault(1) == default(TEntity))
|
||||
{
|
||||
Add(data.First());
|
||||
return;
|
||||
}
|
||||
if (_tableReturnColumns.Length > 0)
|
||||
{
|
||||
//有自增,马上执行
|
||||
switch (_db.OrmOriginal.Ado.DataType)
|
||||
{
|
||||
case DataType.SqlServer:
|
||||
case DataType.OdbcSqlServer:
|
||||
case DataType.CustomSqlServer:
|
||||
case DataType.PostgreSQL:
|
||||
case DataType.OdbcPostgreSQL:
|
||||
case DataType.CustomPostgreSQL:
|
||||
case DataType.KingbaseES:
|
||||
case DataType.OdbcKingbaseES:
|
||||
case DataType.ShenTong:
|
||||
DbContextFlushCommand();
|
||||
var rets = this.OrmInsert(data).ExecuteInserted();
|
||||
if (rets.Count != data.Count()) throw new Exception(DbContextStrings.SpecialError_BatchAdditionFailed(_db.OrmOriginal.Ado.DataType));
|
||||
_db._entityChangeReport.AddRange(rets.Select(a => new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = a, Type = DbContext.EntityChangeType.Insert }));
|
||||
var idx = 0;
|
||||
foreach (var s in data)
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, rets[idx++], s);
|
||||
IncrAffrows(rets.Count);
|
||||
AttachRange(rets);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
foreach (var item in data)
|
||||
AddOrUpdateNavigate(item, true, null);
|
||||
return;
|
||||
default:
|
||||
if (_tableIdentitys.Length == 1)
|
||||
{
|
||||
foreach (var s in data)
|
||||
AddPriv(s, false);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
//进入队列,等待 SaveChanges 时执行
|
||||
foreach (var item in data)
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Insert, CreateEntityState(item));
|
||||
AttachRange(data);
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
foreach (var item in data)
|
||||
AddOrUpdateNavigate(item, true, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
|
||||
/// 场景:在关闭级联保存功能之后,手工使用本方法<para></para>
|
||||
/// 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus")<para></para>
|
||||
/// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
|
||||
/// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
|
||||
/// </summary>
|
||||
/// <param name="item">实体对象</param>
|
||||
/// <param name="propertyName">属性名</param>
|
||||
public void SaveMany(TEntity item, string propertyName)
|
||||
{
|
||||
if (item == null) return;
|
||||
if (string.IsNullOrEmpty(propertyName)) return;
|
||||
if (_table.Properties.TryGetValue(propertyName, out var prop) == false) throw new KeyNotFoundException(DbContextStrings.NotFound_Property(_table.Type.FullName, propertyName));
|
||||
if (_table.ColumnsByCsIgnore.ContainsKey(propertyName)) throw new ArgumentException(DbContextStrings.TypeHasSetProperty_IgnoreAttribute(_table.Type.FullName, propertyName));
|
||||
|
||||
var tref = _table.GetTableRef(propertyName, true, false);
|
||||
if (tref == null) return;
|
||||
switch (tref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
case TableRefType.ManyToOne:
|
||||
case TableRefType.PgArrayToMany:
|
||||
throw new ArgumentException(DbContextStrings.PropertyOfType_IsNot_OneToManyOrManyToMany(_table.Type.FullName, propertyName));
|
||||
}
|
||||
|
||||
DbContextFlushCommand();
|
||||
var oldEnable = _db.Options.EnableCascadeSave;
|
||||
_db.Options.EnableCascadeSave = false;
|
||||
try
|
||||
{
|
||||
AddOrUpdateNavigate(item, false, propertyName);
|
||||
if (tref.RefType == TableRefType.OneToMany)
|
||||
{
|
||||
DbContextFlushCommand();
|
||||
//删除没有保存的数据,求出主体的条件
|
||||
var deleteWhereParentParam = Expression.Parameter(typeof(object), "a");
|
||||
Expression whereParentExp = null;
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
{
|
||||
var whereExp = Expression.Equal(
|
||||
Expression.MakeMemberAccess(Expression.Convert(deleteWhereParentParam, tref.RefEntityType), tref.RefColumns[colidx].Table.Properties[tref.RefColumns[colidx].CsName]),
|
||||
Expression.Constant(
|
||||
FreeSql.Internal.Utils.GetDataReaderValue(
|
||||
tref.Columns[colidx].CsType,
|
||||
_db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName)), tref.RefColumns[colidx].CsType)
|
||||
);
|
||||
if (whereParentExp == null) whereParentExp = whereExp;
|
||||
else whereParentExp = Expression.AndAlso(whereParentExp, whereExp);
|
||||
}
|
||||
var propValEach = GetItemValue(item, prop) as IEnumerable;
|
||||
var subDelete = _db.OrmOriginal.Delete<object>().AsType(tref.RefEntityType)
|
||||
.WithTransaction(_uow?.GetOrBeginTransaction())
|
||||
.Where(Expression.Lambda<Func<object, bool>>(whereParentExp, deleteWhereParentParam));
|
||||
foreach (var propValItem in propValEach)
|
||||
{
|
||||
subDelete.WhereDynamic(propValEach, true);
|
||||
break;
|
||||
}
|
||||
subDelete.ExecuteAffrows();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_db.Options.EnableCascadeSave = oldEnable;
|
||||
}
|
||||
}
|
||||
void AddOrUpdateNavigate(TEntity item, bool isAdd, string propertyName)
|
||||
{
|
||||
Action<PropertyInfo> action = prop =>
|
||||
{
|
||||
if (_table.ColumnsByCsIgnore.ContainsKey(prop.Name)) return;
|
||||
if (_table.ColumnsByCs.ContainsKey(prop.Name)) return;
|
||||
|
||||
var tref = _table.GetTableRef(prop.Name, false, false); //防止非正常的导航属性报错
|
||||
if (tref == null) return;
|
||||
DbSet<object> refSet = null;
|
||||
switch (tref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
refSet = GetDbSetObject(tref.RefEntityType);
|
||||
var propValItem = GetItemValue(item, prop);
|
||||
if (propValItem == null) return;
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
{
|
||||
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
|
||||
}
|
||||
if (isAdd) refSet.Add(propValItem);
|
||||
else refSet.AddOrUpdate(propValItem);
|
||||
return;
|
||||
case TableRefType.ManyToOne:
|
||||
case TableRefType.PgArrayToMany:
|
||||
return;
|
||||
}
|
||||
|
||||
var propValEach = GetItemValue(item, prop) as IEnumerable;
|
||||
if (propValEach == null) return;
|
||||
refSet = GetDbSetObject(tref.RefEntityType);
|
||||
switch (tref.RefType)
|
||||
{
|
||||
case Internal.Model.TableRefType.ManyToMany:
|
||||
var curList = new List<object>();
|
||||
foreach (var propValItem in propValEach)
|
||||
{
|
||||
curList.Add(propValItem);
|
||||
var flagExists = refSet.ExistsInStates(propValItem);
|
||||
if (flagExists == false)
|
||||
flagExists = refSet.Select.WhereDynamic(propValItem).Any();
|
||||
if (refSet.CanAdd(propValItem, false) && flagExists != true)
|
||||
refSet.Add(propValItem);
|
||||
}
|
||||
var midSelectParam = Expression.Parameter(typeof(object), "a");
|
||||
var midWheres = new List<Expression<Func<object, bool>>>();
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
midWheres.Add(Expression.Lambda<Func<object, bool>>(Expression.Equal(
|
||||
Expression.MakeMemberAccess(Expression.Convert(midSelectParam, tref.RefMiddleEntityType), tref.MiddleColumns[colidx].Table.Properties[tref.MiddleColumns[colidx].CsName]),
|
||||
Expression.Constant(
|
||||
FreeSql.Internal.Utils.GetDataReaderValue(
|
||||
tref.MiddleColumns[colidx].CsType,
|
||||
_db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName)), tref.MiddleColumns[colidx].CsType)
|
||||
), midSelectParam));
|
||||
|
||||
if (curList.Any() == false) //全部删除
|
||||
{
|
||||
var delall = OrmDeleteAsType(tref.RefMiddleEntityType);
|
||||
foreach (var midWhere in midWheres) delall.Where(midWhere);
|
||||
var sql = delall.ToSql();
|
||||
delall.ExecuteAffrows();
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = sql, Type = DbContext.EntityChangeType.SqlRaw });
|
||||
}
|
||||
else //保存
|
||||
{
|
||||
var midSet = GetDbSetObject(tref.RefMiddleEntityType);
|
||||
List<object> midList = null;
|
||||
if (isAdd == false)
|
||||
{
|
||||
var midSelect = midSet.Select;
|
||||
foreach (var midWhere in midWheres) midSelect.Where(midWhere);
|
||||
midList = midSelect.ToList();
|
||||
}
|
||||
else
|
||||
midList = new List<object>();
|
||||
var midListDel = new List<object>();
|
||||
var midListAdd = new List<object>();
|
||||
|
||||
foreach (var midItem in midList)
|
||||
{
|
||||
var curContains = new List<int>();
|
||||
for (var curIdx = 0; curIdx < curList.Count; curIdx++)
|
||||
{
|
||||
var isEquals = true;
|
||||
for (var midcolidx = tref.Columns.Count; midcolidx < tref.MiddleColumns.Count; midcolidx++)
|
||||
{
|
||||
var refcol = tref.RefColumns[midcolidx - tref.Columns.Count];
|
||||
var midval = FreeSql.Internal.Utils.GetDataReaderValue(refcol.CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(tref.RefMiddleEntityType, midItem, tref.MiddleColumns[midcolidx].CsName));
|
||||
var refval = FreeSql.Internal.Utils.GetDataReaderValue(refcol.CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(tref.RefEntityType, curList[curIdx], refcol.CsName));
|
||||
if (object.Equals(midval, refval) == false)
|
||||
{
|
||||
isEquals = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isEquals)
|
||||
curContains.Add(curIdx);
|
||||
}
|
||||
if (curContains.Any())
|
||||
for (var delIdx = curContains.Count - 1; delIdx >= 0; delIdx--)
|
||||
curList.RemoveAt(curContains[delIdx]);
|
||||
else
|
||||
midListDel.Add(midItem);
|
||||
}
|
||||
midSet.RemoveRange(midListDel); //删除未保存的项
|
||||
foreach (var curItem in curList)
|
||||
{
|
||||
var newItem = tref.RefMiddleEntityType.CreateInstanceGetDefaultValue();
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
{
|
||||
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.MiddleColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefMiddleEntityType, newItem, tref.MiddleColumns[colidx].CsName, val);
|
||||
}
|
||||
for (var midcolidx = tref.Columns.Count; midcolidx < tref.MiddleColumns.Count; midcolidx++)
|
||||
{
|
||||
var refcol = tref.RefColumns[midcolidx - tref.Columns.Count];
|
||||
var refval = FreeSql.Internal.Utils.GetDataReaderValue(tref.MiddleColumns[midcolidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(tref.RefEntityType, curItem, refcol.CsName));
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefMiddleEntityType, newItem, tref.MiddleColumns[midcolidx].CsName, refval);
|
||||
}
|
||||
midListAdd.Add(newItem);
|
||||
}
|
||||
midSet.AddRange(midListAdd);
|
||||
}
|
||||
break;
|
||||
case Internal.Model.TableRefType.OneToMany:
|
||||
var addList = new List<object>();
|
||||
var addOrUpdateList = new List<object>();
|
||||
foreach (var propValItem in propValEach)
|
||||
{
|
||||
for (var colidx = 0; colidx < tref.Columns.Count; colidx++)
|
||||
{
|
||||
var val = FreeSql.Internal.Utils.GetDataReaderValue(tref.RefColumns[colidx].CsType, _db.OrmOriginal.GetEntityValueWithPropertyName(_table.Type, item, tref.Columns[colidx].CsName));
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tref.RefEntityType, propValItem, tref.RefColumns[colidx].CsName, val);
|
||||
}
|
||||
if (isAdd) addList.Add(propValItem);
|
||||
else
|
||||
{
|
||||
var flagExists = refSet.ExistsInStates(propValItem);
|
||||
if (flagExists == null) addList.Add(propValItem); //自增/Guid
|
||||
else addOrUpdateList.Add(propValItem); //统一状态管理
|
||||
}
|
||||
}
|
||||
if (addList.Any()) refSet.AddRange(addList);
|
||||
if (addOrUpdateList.Any())
|
||||
{
|
||||
var existsList = refSet.Select.WhereDynamic(addOrUpdateList).ToList();
|
||||
foreach (var aouItem in addOrUpdateList) refSet.AddOrUpdate(aouItem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(propertyName))
|
||||
foreach (var prop in _table.Properties)
|
||||
action(prop.Value);
|
||||
else if (_table.Properties.TryGetValue(propertyName, out var prop))
|
||||
action(prop);
|
||||
}
|
||||
static ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>> _dicLazyIsSetField = new ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>>();
|
||||
object GetItemValue(TEntity item, PropertyInfo prop)
|
||||
{
|
||||
object propVal = null;
|
||||
var itemType = item.GetType();
|
||||
if (_table.TypeLazy != null && itemType == _table.TypeLazy)
|
||||
{
|
||||
var lazyField = _dicLazyIsSetField.GetOrAdd(_table.TypeLazy, tl => new ConcurrentDictionary<string, FieldInfo>()).GetOrAdd(prop.Name, propName =>
|
||||
_table.TypeLazy.GetField($"__lazy__{propName}", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
if (lazyField != null)
|
||||
{
|
||||
var lazyFieldValue = (bool)lazyField.GetValue(item);
|
||||
if (lazyFieldValue == false) return null;
|
||||
}
|
||||
propVal = prop.GetValue(item, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
propVal = prop.GetValue(item, null);
|
||||
if (propVal == null) return null;
|
||||
}
|
||||
return propVal;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Update
|
||||
int DbContextBatchUpdate(EntityState[] ups) => DbContextBatchUpdatePriv(ups, false);
|
||||
int DbContextBatchUpdateNow(EntityState[] ups) => DbContextBatchUpdatePriv(ups, true);
|
||||
int DbContextBatchUpdatePriv(EntityState[] ups, bool isLiveUpdate)
|
||||
{
|
||||
if (ups.Any() == false) return 0;
|
||||
var uplst1 = ups[ups.Length - 1];
|
||||
var uplst2 = ups.Length > 1 ? ups[ups.Length - 2] : null;
|
||||
|
||||
if (_states.TryGetValue(uplst1.Key, out var lstval1) == false) return -999;
|
||||
var lstval2 = default(EntityState);
|
||||
if (uplst2 != null && _states.TryGetValue(uplst2.Key, out lstval2) == false) throw new Exception(DbContextStrings.SpecialError_UpdateFailedDataNotTracked(_db.OrmOriginal.GetEntityString(_entityType, uplst2.Value)));
|
||||
|
||||
var cuig1 = _db.OrmOriginal.CompareEntityValueReturnColumns(_entityType, uplst1.Value, lstval1.Value, true);
|
||||
var cuig2 = uplst2 != null ? _db.OrmOriginal.CompareEntityValueReturnColumns(_entityType, uplst2.Value, lstval2.Value, true) : null;
|
||||
|
||||
List<EntityState> data = null;
|
||||
string[] cuig = null;
|
||||
if (uplst2 != null && string.Compare(string.Join(",", cuig1), string.Join(",", cuig2)) != 0)
|
||||
{
|
||||
//最后一个不保存
|
||||
data = ups.ToList();
|
||||
data.RemoveAt(ups.Length - 1);
|
||||
cuig = cuig2;
|
||||
}
|
||||
else if (isLiveUpdate)
|
||||
{
|
||||
//立即保存
|
||||
data = ups.ToList();
|
||||
cuig = cuig1;
|
||||
}
|
||||
|
||||
if (data?.Count > 0)
|
||||
{
|
||||
if (cuig.Length == _table.Columns.Count)
|
||||
return ups.Length == data.Count ? -998 : -997;
|
||||
|
||||
var update = this.OrmUpdate(data.Select(a => a.Value)).IgnoreColumns(cuig);
|
||||
var affrows = update.ExecuteAffrows();
|
||||
_db._entityChangeReport.AddRange(data.Select(a => new DbContext.EntityChangeReport.ChangeInfo
|
||||
{
|
||||
EntityType = _entityType,
|
||||
Object = a.Value,
|
||||
BeforeObject = _states.TryGetValue(a.Key, out var beforeVal) ? CreateEntityState(beforeVal.Value).Value : null,
|
||||
Type = DbContext.EntityChangeType.Update
|
||||
}));
|
||||
|
||||
foreach (var newval in data)
|
||||
{
|
||||
if (_states.TryGetValue(newval.Key, out var tryold))
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, newval.Value, tryold.Value);
|
||||
if (newval.OldValue != null)
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, newval.Value, newval.OldValue);
|
||||
}
|
||||
return affrows;
|
||||
}
|
||||
|
||||
//等待下次对比再保存
|
||||
return 0;
|
||||
}
|
||||
|
||||
Dictionary<TEntity, byte> _dicUpdateTimes = new Dictionary<TEntity, byte>();
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void Update(TEntity data)
|
||||
{
|
||||
var exists = ExistsInStates(data);
|
||||
if (exists == null) throw new Exception(DbContextStrings.CannotUpdate_PrimaryKey_NotSet(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
if (exists == false)
|
||||
{
|
||||
var olddata = OrmSelect(data).First();
|
||||
if (olddata == null) throw new Exception(DbContextStrings.CannotUpdate_RecordDoesNotExist(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
}
|
||||
|
||||
UpdateRangePriv(new[] { data }, true);
|
||||
}
|
||||
public void UpdateRange(IEnumerable<TEntity> data) => UpdateRangePriv(data, true);
|
||||
void UpdateRangePriv(IEnumerable<TEntity> data, bool isCheck)
|
||||
{
|
||||
if (data is List<TEntity> == false) data = data?.ToList();
|
||||
if (CanUpdate(data, true) == false) return;
|
||||
foreach (var item in data)
|
||||
{
|
||||
if (_dicUpdateTimes.ContainsKey(item))
|
||||
{
|
||||
var itemCopy = CreateEntityState(item).Value;
|
||||
DbContextFlushCommand();
|
||||
if (_table.VersionColumn != null)
|
||||
{
|
||||
var itemVersion = _db.OrmOriginal.GetEntityValueWithPropertyName(_entityType, item, _table.VersionColumn.CsName);
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, itemCopy, item);
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(_entityType, item, _table.VersionColumn.CsName, itemVersion);
|
||||
}
|
||||
else
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, itemCopy, item);
|
||||
}
|
||||
_dicUpdateTimes.Add(item, 1);
|
||||
|
||||
var state = CreateEntityState(item);
|
||||
state.OldValue = item;
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Update, state);
|
||||
}
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
foreach (var item in data)
|
||||
AddOrUpdateNavigate(item, false, null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Remove
|
||||
int DbContextBatchRemove(EntityState[] dels)
|
||||
{
|
||||
if (dels.Any() == false) return 0;
|
||||
var affrows = this.OrmDelete(dels.Select(a => a.Value)).ExecuteAffrows();
|
||||
_db._entityChangeReport.AddRange(dels.Select(a => new DbContext.EntityChangeReport.ChangeInfo { EntityType = _entityType, Object = a.Value, Type = DbContext.EntityChangeType.Delete }));
|
||||
return affrows; //https://github.com/dotnetcore/FreeSql/issues/373
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void Remove(TEntity data) => RemoveRange(new[] { data });
|
||||
public void RemoveRange(IEnumerable<TEntity> data)
|
||||
{
|
||||
if (_db.Options.EnableCascadeSave)
|
||||
{
|
||||
RemoveRangeCascadeByMemoryOrDatabase(data, true);
|
||||
return;
|
||||
}
|
||||
if (CanRemove(data, true) == false) return;
|
||||
foreach (var item in data)
|
||||
{
|
||||
var state = CreateEntityState(item);
|
||||
_states.TryRemove(state.Key, out var trystate);
|
||||
_db.OrmOriginal.ClearEntityPrimaryValueWithIdentityAndGuid(_entityType, item);
|
||||
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Delete, state);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据 lambda 条件删除数据
|
||||
/// </summary>
|
||||
/// <param name="predicate"></param>
|
||||
/// <returns></returns>
|
||||
public int Remove(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
DbContextFlushCommand();
|
||||
return this.OrmDelete(null).Where(predicate).ExecuteAffrows();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AddOrUpdate
|
||||
/// <summary>
|
||||
/// 添加或更新
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void AddOrUpdate(TEntity data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(_db.OrmOriginal.GetEntityString(_entityType, data)));
|
||||
|
||||
var flagExists = ExistsInStates(data);
|
||||
if (flagExists == false)
|
||||
{
|
||||
var olddata = OrmSelect(data).First();
|
||||
flagExists = olddata != null;
|
||||
}
|
||||
|
||||
if (flagExists == true && CanUpdate(data, false))
|
||||
{
|
||||
DbContextFlushCommand();
|
||||
var affrows = _db._affrows;
|
||||
UpdateRangePriv(new[] { data }, false);
|
||||
DbContextFlushCommand();
|
||||
affrows = _db._affrows - affrows;
|
||||
return;
|
||||
}
|
||||
if (CanAdd(data, false))
|
||||
{
|
||||
_db.OrmOriginal.ClearEntityPrimaryValueWithIdentity(_entityType, data);
|
||||
AddPriv(data, false);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BeginEdit
|
||||
protected List<TEntity> _dataEditing;
|
||||
protected ConcurrentDictionary<string, EntityState> _statesEditing = new ConcurrentDictionary<string, EntityState>();
|
||||
|
||||
/// <summary>
|
||||
/// 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行<para></para>
|
||||
/// 场景:winform 加载表数据后,一顿添加、修改、删除操作之后,最后才点击【保存】<para></para><para></para>
|
||||
/// 示例:https://github.com/dotnetcore/FreeSql/issues/397<para></para>
|
||||
/// 注意:* 本方法只支持单表操作,不支持导航属性级联保存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void BeginEdit(List<TEntity> data)
|
||||
{
|
||||
if (data == null) return;
|
||||
if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotEdit_EntityHasNo_PrimaryKey(_db.OrmOriginal.GetEntityString(_entityType, data.First())));
|
||||
_statesEditing.Clear();
|
||||
_dataEditing = data;
|
||||
foreach (var item in data)
|
||||
{
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, item, false);
|
||||
if (string.IsNullOrEmpty(key)) continue;
|
||||
|
||||
_statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) =>
|
||||
{
|
||||
_db.OrmOriginal.MapEntityValue(_entityType, item, ov.Value);
|
||||
ov.Time = DateTime.Now;
|
||||
return ov;
|
||||
});
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 完成编辑数据,进行保存动作<para></para>
|
||||
/// 该方法根据 BeginEdit 传入的数据状态分析出添加、修改、删除 SQL 语句<para></para>
|
||||
/// 注意:* 本方法只支持单表操作,不支持导航属性级联保存
|
||||
/// </summary>
|
||||
/// <param name="data">可选参数:手工传递最终的 data 值进行对比<para></para>默认:如果不传递,则使用 BeginEdit 传入的 data 引用进行对比</param>
|
||||
/// <returns></returns>
|
||||
public int EndEdit(List<TEntity> data = null)
|
||||
{
|
||||
if (data == null) data = _dataEditing;
|
||||
var beforeAffrows = 0;
|
||||
if (data == null) return 0;
|
||||
var oldEnable = _db.Options.EnableCascadeSave;
|
||||
_db.Options.EnableCascadeSave = false;
|
||||
try
|
||||
{
|
||||
DbContextFlushCommand();
|
||||
var addList = new List<TEntity>();
|
||||
var ediList = new List<NativeTuple<TEntity, string>>();
|
||||
foreach (var item in data)
|
||||
{
|
||||
var key = _db.OrmOriginal.GetEntityKeyString(_entityType, item, false);
|
||||
if (_statesEditing.TryRemove(key, out var state) == false)
|
||||
{
|
||||
addList.Add(item);
|
||||
continue;
|
||||
}
|
||||
_states.AddOrUpdate(key, k => state, (k, ov) =>
|
||||
{
|
||||
ov.Value = state.Value;
|
||||
ov.Time = DateTime.Now;
|
||||
return ov;
|
||||
});
|
||||
var edicmp = _db.OrmOriginal.CompareEntityValueReturnColumns(_entityType, item, state.Value, false);
|
||||
if (edicmp.Any())
|
||||
ediList.Add(NativeTuple.Create(item, string.Join(",", edicmp)));
|
||||
}
|
||||
beforeAffrows = _db._affrows;
|
||||
AddRange(addList);
|
||||
UpdateRange(ediList.OrderBy(a => a.Item2).Select(a => a.Item1).ToList());
|
||||
|
||||
DbContextFlushCommand();
|
||||
var delList = _statesEditing.Values.OrderBy(a => a.Time).ToArray();
|
||||
_db._affrows += DbContextBatchRemove(delList); //为了减的少不必要的开销,此处没有直接调用 RemoveRange
|
||||
foreach (var state in delList)
|
||||
{
|
||||
_db.OrmOriginal.ClearEntityPrimaryValueWithIdentityAndGuid(_entityType, state.Value);
|
||||
_states.TryRemove(state.Key, out var oldstate);
|
||||
}
|
||||
DbContextFlushCommand();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dataEditing = null;
|
||||
_statesEditing.Clear();
|
||||
_db.Options.EnableCascadeSave = oldEnable;
|
||||
}
|
||||
return _db._affrows - beforeAffrows;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RemoveCascade
|
||||
/// <summary>
|
||||
/// 根据设置的 OneToOne/OneToMany/ManyToMany 导航属性,级联查询所有的数据库记录,删除并返回它们
|
||||
/// </summary>
|
||||
/// <param name="predicate"></param>
|
||||
/// <returns></returns>
|
||||
public List<object> RemoveCascadeByDatabase(Expression<Func<TEntity, bool>> predicate) => RemoveRangeCascadeByMemoryOrDatabase(Select.Where(predicate).ToList(), false);
|
||||
internal protected List<object> RemoveRangeCascadeByMemoryOrDatabase(IEnumerable<TEntity> data, bool inMemory)
|
||||
{
|
||||
var returnDeleted = inMemory ? null : new List<object>();
|
||||
if (data?.Any() != true) return returnDeleted;
|
||||
if (LocalGetNavigates(_table).Any() == false)
|
||||
{
|
||||
if (CanRemove(data, true) == false) return returnDeleted;
|
||||
foreach (var item in data) //不直接调用 Remove,防止清除 Identity/Guid
|
||||
{
|
||||
var state = CreateEntityState(item);
|
||||
_states.TryRemove(state.Key, out var trystate);
|
||||
if (inMemory) _db.OrmOriginal.ClearEntityPrimaryValueWithIdentityAndGuid(_entityType, item);
|
||||
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Delete, state);
|
||||
}
|
||||
returnDeleted?.AddRange(data.Select(a => (object)a));
|
||||
return returnDeleted;
|
||||
}
|
||||
|
||||
var fsql = _db.Orm;
|
||||
var commonUtils = (fsql.Select<object>() as Internal.CommonProvider.Select0Provider)._commonUtils;
|
||||
var eachdic = new Dictionary<string, bool>();
|
||||
var rootItems = data.Select(a => (object)a).ToArray();
|
||||
var rootDbSet = _db.Set<object>();
|
||||
rootDbSet.AsType(_table.Type);
|
||||
rootDbSet.AttachRange(rootItems);
|
||||
LocalEach(rootDbSet, rootItems, true);
|
||||
rootDbSet.FlushState();
|
||||
return returnDeleted;
|
||||
|
||||
List<NativeTuple<TableRef, PropertyInfo>> LocalGetNavigates(TableInfo tb)
|
||||
{
|
||||
return tb.GetAllTableRef().Where(a => tb.ColumnsByCs.ContainsKey(a.Key) == false && a.Value.Exception == null)
|
||||
.Select(a => new NativeTuple<TableRef, PropertyInfo>(a.Value, tb.Properties[a.Key]))
|
||||
.Where(a => a.Item1 != null && new[] { TableRefType.OneToOne, TableRefType.OneToMany, TableRefType.ManyToMany }.Contains(a.Item1.RefType))
|
||||
.ToList();
|
||||
}
|
||||
void LocalEach(DbSet<object> dbset, IEnumerable<object> items, bool isOneToOne)
|
||||
{
|
||||
items = items?.Where(item =>
|
||||
{
|
||||
var itemkeyStr = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetEntityKeyString(fsql, dbset.EntityType, item, false);
|
||||
var eachdicKey = $"{dbset.EntityType.FullName},{itemkeyStr}";
|
||||
if (eachdic.ContainsKey(eachdicKey)) return false;
|
||||
eachdic.Add(eachdicKey, true);
|
||||
return true;
|
||||
}).ToList();
|
||||
if (items?.Any() != true) return;
|
||||
|
||||
var tb = fsql.CodeFirst.GetTableByEntity(dbset.EntityType);
|
||||
var navs = LocalGetNavigates(tb);
|
||||
|
||||
var otos = navs.Where(a => a.Item1.RefType == TableRefType.OneToOne).ToList();
|
||||
if (isOneToOne && otos.Any())
|
||||
{
|
||||
foreach (var oto in otos)
|
||||
{
|
||||
var refset = _db.Set<object>();
|
||||
refset.AsType(oto.Item1.RefEntityType);
|
||||
|
||||
if (inMemory)
|
||||
{
|
||||
var refitems = items.Select(item => FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, oto.Item2.Name)).Where(item => item != null).ToList();
|
||||
refset.AttachRange(refitems);
|
||||
LocalEach(refset, refitems, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var reftb = fsql.CodeFirst.GetTableByEntity(oto.Item1.RefEntityType);
|
||||
var refwhereItems = items.Select(item =>
|
||||
{
|
||||
var refitem = oto.Item1.RefEntityType.CreateInstanceGetDefaultValue();
|
||||
for (var a = 0; a < oto.Item1.Columns.Count; a++)
|
||||
{
|
||||
var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, oto.Item1.Columns[a].CsName);
|
||||
FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(reftb, refitem, oto.Item1.RefColumns[a].CsName, colval);
|
||||
}
|
||||
return refitem;
|
||||
}).ToList();
|
||||
var refitems = refset.Select.Where(commonUtils.WhereItems(oto.Item1.RefColumns.ToArray(), "a.", refwhereItems)).ToList();
|
||||
LocalEach(refset, refitems, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var otms = navs.Where(a => a.Item1.RefType == TableRefType.OneToMany).ToList();
|
||||
if (otms.Any())
|
||||
{
|
||||
foreach (var otm in otms)
|
||||
{
|
||||
var refset = _db.Set<object>();
|
||||
refset.AsType(otm.Item1.RefEntityType);
|
||||
|
||||
if (inMemory)
|
||||
{
|
||||
var refitems = items.Select(item =>
|
||||
{
|
||||
var reflist = new List<object>();
|
||||
var reflistie = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, otm.Item2.Name) as IEnumerable;
|
||||
if (reflistie == null) return null;
|
||||
foreach (var refitem in reflistie) reflist.Add(refitem);
|
||||
return reflist;
|
||||
}).Where(itemlst => itemlst != null).SelectMany(itemlst => itemlst).ToList();
|
||||
refset.AttachRange(refitems);
|
||||
LocalEach(refset, refitems, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var reftb = fsql.CodeFirst.GetTableByEntity(otm.Item1.RefEntityType);
|
||||
var refwhereItems = items.Select(item =>
|
||||
{
|
||||
var refitem = otm.Item1.RefEntityType.CreateInstanceGetDefaultValue();
|
||||
for (var a = 0; a < otm.Item1.Columns.Count; a++)
|
||||
{
|
||||
var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, otm.Item1.Columns[a].CsName);
|
||||
FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(reftb, refitem, otm.Item1.RefColumns[a].CsName, colval);
|
||||
}
|
||||
return refitem;
|
||||
}).ToList();
|
||||
var childs = refset.Select.Where(commonUtils.WhereItems(otm.Item1.RefColumns.ToArray(), "a.", refwhereItems)).ToList();
|
||||
LocalEach(refset, childs, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mtms = navs.Where(a => a.Item1.RefType == TableRefType.ManyToMany).ToList();
|
||||
if (mtms.Any())
|
||||
{
|
||||
foreach (var mtm in mtms)
|
||||
{
|
||||
var midset = _db.Set<object>();
|
||||
midset.AsType(mtm.Item1.RefMiddleEntityType);
|
||||
var childTable = fsql.CodeFirst.GetTableByEntity(mtm.Item1.RefMiddleEntityType);
|
||||
|
||||
if (inMemory)
|
||||
{
|
||||
var miditems = items.Select(item =>
|
||||
{
|
||||
var midlist = new List<object>();
|
||||
var refitems = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, mtm.Item2.Name) as IEnumerable;
|
||||
if (refitems == null) return null;
|
||||
var reftb = fsql.CodeFirst.GetTableByEntity(mtm.Item1.RefEntityType);
|
||||
foreach (var refitem in refitems)
|
||||
{
|
||||
var miditem = mtm.Item1.RefMiddleEntityType.CreateInstanceGetDefaultValue();
|
||||
for (var a = 0; a < mtm.Item1.Columns.Count; a++)
|
||||
{
|
||||
var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, mtm.Item1.Columns[a].CsName);
|
||||
FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, miditem, mtm.Item1.MiddleColumns[a].CsName, colval);
|
||||
}
|
||||
for (var a = 0; a < mtm.Item1.RefColumns.Count; a++)
|
||||
{
|
||||
var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(reftb, refitem, mtm.Item1.RefColumns[a].CsName);
|
||||
FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, miditem, mtm.Item1.MiddleColumns[a + mtm.Item1.Columns.Count].CsName, colval);
|
||||
}
|
||||
midlist.Add(miditem);
|
||||
}
|
||||
return midlist;
|
||||
}).Where(midlist => midlist != null).SelectMany(midlist => midlist).ToList();
|
||||
midset.AttachRange(miditems);
|
||||
LocalEach(midset, miditems, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var miditems = items.Select(item =>
|
||||
{
|
||||
var refitem = mtm.Item1.RefMiddleEntityType.CreateInstanceGetDefaultValue();
|
||||
for (var a = 0; a < mtm.Item1.Columns.Count; a++)
|
||||
{
|
||||
var colval = FreeSql.Extensions.EntityUtil.EntityUtilExtensions.GetPropertyValue(tb, item, mtm.Item1.Columns[a].CsName);
|
||||
FreeSql.Extensions.EntityUtil.EntityUtilExtensions.SetPropertyValue(childTable, refitem, mtm.Item1.MiddleColumns[a].CsName, colval);
|
||||
}
|
||||
return refitem;
|
||||
}).ToList();
|
||||
var childs = midset.Select.Where(commonUtils.WhereItems(mtm.Item1.MiddleColumns.Take(mtm.Item1.Columns.Count).ToArray(), "a.", miditems)).ToList();
|
||||
LocalEach(midset, childs, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var atms = navs.Where(a => a.Item1.RefType == TableRefType.PgArrayToMany).ToList();
|
||||
if (atms.Any())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
if (dbset == rootDbSet)
|
||||
{
|
||||
if (CanRemove(data, true) == false) return;
|
||||
foreach (var item in data) //不直接调用 Remove,防止清除 Identity/Guid
|
||||
{
|
||||
var state = CreateEntityState(item);
|
||||
_states.TryRemove(state.Key, out var trystate);
|
||||
if (inMemory) _db.OrmOriginal.ClearEntityPrimaryValueWithIdentityAndGuid(_entityType, item);
|
||||
|
||||
EnqueueToDbContext(DbContext.EntityChangeType.Delete, state);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dbset.CanRemove(items, true) == false) return;
|
||||
foreach (var item in items) //不直接调用 dbset.Remove,防止清除 Identity/Guid
|
||||
{
|
||||
var state = dbset.CreateEntityState(item);
|
||||
dbset._states.TryRemove(state.Key, out var trystate);
|
||||
if (inMemory) _db.OrmOriginal.ClearEntityPrimaryValueWithIdentityAndGuid(dbset.EntityType, item);
|
||||
|
||||
dbset.EnqueueToDbContext(DbContext.EntityChangeType.Delete, state);
|
||||
}
|
||||
|
||||
var rawset = _db.Set(dbset.EntityType);
|
||||
var statesRemove = typeof(DbSet<>).MakeGenericType(dbset.EntityType).GetMethod("StatesRemoveByObjects", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(IEnumerable<object>) }, null);
|
||||
if (statesRemove == null) throw new Exception(DbContextStrings.NotFoundMethod_StatesRemoveByObjects);
|
||||
statesRemove.Invoke(rawset, new object[] { items });
|
||||
}
|
||||
returnDeleted?.AddRange(items);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
61
FreeSql.DbContext/EfCoreFluentApi/EfCoreColumnFluent.cs
Normal file
61
FreeSql.DbContext/EfCoreFluentApi/EfCoreColumnFluent.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
|
||||
namespace FreeSql.Extensions.EfCoreFluentApi
|
||||
{
|
||||
public class EfCoreColumnFluent
|
||||
{
|
||||
ColumnFluent _cf;
|
||||
|
||||
internal EfCoreColumnFluent(ColumnFluent tf)
|
||||
{
|
||||
_cf = tf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 FreeSql FluentApi 方法,当 EFCore FluentApi 方法无法表示的时候使用
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ColumnFluent Help() => _cf;
|
||||
|
||||
public EfCoreColumnFluent HasColumnName(string name)
|
||||
{
|
||||
_cf.Name(name);
|
||||
return this;
|
||||
}
|
||||
public EfCoreColumnFluent HasColumnType(string dbtype)
|
||||
{
|
||||
_cf.DbType(dbtype);
|
||||
return this;
|
||||
}
|
||||
public EfCoreColumnFluent IsRequired()
|
||||
{
|
||||
_cf.IsNullable(false);
|
||||
return this;
|
||||
}
|
||||
public EfCoreColumnFluent HasMaxLength(int length)
|
||||
{
|
||||
_cf.StringLength(length);
|
||||
return this;
|
||||
}
|
||||
public EfCoreColumnFluent HasDefaultValueSql(string sqlValue)
|
||||
{
|
||||
_cf.InsertValueSql(sqlValue);
|
||||
return this;
|
||||
}
|
||||
public EfCoreColumnFluent IsRowVersion()
|
||||
{
|
||||
_cf.IsVersion(true);
|
||||
return this;
|
||||
}
|
||||
//public EfCoreColumnFluent HasConversion(Func<object, string> stringify, Func<string, object> parse)
|
||||
//{
|
||||
// return this;
|
||||
//}
|
||||
public EfCoreColumnFluent HasPrecision(int precision, int scale = 0)
|
||||
{
|
||||
_cf.Precision(precision, scale);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
138
FreeSql.DbContext/EfCoreFluentApi/EfCoreFluentApiExtensions.cs
Normal file
138
FreeSql.DbContext/EfCoreFluentApi/EfCoreFluentApiExtensions.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using FreeSql;
|
||||
using FreeSql.DataAnnotations;
|
||||
using FreeSql.Extensions.EfCoreFluentApi;
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
|
||||
partial class FreeSqlDbContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// EFCore 95% 相似的 FluentApi 扩展方法
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="codeFirst"></param>
|
||||
/// <param name="modelBuilder"></param>
|
||||
/// <returns></returns>
|
||||
public static ICodeFirst Entity<T>(this ICodeFirst codeFirst, Action<EfCoreTableFluent<T>> modelBuilder)
|
||||
{
|
||||
var cf = codeFirst as CodeFirstProvider;
|
||||
codeFirst.ConfigEntity<T>(tf => modelBuilder(new EfCoreTableFluent<T>(cf._orm, tf)));
|
||||
return codeFirst;
|
||||
}
|
||||
/// <summary>
|
||||
/// EFCore 95% 相似的 FluentApi 扩展方法
|
||||
/// </summary>
|
||||
/// <param name="codeFirst"></param>
|
||||
/// <param name="entityType">实体类型</param>
|
||||
/// <param name="modelBuilder"></param>
|
||||
/// <returns></returns>
|
||||
public static ICodeFirst Entity(this ICodeFirst codeFirst, Type entityType, Action<EfCoreTableFluent> modelBuilder)
|
||||
{
|
||||
var cf = codeFirst as CodeFirstProvider;
|
||||
codeFirst.ConfigEntity(entityType, tf => modelBuilder(new EfCoreTableFluent(cf._orm, tf, entityType)));
|
||||
return codeFirst;
|
||||
}
|
||||
|
||||
public static ICodeFirst ApplyConfiguration<TEntity>(this ICodeFirst codeFirst, IEntityTypeConfiguration<TEntity> configuration) where TEntity : class
|
||||
{
|
||||
return codeFirst.Entity<TEntity>(eb =>
|
||||
{
|
||||
configuration.Configure(eb);
|
||||
});
|
||||
}
|
||||
#if net40
|
||||
#else
|
||||
static IEnumerable<MethodInfo> GetExtensionMethods(this Assembly assembly, Type extendedType)
|
||||
{
|
||||
var query = from type in assembly.GetTypes()
|
||||
where !type.IsGenericType && !type.IsNested
|
||||
from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
where method.IsDefined(typeof(System.Runtime.CompilerServices.ExtensionAttribute), false)
|
||||
where method.GetParameters()[0].ParameterType == extendedType
|
||||
select method;
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据Assembly扫描所有继承IEntityTypeConfiguration<T>的配置类
|
||||
/// </summary>
|
||||
/// <param name="codeFirst"></param>
|
||||
/// <param name="assembly"></param>
|
||||
/// <param name="predicate"></param>
|
||||
/// <returns></returns>
|
||||
public static ICodeFirst ApplyConfigurationsFromAssembly(this ICodeFirst codeFirst, Assembly assembly, Func<Type, bool> predicate = null)
|
||||
{
|
||||
IEnumerable<TypeInfo> typeInfos = assembly.DefinedTypes.Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition);
|
||||
|
||||
MethodInfo methodInfo = typeof(FreeSqlDbContextExtensions).Assembly.GetExtensionMethods(typeof(ICodeFirst))
|
||||
.Single((e) => e.Name == "Entity" && e.ContainsGenericParameters);
|
||||
|
||||
if (methodInfo == null) return codeFirst;
|
||||
|
||||
foreach (TypeInfo constructibleType in typeInfos)
|
||||
{
|
||||
if (constructibleType.GetConstructor(Type.EmptyTypes) == null || predicate != null && !predicate(constructibleType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var @interface in constructibleType.GetInterfaces())
|
||||
{
|
||||
if (@interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>))
|
||||
{
|
||||
var type = @interface.GetGenericArguments().First();
|
||||
var efFluentType = typeof(EfCoreTableFluent<>).MakeGenericType(type);
|
||||
var actionType = typeof(Action<>).MakeGenericType(efFluentType);
|
||||
|
||||
//1.需要实体和Configuration配置
|
||||
//codeFirst.Entity<ToDoItem>(eb =>
|
||||
//{
|
||||
// new ToDoItemConfiguration().Configure(eb);
|
||||
//});
|
||||
|
||||
//2.需要实体
|
||||
//Action<EfCoreTableFluent<ToDoItem>> x = new Action<EfCoreTableFluent<ToDoItem>>(e =>
|
||||
//{
|
||||
// object o = Activator.CreateInstance(constructibleType);
|
||||
// constructibleType.GetMethod("ApplyConfiguration")?.Invoke(o, new object[1] { e });
|
||||
//});
|
||||
//codeFirst.Entity<ToDoItem>(x);
|
||||
|
||||
//3.实现动态调用泛型委托
|
||||
DelegateBuilder delegateBuilder = new DelegateBuilder(constructibleType);
|
||||
MethodInfo applyconfigureMethod = delegateBuilder.GetType().GetMethod("ApplyConfiguration")?.MakeGenericMethod(type);
|
||||
if (applyconfigureMethod == null) continue;
|
||||
Delegate @delegate = Delegate.CreateDelegate(actionType, delegateBuilder, applyconfigureMethod);
|
||||
|
||||
methodInfo.MakeGenericMethod(type).Invoke(null, new object[2]
|
||||
{
|
||||
codeFirst,
|
||||
@delegate
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return codeFirst;
|
||||
}
|
||||
class DelegateBuilder
|
||||
{
|
||||
private readonly Type type;
|
||||
|
||||
public DelegateBuilder(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
public void ApplyConfiguration<T>(EfCoreTableFluent<T> ex)
|
||||
{
|
||||
object o = Activator.CreateInstance(type);
|
||||
type.GetMethod("Configure")?.Invoke(o, new object[1] { ex });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
278
FreeSql.DbContext/EfCoreFluentApi/EfCoreTableFluent.cs
Normal file
278
FreeSql.DbContext/EfCoreFluentApi/EfCoreTableFluent.cs
Normal file
@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using FreeSql.DataAnnotations;
|
||||
|
||||
namespace FreeSql.Extensions.EfCoreFluentApi
|
||||
{
|
||||
public class EfCoreTableFluent
|
||||
{
|
||||
IFreeSql _fsql;
|
||||
TableFluent _tf;
|
||||
internal Type _entityType;
|
||||
internal EfCoreTableFluent(IFreeSql fsql, TableFluent tf, Type entityType)
|
||||
{
|
||||
_fsql = fsql;
|
||||
_tf = tf;
|
||||
_entityType = entityType;
|
||||
}
|
||||
|
||||
public EfCoreTableFluent ToTable(string name)
|
||||
{
|
||||
_tf.Name(name);
|
||||
return this;
|
||||
}
|
||||
public EfCoreTableFluent ToView(string name)
|
||||
{
|
||||
_tf.DisableSyncStructure(true);
|
||||
_tf.Name(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EfCoreColumnFluent Property(string property) => new EfCoreColumnFluent(_tf.Property(property));
|
||||
|
||||
/// <summary>
|
||||
/// 使用 FreeSql FluentApi 方法,当 EFCore FluentApi 方法无法表示的时候使用
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TableFluent Help() => _tf;
|
||||
|
||||
#region HasKey
|
||||
public EfCoreTableFluent HasKey(string key)
|
||||
{
|
||||
if (key == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("key"));
|
||||
foreach (string name in key.Split(','))
|
||||
{
|
||||
if (string.IsNullOrEmpty(name.Trim())) continue;
|
||||
_tf.Property(name.Trim()).IsPrimary(true);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HasIndex
|
||||
public HasIndexFluent HasIndex(string index)
|
||||
{
|
||||
if (index == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("index"));
|
||||
var indexName = $"idx_{Guid.NewGuid().ToString("N").Substring(0, 8)}";
|
||||
var columns = new List<string>();
|
||||
foreach (string name in index.Split(','))
|
||||
{
|
||||
if (string.IsNullOrEmpty(name.Trim())) continue;
|
||||
columns.Add(name.Trim());
|
||||
}
|
||||
_tf.Index(indexName, string.Join(", ", columns), false);
|
||||
return new HasIndexFluent(_tf, indexName, columns);
|
||||
}
|
||||
public class HasIndexFluent
|
||||
{
|
||||
TableFluent _modelBuilder;
|
||||
string _indexName;
|
||||
List<string> _columns;
|
||||
bool _isUnique;
|
||||
|
||||
internal HasIndexFluent(TableFluent modelBuilder, string indexName, List<string> columns)
|
||||
{
|
||||
_modelBuilder = modelBuilder;
|
||||
_indexName = indexName;
|
||||
_columns = columns;
|
||||
}
|
||||
public HasIndexFluent IsUnique()
|
||||
{
|
||||
_isUnique = true;
|
||||
_modelBuilder.Index(_indexName, string.Join(", ", _columns), _isUnique);
|
||||
return this;
|
||||
}
|
||||
public HasIndexFluent HasName(string name)
|
||||
{
|
||||
_modelBuilder.IndexRemove(_indexName);
|
||||
_indexName = name;
|
||||
_modelBuilder.Index(_indexName, string.Join(", ", _columns), _isUnique);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HasOne
|
||||
public HasOneFluent HasOne(string one)
|
||||
{
|
||||
if (string.IsNullOrEmpty(one)) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("one"));
|
||||
if (_entityType.GetPropertiesDictIgnoreCase().TryGetValue(one, out var oneProperty) == false) throw new ArgumentException(DbContextStrings.ParameterError_NotFound_Property(one));
|
||||
return new HasOneFluent(_fsql, _tf, _entityType, oneProperty.PropertyType, one);
|
||||
}
|
||||
public class HasOneFluent
|
||||
{
|
||||
IFreeSql _fsql;
|
||||
TableFluent _tf;
|
||||
Type _entityType1;
|
||||
Type _entityType2;
|
||||
string _selfProperty;
|
||||
string _selfBind;
|
||||
string _withManyProperty;
|
||||
string _withOneProperty;
|
||||
string _withOneBind;
|
||||
|
||||
internal HasOneFluent(IFreeSql fsql, TableFluent modelBuilder, Type entityType1, Type entityType2, string oneProperty)
|
||||
{
|
||||
_fsql = fsql;
|
||||
_tf = modelBuilder;
|
||||
_entityType1 = entityType1;
|
||||
_entityType2 = entityType2;
|
||||
_selfProperty = oneProperty;
|
||||
}
|
||||
public HasOneFluent WithMany(string many)
|
||||
{
|
||||
if (many == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("many"));
|
||||
if (_entityType2.GetPropertiesDictIgnoreCase().TryGetValue(many, out var manyProperty) == false) throw new ArgumentException(DbContextStrings.ParameterError_NotFound_Property(many));
|
||||
_withManyProperty = manyProperty.Name;
|
||||
if (string.IsNullOrEmpty(_selfBind) == false)
|
||||
_fsql.CodeFirst.ConfigEntity(_entityType2, eb2 => eb2.Navigate(many, _selfBind));
|
||||
return this;
|
||||
}
|
||||
public HasOneFluent WithOne(string one, string foreignKey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(one)) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("one"));
|
||||
if (_entityType1.GetPropertiesDictIgnoreCase().TryGetValue(one, out var oneProperty) == false) throw new ArgumentException(DbContextStrings.ParameterError_NotFound_Property(one));
|
||||
if (oneProperty != _entityType1) throw new ArgumentException(DbContextStrings.ParameterError_NotFound_Property(one));
|
||||
_withOneProperty = oneProperty.Name;
|
||||
|
||||
if (foreignKey == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("foreignKey"));
|
||||
foreach (string name in foreignKey.Split(','))
|
||||
{
|
||||
if (string.IsNullOrEmpty(name.Trim())) continue;
|
||||
_withOneBind += ", " + name.Trim();
|
||||
}
|
||||
if (string.IsNullOrEmpty(_withOneBind)) throw new ArgumentException(DbContextStrings.ParameterError("foreignKey"));
|
||||
_withOneBind = _withOneBind.TrimStart(',', ' ');
|
||||
if (string.IsNullOrEmpty(_selfBind) == false)
|
||||
_fsql.CodeFirst.ConfigEntity(_entityType2, eb2 => eb2.Navigate(_withOneProperty, _withOneBind));
|
||||
return this;
|
||||
}
|
||||
public HasOneFluent HasForeignKey(string foreignKey)
|
||||
{
|
||||
if (foreignKey == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("foreignKey"));
|
||||
foreach (string name in foreignKey.Split(','))
|
||||
{
|
||||
if (string.IsNullOrEmpty(name.Trim())) continue;
|
||||
_selfBind += ", " + name.Trim();
|
||||
}
|
||||
if (string.IsNullOrEmpty(_selfBind)) throw new ArgumentException(DbContextStrings.ParameterError("foreignKey"));
|
||||
_selfBind = _selfBind.TrimStart(',', ' ');
|
||||
_tf.Navigate(_selfProperty, _selfBind);
|
||||
if (string.IsNullOrEmpty(_withManyProperty) == false)
|
||||
_fsql.CodeFirst.ConfigEntity(_entityType2, eb2 => eb2.Navigate(_withManyProperty, _selfBind));
|
||||
if (string.IsNullOrEmpty(_withOneProperty) == false && string.IsNullOrEmpty(_withOneBind) == false)
|
||||
_fsql.CodeFirst.ConfigEntity(_entityType2, eb2 => eb2.Navigate(_withOneProperty, _withOneBind));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HasMany
|
||||
public HasManyFluent HasMany(string many)
|
||||
{
|
||||
if (string.IsNullOrEmpty(many)) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("many"));
|
||||
if (_entityType.GetPropertiesDictIgnoreCase().TryGetValue(many, out var manyProperty) == false) throw new ArgumentException(DbContextStrings.ParameterError_NotFound_CollectionProperties(many));
|
||||
if (typeof(IEnumerable).IsAssignableFrom(manyProperty.PropertyType) == false || manyProperty.PropertyType.IsGenericType == false) throw new ArgumentException(DbContextStrings.ParameterError_IsNot_CollectionProperties(many));
|
||||
return new HasManyFluent(_fsql, _tf, _entityType, manyProperty.PropertyType.GetGenericArguments()[0], manyProperty.Name);
|
||||
}
|
||||
public class HasManyFluent
|
||||
{
|
||||
IFreeSql _fsql;
|
||||
TableFluent _tf;
|
||||
Type _entityType1;
|
||||
Type _entityType2;
|
||||
string _selfProperty;
|
||||
string _selfBind;
|
||||
string _withOneProperty;
|
||||
string _withManyProperty;
|
||||
|
||||
internal HasManyFluent(IFreeSql fsql, TableFluent modelBuilder, Type entityType1, Type entityType2, string manyProperty)
|
||||
{
|
||||
_fsql = fsql;
|
||||
_tf = modelBuilder;
|
||||
_entityType1 = entityType1;
|
||||
_entityType2 = entityType2;
|
||||
_selfProperty = manyProperty;
|
||||
}
|
||||
|
||||
public void WithMany(string many, Type middleType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(many)) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("many"));
|
||||
if (_entityType2.GetPropertiesDictIgnoreCase().TryGetValue(many, out var manyProperty) == false) throw new ArgumentException(DbContextStrings.ParameterError_NotFound_CollectionProperties(many));
|
||||
if (typeof(IEnumerable).IsAssignableFrom(manyProperty.PropertyType) == false || manyProperty.PropertyType.IsGenericType == false) throw new ArgumentException(DbContextStrings.ParameterError_IsNot_CollectionProperties(many));
|
||||
_withManyProperty = manyProperty.Name;
|
||||
_tf.Navigate(_selfProperty, null, middleType);
|
||||
_fsql.CodeFirst.ConfigEntity(_entityType2, eb2 => eb2.Navigate(_withManyProperty, null, middleType));
|
||||
}
|
||||
public HasManyFluent WithOne(string one)
|
||||
{
|
||||
if (string.IsNullOrEmpty(one)) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("one"));
|
||||
if (_entityType2.GetPropertiesDictIgnoreCase().TryGetValue(one, out var oneProperty) == false) throw new ArgumentException(DbContextStrings.ParameterError_NotFound_Property(one));
|
||||
if (oneProperty.PropertyType != _entityType1) throw new ArgumentException(DbContextStrings.ParameterError_NotFound_Property(one));
|
||||
_withOneProperty = oneProperty.Name;
|
||||
if (string.IsNullOrEmpty(_selfBind) == false)
|
||||
_fsql.CodeFirst.ConfigEntity(_entityType2, eb2 => eb2.Navigate(oneProperty.Name, _selfBind));
|
||||
return this;
|
||||
}
|
||||
public HasManyFluent HasForeignKey(string foreignKey)
|
||||
{
|
||||
if (foreignKey == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("foreignKey"));
|
||||
foreach (string name in foreignKey.Split(','))
|
||||
{
|
||||
if (string.IsNullOrEmpty(name.Trim())) continue;
|
||||
_selfBind += ", " + name.Trim();
|
||||
}
|
||||
if (string.IsNullOrEmpty(_selfBind)) throw new ArgumentException(DbContextStrings.ParameterError("foreignKey"));
|
||||
_selfBind = _selfBind.TrimStart(',', ' ');
|
||||
_tf.Navigate(_selfProperty, _selfBind);
|
||||
if (string.IsNullOrEmpty(_withOneProperty) == false)
|
||||
_fsql.CodeFirst.ConfigEntity(_entityType2, eb2 => eb2.Navigate(_withOneProperty, _selfBind));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public EfCoreTableFluent Ignore(string property)
|
||||
{
|
||||
_tf.Property(property).IsIgnore(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Repository + EnableCascadeSave + NoneParameter 方式插入种子数据
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public EfCoreTableFluent HasData(IEnumerable<object> data)
|
||||
{
|
||||
if (data.Any() == false) return this;
|
||||
var sdCopy = data.Select(a => (object)a).ToList();
|
||||
var sdCopyLock = new object();
|
||||
_fsql.Aop.SyncStructureAfter += new EventHandler<Aop.SyncStructureAfterEventArgs>((s, e) =>
|
||||
{
|
||||
object[] sd = null;
|
||||
lock (sdCopyLock)
|
||||
sd = sdCopy?.ToArray();
|
||||
if (sd == null || sd.Any() == false) return;
|
||||
foreach (var et in e.EntityTypes)
|
||||
{
|
||||
if (et != _entityType) continue;
|
||||
if (_fsql.Select<object>().AsType(et).Any()) continue;
|
||||
|
||||
var repo = _fsql.GetRepository<object>();
|
||||
repo.DbContextOptions.EnableCascadeSave = true;
|
||||
repo.DbContextOptions.NoneParameter = true;
|
||||
repo.AsType(et);
|
||||
repo.Insert(sd);
|
||||
|
||||
lock (sdCopyLock)
|
||||
sdCopy = null;
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
365
FreeSql.DbContext/EfCoreFluentApi/EfCoreTableFluent`1.cs
Normal file
365
FreeSql.DbContext/EfCoreFluentApi/EfCoreTableFluent`1.cs
Normal file
@ -0,0 +1,365 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using FreeSql.DataAnnotations;
|
||||
|
||||
namespace FreeSql.Extensions.EfCoreFluentApi
|
||||
{
|
||||
public class EfCoreTableFluent<T>
|
||||
{
|
||||
IFreeSql _fsql;
|
||||
TableFluent<T> _tf;
|
||||
internal EfCoreTableFluent(IFreeSql fsql, TableFluent<T> tf)
|
||||
{
|
||||
_fsql = fsql;
|
||||
_tf = tf;
|
||||
}
|
||||
|
||||
public EfCoreTableFluent<T> ToTable(string name)
|
||||
{
|
||||
_tf.Name(name);
|
||||
return this;
|
||||
}
|
||||
public EfCoreTableFluent<T> ToView(string name)
|
||||
{
|
||||
_tf.DisableSyncStructure(true);
|
||||
_tf.Name(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EfCoreColumnFluent Property<TProperty>(Expression<Func<T, TProperty>> property) => new EfCoreColumnFluent(_tf.Property(property));
|
||||
public EfCoreColumnFluent Property(string property) => new EfCoreColumnFluent(_tf.Property(property));
|
||||
|
||||
/// <summary>
|
||||
/// 使用 FreeSql FluentApi 方法,当 EFCore FluentApi 方法无法表示的时候使用
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TableFluent<T> Help() => _tf;
|
||||
|
||||
#region HasKey
|
||||
public EfCoreTableFluent<T> HasKey(Expression<Func<T, object>> key)
|
||||
{
|
||||
var exp = key?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("key"));
|
||||
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
_tf.Property((exp as MemberExpression).Member.Name).IsPrimary(true);
|
||||
break;
|
||||
case ExpressionType.New:
|
||||
foreach (var member in (exp as NewExpression).Members)
|
||||
_tf.Property(member.Name).IsPrimary(true);
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HasIndex
|
||||
public HasIndexFluent HasIndex(Expression<Func<T, object>> index)
|
||||
{
|
||||
var exp = index?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("index"));
|
||||
|
||||
var indexName = $"idx_{Guid.NewGuid().ToString("N").Substring(0, 8)}";
|
||||
var columns = new List<string>();
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
columns.Add((exp as MemberExpression).Member.Name);
|
||||
break;
|
||||
case ExpressionType.New:
|
||||
foreach (var member in (exp as NewExpression).Members)
|
||||
columns.Add(member.Name);
|
||||
break;
|
||||
}
|
||||
_tf.Index(indexName, string.Join(", ", columns), false);
|
||||
return new HasIndexFluent(_tf, indexName, columns);
|
||||
}
|
||||
public class HasIndexFluent
|
||||
{
|
||||
TableFluent<T> _modelBuilder;
|
||||
string _indexName;
|
||||
List<string> _columns;
|
||||
bool _isUnique;
|
||||
|
||||
internal HasIndexFluent(TableFluent<T> modelBuilder, string indexName, List<string> columns)
|
||||
{
|
||||
_modelBuilder = modelBuilder;
|
||||
_indexName = indexName;
|
||||
_columns = columns;
|
||||
}
|
||||
public HasIndexFluent IsUnique()
|
||||
{
|
||||
_isUnique = true;
|
||||
_modelBuilder.Index(_indexName, string.Join(", ", _columns), _isUnique);
|
||||
return this;
|
||||
}
|
||||
public HasIndexFluent HasName(string name)
|
||||
{
|
||||
_modelBuilder.IndexRemove(_indexName);
|
||||
_indexName = name;
|
||||
_modelBuilder.Index(_indexName, string.Join(", ", _columns), _isUnique);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HasOne
|
||||
public HasOneFluent<T2> HasOne<T2>(Expression<Func<T, T2>> one)
|
||||
{
|
||||
var exp = one?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("one"));
|
||||
|
||||
var oneProperty = "";
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
oneProperty = (exp as MemberExpression).Member.Name;
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(oneProperty)) throw new ArgumentException(DbContextStrings.ParameterError("one"));
|
||||
return new HasOneFluent<T2>(_fsql, _tf, oneProperty);
|
||||
}
|
||||
public class HasOneFluent<T2>
|
||||
{
|
||||
IFreeSql _fsql;
|
||||
TableFluent<T> _tf;
|
||||
string _selfProperty;
|
||||
string _selfBind;
|
||||
string _withManyProperty;
|
||||
string _withOneProperty;
|
||||
string _withOneBind;
|
||||
|
||||
internal HasOneFluent(IFreeSql fsql, TableFluent<T> modelBuilder, string oneProperty)
|
||||
{
|
||||
_fsql = fsql;
|
||||
_tf = modelBuilder;
|
||||
_selfProperty = oneProperty;
|
||||
}
|
||||
public HasOneFluent<T2> WithMany<TMany>(Expression<Func<T2, TMany>> many)
|
||||
{
|
||||
var exp = many?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("many"));
|
||||
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
_withManyProperty = (exp as MemberExpression).Member.Name;
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(_withManyProperty)) throw new ArgumentException(DbContextStrings.ParameterError("many"));
|
||||
if (string.IsNullOrEmpty(_selfBind) == false)
|
||||
_fsql.CodeFirst.ConfigEntity<T2>(eb2 => eb2.Navigate(_withManyProperty, _selfBind));
|
||||
return this;
|
||||
}
|
||||
public HasOneFluent<T2> WithOne(Expression<Func<T2, T>> one, Expression<Func<T2, object>> foreignKey)
|
||||
{
|
||||
var exp = one?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("one"));
|
||||
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
_withOneProperty = (exp as MemberExpression).Member.Name;
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(_withOneProperty)) throw new ArgumentException(DbContextStrings.ParameterError("one"));
|
||||
|
||||
exp = foreignKey?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("foreignKey"));
|
||||
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
_withOneBind = (exp as MemberExpression).Member.Name;
|
||||
_withOneBind = _withOneBind.TrimStart(',', ' ');
|
||||
break;
|
||||
case ExpressionType.New:
|
||||
_withOneBind = "";
|
||||
foreach (var member in (exp as NewExpression).Members)
|
||||
_withOneBind += ", " + member.Name;
|
||||
_withOneBind = _withOneBind.TrimStart(',', ' ');
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(_withOneBind)) throw new ArgumentException(DbContextStrings.ParameterError("foreignKey"));
|
||||
if (string.IsNullOrEmpty(_selfBind) == false)
|
||||
_fsql.CodeFirst.ConfigEntity<T2>(eb2 => eb2.Navigate(_withOneProperty, _withOneBind));
|
||||
return this;
|
||||
}
|
||||
public HasOneFluent<T2> HasForeignKey(Expression<Func<T, object>> foreignKey)
|
||||
{
|
||||
var exp = foreignKey?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("foreignKey"));
|
||||
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
_selfBind = (exp as MemberExpression).Member.Name;
|
||||
_selfBind = _selfBind.TrimStart(',', ' ');
|
||||
break;
|
||||
case ExpressionType.New:
|
||||
_selfBind = "";
|
||||
foreach (var member in (exp as NewExpression).Members)
|
||||
_selfBind += ", " + member.Name;
|
||||
_selfBind = _selfBind.TrimStart(',', ' ');
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(_selfBind)) throw new ArgumentException(DbContextStrings.ParameterError("foreignKey"));
|
||||
_tf.Navigate(_selfProperty, _selfBind);
|
||||
if (string.IsNullOrEmpty(_withManyProperty) == false)
|
||||
_fsql.CodeFirst.ConfigEntity<T2>(eb2 => eb2.Navigate(_withManyProperty, _selfBind));
|
||||
if (string.IsNullOrEmpty(_withOneProperty) == false && string.IsNullOrEmpty(_withOneBind) == false)
|
||||
_fsql.CodeFirst.ConfigEntity<T2>(eb2 => eb2.Navigate(_withOneProperty, _withOneBind));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HasMany
|
||||
public HasManyFluent<T2> HasMany<T2>(Expression<Func<T, IEnumerable<T2>>> many)
|
||||
{
|
||||
var exp = many?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("many"));
|
||||
|
||||
var manyProperty = "";
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
manyProperty = (exp as MemberExpression).Member.Name;
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(manyProperty)) throw new ArgumentException(DbContextStrings.ParameterError("many"));
|
||||
return new HasManyFluent<T2>(_fsql, _tf, manyProperty);
|
||||
}
|
||||
public class HasManyFluent<T2>
|
||||
{
|
||||
IFreeSql _fsql;
|
||||
TableFluent<T> _tf;
|
||||
string _selfProperty;
|
||||
string _selfBind;
|
||||
string _withOneProperty;
|
||||
string _withManyProperty;
|
||||
|
||||
internal HasManyFluent(IFreeSql fsql, TableFluent<T> modelBuilder, string manyProperty)
|
||||
{
|
||||
_fsql = fsql;
|
||||
_tf = modelBuilder;
|
||||
_selfProperty = manyProperty;
|
||||
}
|
||||
|
||||
public void WithMany(Expression<Func<T2, IEnumerable<T>>> many, Type middleType)
|
||||
{
|
||||
var exp = many?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("many"));
|
||||
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
_withManyProperty = (exp as MemberExpression).Member.Name;
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(_withManyProperty)) throw new ArgumentException(DbContextStrings.ParameterError("many"));
|
||||
|
||||
_tf.Navigate(_selfProperty, null, middleType);
|
||||
_fsql.CodeFirst.ConfigEntity<T2>(eb2 => eb2.Navigate(_withManyProperty, null, middleType));
|
||||
}
|
||||
public HasManyFluent<T2> WithOne(Expression<Func<T2, T>> one)
|
||||
{
|
||||
var exp = one?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("one"));
|
||||
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
_withOneProperty = (exp as MemberExpression).Member.Name;
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(_withOneProperty)) throw new ArgumentException(DbContextStrings.ParameterError("one"));
|
||||
|
||||
if (string.IsNullOrEmpty(_selfBind) == false)
|
||||
_fsql.CodeFirst.ConfigEntity<T2>(eb2 => eb2.Navigate(_withOneProperty, _selfBind));
|
||||
return this;
|
||||
}
|
||||
public HasManyFluent<T2> HasForeignKey(Expression<Func<T2, object>> foreignKey)
|
||||
{
|
||||
var exp = foreignKey?.Body;
|
||||
if (exp?.NodeType == ExpressionType.Convert) exp = (exp as UnaryExpression)?.Operand;
|
||||
if (exp == null) throw new ArgumentException(DbContextStrings.ParameterError_CannotBeNull("foreignKey"));
|
||||
|
||||
switch (exp.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
_selfBind = (exp as MemberExpression).Member.Name;
|
||||
_selfBind = _selfBind.TrimStart(',', ' ');
|
||||
break;
|
||||
case ExpressionType.New:
|
||||
_selfBind = "";
|
||||
foreach (var member in (exp as NewExpression).Members)
|
||||
_selfBind += ", " + member.Name;
|
||||
_selfBind = _selfBind.TrimStart(',', ' ');
|
||||
break;
|
||||
}
|
||||
if (string.IsNullOrEmpty(_selfBind)) throw new ArgumentException(DbContextStrings.ParameterError("foreignKey"));
|
||||
_tf.Navigate(_selfProperty, _selfBind);
|
||||
if (string.IsNullOrEmpty(_withOneProperty) == false)
|
||||
_fsql.CodeFirst.ConfigEntity<T2>(eb2 => eb2.Navigate(_withOneProperty, _selfBind));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public EfCoreTableFluent<T> Ignore<TProperty>(Expression<Func<T, TProperty>> property)
|
||||
{
|
||||
_tf.Property(property).IsIgnore(true);
|
||||
return this;
|
||||
}
|
||||
public EfCoreTableFluent<T> HasData(T data) => HasData(new[] { data });
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Repository + EnableCascadeSave + NoneParameter 方式插入种子数据
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public EfCoreTableFluent<T> HasData(IEnumerable<T> data)
|
||||
{
|
||||
if (data.Any() == false) return this;
|
||||
var sdCopy = data.Select(a => (object)a).ToList();
|
||||
var sdCopyLock = new object();
|
||||
_fsql.Aop.SyncStructureAfter += new EventHandler<Aop.SyncStructureAfterEventArgs>((s, e) =>
|
||||
{
|
||||
object[] sd = null;
|
||||
lock (sdCopyLock)
|
||||
sd = sdCopy?.ToArray();
|
||||
if (sd == null || sd.Any() == false) return;
|
||||
foreach (var et in e.EntityTypes)
|
||||
{
|
||||
if (et != typeof(T)) continue;
|
||||
if (_fsql.Select<object>().AsType(et).Any()) continue;
|
||||
|
||||
var repo = _fsql.GetRepository<object>();
|
||||
repo.DbContextOptions.EnableCascadeSave = true;
|
||||
repo.DbContextOptions.NoneParameter = true;
|
||||
repo.AsType(et);
|
||||
repo.Insert(sd);
|
||||
|
||||
lock (sdCopyLock)
|
||||
sdCopy = null;
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace FreeSql.Extensions.EfCoreFluentApi
|
||||
{
|
||||
public interface IEntityTypeConfiguration<TEntity> where TEntity : class
|
||||
{
|
||||
void Configure(EfCoreTableFluent<TEntity> model);
|
||||
}
|
||||
}
|
56
FreeSql.DbContext/Extensions/DependencyInjection.cs
Normal file
56
FreeSql.DbContext/Extensions/DependencyInjection.cs
Normal file
@ -0,0 +1,56 @@
|
||||
#if netcoreapp
|
||||
using FreeSql;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class FreeSqlDbContextDependencyInjection
|
||||
{
|
||||
static IServiceCollection AddFreeDbContext(this IServiceCollection services, Type dbContextType, Action<DbContextOptionsBuilder> options)
|
||||
{
|
||||
services.AddScoped(dbContextType, sp =>
|
||||
{
|
||||
DbContext ctx = null;
|
||||
try
|
||||
{
|
||||
var ctor = dbContextType. GetConstructors().FirstOrDefault();
|
||||
var ctorParams = ctor.GetParameters().Select(a => sp.GetService(a.ParameterType)).ToArray();
|
||||
ctx = Activator.CreateInstance(dbContextType, ctorParams) as DbContext;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
throw new Exception(DbContextStrings.AddFreeDbContextError_CheckConstruction(dbContextType.Name), ex);
|
||||
}
|
||||
if (ctx != null && ctx._ormScoped == null)
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder();
|
||||
options(builder);
|
||||
ctx._ormScoped = DbContextScopedFreeSql.Create(builder._fsql, () => ctx, () => ctx.UnitOfWork);
|
||||
ctx._optionsPriv = builder._options;
|
||||
|
||||
if (ctx._ormScoped == null)
|
||||
throw new Exception(DbContextStrings.ConfigureUseFreeSql);
|
||||
|
||||
ctx.InitPropSets();
|
||||
}
|
||||
return ctx;
|
||||
});
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddFreeDbContext<TDbContext>(this IServiceCollection services, Action<DbContextOptionsBuilder> options) where TDbContext : DbContext =>
|
||||
AddFreeDbContext(services, typeof(TDbContext), options);
|
||||
|
||||
public static IServiceCollection AddFreeDbContext(this IServiceCollection services, Action<DbContextOptionsBuilder> options, Assembly[] assemblies)
|
||||
{
|
||||
if (assemblies?.Any() == true)
|
||||
foreach (var asse in assemblies)
|
||||
foreach (var dbType in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(DbContext).IsAssignableFrom(a)))
|
||||
AddFreeDbContext(services, dbType, options);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
44
FreeSql.DbContext/Extensions/FreeSqlDbContextExtensions.cs
Normal file
44
FreeSql.DbContext/Extensions/FreeSqlDbContextExtensions.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using FreeSql;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public static partial class FreeSqlDbContextExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 创建普通数据上下文档对象
|
||||
/// </summary>
|
||||
/// <param name="that"></param>
|
||||
/// <returns></returns>
|
||||
public static DbContext CreateDbContext(this IFreeSql that)
|
||||
{
|
||||
return new FreeContext(that);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 不跟踪查询的实体数据(在不需要更新其数据时使用),可提升查询性能
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="select"></param>
|
||||
/// <returns></returns>
|
||||
public static ISelect<T> NoTracking<T>(this ISelect<T> select) where T : class
|
||||
{
|
||||
return select.TrackToList(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 DbContext 选项设置
|
||||
/// </summary>
|
||||
/// <param name="that"></param>
|
||||
/// <param name="options"></param>
|
||||
public static IFreeSql SetDbContextOptions(this IFreeSql that, Action<DbContextOptions> options)
|
||||
{
|
||||
if (options == null) return that;
|
||||
var cfg = _dicSetDbContextOptions.GetOrAdd(that.Ado.Identifier, t => new DbContextOptions());
|
||||
options(cfg);
|
||||
_dicSetDbContextOptions.AddOrUpdate(that.Ado.Identifier, cfg, (t, o) => cfg);
|
||||
return that;
|
||||
}
|
||||
internal static ConcurrentDictionary<Guid, DbContextOptions> _dicSetDbContextOptions = new ConcurrentDictionary<Guid, DbContextOptions>();
|
||||
}
|
88
FreeSql.DbContext/FreeSql.DbContext.csproj
Normal file
88
FreeSql.DbContext/FreeSql.DbContext.csproj
Normal file
@ -0,0 +1,88 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0;net5.0;netstandard2.1;netcoreapp3.1;netstandard2.0;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, Clickhouse, QuestDB, Odbc, Oledb, 达梦, 人大金仓, 南大通用, 虚谷, 神舟通用, 翰高, 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).NS</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.833-ns5</Version>
|
||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../readme.md" Pack="true" PackagePath="\"/>
|
||||
<None Include="../logo.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<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)' == 'net8.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'netcoreapp3.1'">
|
||||
<DefineConstants>netcoreapp</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.10" />
|
||||
</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>
|
375
FreeSql.DbContext/Properties/DbContextStrings.Designer.cs
generated
Normal file
375
FreeSql.DbContext/Properties/DbContextStrings.Designer.cs
generated
Normal file
@ -0,0 +1,375 @@
|
||||
|
||||
// <auto-generated />
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// String resources used in FreeSql exceptions, etc.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// These strings are exposed publicly for use by database providers and extensions.
|
||||
/// It is unusual for application code to need these strings.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class DbContextStrings
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager = new ResourceManager("FreeSql.DbContext.Properties.DbContextStrings", typeof(DbContextStrings).Assembly);
|
||||
|
||||
private static CultureInfo _resourceCulture;
|
||||
|
||||
/// <summary>
|
||||
/// 重写当前线程的 CurrentUICulture 属性,对
|
||||
/// 使用此强类型资源类的所有资源查找执行重写。
|
||||
/// </summary>
|
||||
public static CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return _resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
_resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AddFreeDbContext 发生错误,请检查 {dbContextTypeName} 的构造参数都已正确注入
|
||||
/// </summary>
|
||||
public static string AddFreeDbContextError_CheckConstruction(object dbContextTypeName)
|
||||
=> string.Format(
|
||||
GetString("AddFreeDbContextError_CheckConstruction", nameof(dbContextTypeName)),
|
||||
dbContextTypeName);
|
||||
|
||||
/// <summary>
|
||||
/// 不可添加,已存在于状态管理:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotAdd_AlreadyExistsInStateManagement(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotAdd_AlreadyExistsInStateManagement", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可添加,实体没有主键:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotAdd_EntityHasNo_PrimaryKey(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotAdd_EntityHasNo_PrimaryKey", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可添加,未设置主键的值:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotAdd_PrimaryKey_NotSet(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotAdd_PrimaryKey_NotSet", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可添加,自增属性有值:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotAdd_SelfIncreasingHasValue(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotAdd_SelfIncreasingHasValue", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可附加,实体没有主键:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotAttach_EntityHasNo_PrimaryKey(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotAttach_EntityHasNo_PrimaryKey", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可附加,未设置主键的值:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotAttach_PrimaryKey_NotSet(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotAttach_PrimaryKey_NotSet", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可删除,数据未被跟踪,应该先查询:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotDelete_DataNotTracked_ShouldQuery(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotDelete_DataNotTracked_ShouldQuery", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可删除,实体没有主键:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotDelete_EntityHasNo_PrimaryKey(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotDelete_EntityHasNo_PrimaryKey", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可删除,未设置主键的值:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotDelete_PrimaryKey_NotSet(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotDelete_PrimaryKey_NotSet", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可进行编辑,实体没有主键:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotEdit_EntityHasNo_PrimaryKey(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotEdit_EntityHasNo_PrimaryKey", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可更新,数据未被跟踪,应该先查询 或者 Attach:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotUpdate_DataShouldQueryOrAttach(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotUpdate_DataShouldQueryOrAttach", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可更新,实体没有主键:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotUpdate_EntityHasNo_PrimaryKey(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotUpdate_EntityHasNo_PrimaryKey", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可更新,未设置主键的值:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotUpdate_PrimaryKey_NotSet(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotUpdate_PrimaryKey_NotSet", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可更新,数据库不存在该记录:{entityString}
|
||||
/// </summary>
|
||||
public static string CannotUpdate_RecordDoesNotExist(object entityString)
|
||||
=> string.Format(
|
||||
GetString("CannotUpdate_RecordDoesNotExist", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 请在 OnConfiguring 或 AddFreeDbContext 中配置 UseFreeSql
|
||||
/// </summary>
|
||||
public static string ConfigureUseFreeSql
|
||||
=> GetString("ConfigureUseFreeSql");
|
||||
|
||||
/// <summary>
|
||||
/// DbSet.AsType 参数错误,请传入正确的实体类型
|
||||
/// </summary>
|
||||
public static string DbSetAsType_NotSupport_Object
|
||||
=> GetString("DbSetAsType_NotSupport_Object");
|
||||
|
||||
/// <summary>
|
||||
/// 实体类型 {EntityTypeName} 无法转换为 {name},无法使用该方法
|
||||
/// </summary>
|
||||
public static string EntityType_CannotConvert(object EntityTypeName, object name)
|
||||
=> string.Format(
|
||||
GetString("EntityType_CannotConvert", nameof(EntityTypeName), nameof(name)),
|
||||
EntityTypeName, name);
|
||||
|
||||
/// <summary>
|
||||
/// 实体类型 {EntityTypeName} 主键类型不为 {fullName},无法使用该方法
|
||||
/// </summary>
|
||||
public static string EntityType_PrimaryKeyError(object EntityTypeName, object fullName)
|
||||
=> string.Format(
|
||||
GetString("EntityType_PrimaryKeyError", nameof(EntityTypeName), nameof(fullName)),
|
||||
EntityTypeName, fullName);
|
||||
|
||||
/// <summary>
|
||||
/// 实体类型 {EntityTypeName} 主键数量不为 1,无法使用该方法
|
||||
/// </summary>
|
||||
public static string EntityType_PrimaryKeyIsNotOne(object EntityTypeName)
|
||||
=> string.Format(
|
||||
GetString("EntityType_PrimaryKeyIsNotOne", nameof(EntityTypeName)),
|
||||
EntityTypeName);
|
||||
|
||||
/// <summary>
|
||||
/// FreeSql.Repository 设置过滤器失败,原因是对象不属于 IRepository
|
||||
/// </summary>
|
||||
public static string FailedSetFilter_NotBelongIRpository
|
||||
=> GetString("FailedSetFilter_NotBelongIRpository");
|
||||
|
||||
/// <summary>
|
||||
/// 不可比较,实体没有主键:{entityString}
|
||||
/// </summary>
|
||||
public static string Incomparable_EntityHasNo_PrimaryKey(object entityString)
|
||||
=> string.Format(
|
||||
GetString("Incomparable_EntityHasNo_PrimaryKey", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 不可比较,未设置主键的值:{entityString}
|
||||
/// </summary>
|
||||
public static string Incomparable_PrimaryKey_NotSet(object entityString)
|
||||
=> string.Format(
|
||||
GetString("Incomparable_PrimaryKey_NotSet", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// FreeSql.Repository Insert 失败,因为设置了过滤器 {filterKey}: {filterValueExpression},插入的数据不符合 {entityString}
|
||||
/// </summary>
|
||||
public static string InsertError_Filter(object filterKey, object filterValueExpression, object entityString)
|
||||
=> string.Format(
|
||||
GetString("InsertError_Filter", nameof(filterKey), nameof(filterValueExpression), nameof(entityString)),
|
||||
filterKey, filterValueExpression, entityString);
|
||||
|
||||
/// <summary>
|
||||
/// ISelect.AsType 参数不支持指定为 object
|
||||
/// </summary>
|
||||
public static string ISelectAsType_ParameterError
|
||||
=> GetString("ISelectAsType_ParameterError");
|
||||
|
||||
/// <summary>
|
||||
/// {tableTypeFullName} 不存在属性 {propertyName}
|
||||
/// </summary>
|
||||
public static string NotFound_Property(object tableTypeFullName, object propertyName)
|
||||
=> string.Format(
|
||||
GetString("NotFound_Property", nameof(tableTypeFullName), nameof(propertyName)),
|
||||
tableTypeFullName, propertyName);
|
||||
|
||||
/// <summary>
|
||||
/// 找不到方法 DbSet<>.StatesRemoveByObjects
|
||||
/// </summary>
|
||||
public static string NotFoundMethod_StatesRemoveByObjects
|
||||
=> GetString("NotFoundMethod_StatesRemoveByObjects");
|
||||
|
||||
/// <summary>
|
||||
/// 参数 data 类型错误 {entityTypeFullName}
|
||||
/// </summary>
|
||||
public static string ParameterDataTypeError(object entityTypeFullName)
|
||||
=> string.Format(
|
||||
GetString("ParameterDataTypeError", nameof(entityTypeFullName)),
|
||||
entityTypeFullName);
|
||||
|
||||
/// <summary>
|
||||
/// 参数错误 {param}
|
||||
/// </summary>
|
||||
public static string ParameterError(object param)
|
||||
=> string.Format(
|
||||
GetString("ParameterError", nameof(param)),
|
||||
param);
|
||||
|
||||
/// <summary>
|
||||
/// 参数错误 {param} 不能为 null
|
||||
/// </summary>
|
||||
public static string ParameterError_CannotBeNull(object param)
|
||||
=> string.Format(
|
||||
GetString("ParameterError_CannotBeNull", nameof(param)),
|
||||
param);
|
||||
|
||||
/// <summary>
|
||||
/// 参数错误 {many} 不是集合属性
|
||||
/// </summary>
|
||||
public static string ParameterError_IsNot_CollectionProperties(object many)
|
||||
=> string.Format(
|
||||
GetString("ParameterError_IsNot_CollectionProperties", nameof(many)),
|
||||
many);
|
||||
|
||||
/// <summary>
|
||||
/// 参数错误 {many} 集合属性不存在
|
||||
/// </summary>
|
||||
public static string ParameterError_NotFound_CollectionProperties(object many)
|
||||
=> string.Format(
|
||||
GetString("ParameterError_NotFound_CollectionProperties", nameof(many)),
|
||||
many);
|
||||
|
||||
/// <summary>
|
||||
/// 参数错误 {one} 属性不存在
|
||||
/// </summary>
|
||||
public static string ParameterError_NotFound_Property(object one)
|
||||
=> string.Format(
|
||||
GetString("ParameterError_NotFound_Property", nameof(one)),
|
||||
one);
|
||||
|
||||
/// <summary>
|
||||
/// Propagation_Mandatory: 使用当前事务,如果没有当前事务,就抛出异常
|
||||
/// </summary>
|
||||
public static string Propagation_Mandatory
|
||||
=> GetString("Propagation_Mandatory");
|
||||
|
||||
/// <summary>
|
||||
/// Propagation_Never: 以非事务方式执行操作,如果当前事务存在则抛出异常
|
||||
/// </summary>
|
||||
public static string Propagation_Never
|
||||
=> GetString("Propagation_Never");
|
||||
|
||||
/// <summary>
|
||||
/// {tableTypeFullName} 类型的属性 {propertyName} 不是 OneToMany 或 ManyToMany 特性
|
||||
/// </summary>
|
||||
public static string PropertyOfType_IsNot_OneToManyOrManyToMany(object tableTypeFullName, object propertyName)
|
||||
=> string.Format(
|
||||
GetString("PropertyOfType_IsNot_OneToManyOrManyToMany", nameof(tableTypeFullName), nameof(propertyName)),
|
||||
tableTypeFullName, propertyName);
|
||||
|
||||
/// <summary>
|
||||
/// 特别错误:批量添加失败,{dataType} 的返回数据,与添加的数目不匹配
|
||||
/// </summary>
|
||||
public static string SpecialError_BatchAdditionFailed(object dataType)
|
||||
=> string.Format(
|
||||
GetString("SpecialError_BatchAdditionFailed", nameof(dataType)),
|
||||
dataType);
|
||||
|
||||
/// <summary>
|
||||
/// 特别错误:更新失败,数据未被跟踪:{entityString}
|
||||
/// </summary>
|
||||
public static string SpecialError_UpdateFailedDataNotTracked(object entityString)
|
||||
=> string.Format(
|
||||
GetString("SpecialError_UpdateFailedDataNotTracked", nameof(entityString)),
|
||||
entityString);
|
||||
|
||||
/// <summary>
|
||||
/// 已开启事务,不能禁用工作单元
|
||||
/// </summary>
|
||||
public static string TransactionHasBeenStarted
|
||||
=> GetString("TransactionHasBeenStarted");
|
||||
|
||||
/// <summary>
|
||||
/// {tableTypeFullName} 类型已设置属性 {propertyName} 忽略特性
|
||||
/// </summary>
|
||||
public static string TypeHasSetProperty_IgnoreAttribute(object tableTypeFullName, object propertyName)
|
||||
=> string.Format(
|
||||
GetString("TypeHasSetProperty_IgnoreAttribute", nameof(tableTypeFullName), nameof(propertyName)),
|
||||
tableTypeFullName, propertyName);
|
||||
|
||||
/// <summary>
|
||||
/// {unitOfWorkManager} 构造参数 {fsql} 不能为 null
|
||||
/// </summary>
|
||||
public static string UnitOfWorkManager_Construction_CannotBeNull(object unitOfWorkManager, object fsql)
|
||||
=> string.Format(
|
||||
GetString("UnitOfWorkManager_Construction_CannotBeNull", nameof(unitOfWorkManager), nameof(fsql)),
|
||||
unitOfWorkManager, fsql);
|
||||
|
||||
/// <summary>
|
||||
/// FreeSql.Repository Update 失败,因为设置了过滤器 {filterKey}: {filterValueExpression},更新的数据不符合{entityString}
|
||||
/// </summary>
|
||||
public static string UpdateError_Filter(object filterKey, object filterValueExpression, object entityString)
|
||||
=> string.Format(
|
||||
GetString("UpdateError_Filter", nameof(filterKey), nameof(filterValueExpression), nameof(entityString)),
|
||||
filterKey, filterValueExpression, entityString);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name,_resourceCulture);
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
<#
|
||||
Session["ResourceFile"] = "DbContextStrings.resx";
|
||||
Session["AccessModifier"] = "public";
|
||||
#>
|
||||
<#@ include file="../../FreeSql/Properties/Resources.tt" #>
|
246
FreeSql.DbContext/Properties/DbContextStrings.resx
Normal file
246
FreeSql.DbContext/Properties/DbContextStrings.resx
Normal file
@ -0,0 +1,246 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AddFreeDbContextError_CheckConstruction" xml:space="preserve">
|
||||
<value>FreeSql: An error occurred in AddFreeDbContext, check that the construction parameters of {dbContextTypeName} have been injected correctly</value>
|
||||
</data>
|
||||
<data name="CannotAdd_AlreadyExistsInStateManagement" xml:space="preserve">
|
||||
<value>FreeSql: Not addable, already exists in state management: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAdd_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>FreeSql: Not addable, entity has no primary key: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAdd_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>FreeSql: Not addable, no value for primary key set: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAdd_SelfIncreasingHasValue" xml:space="preserve">
|
||||
<value>FreeSql: Not addable, self-increasing attribute has value: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAttach_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>FreeSql: Not attachable, entity has no primary key: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAttach_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>FreeSql: Not attachable, no value for primary key set: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotDelete_DataNotTracked_ShouldQuery" xml:space="preserve">
|
||||
<value>FreeSql: Not deletable, data not tracked, should query first: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotDelete_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>FreeSql: Not deletable, entity has no primary key: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotDelete_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>FreeSql: Not deletable, no value for primary key set: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotEdit_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>FreeSql: Not editable, entity has no primary key: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotUpdate_DataShouldQueryOrAttach" xml:space="preserve">
|
||||
<value>FreeSql: Not updatable, data not tracked, should be queried first or Attach:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotUpdate_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>FreeSql: Not updatable, entity has no primary key: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotUpdate_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>FreeSql: Not updatable, no value for primary key set: {entityString}</value>
|
||||
</data>
|
||||
<data name="CannotUpdate_RecordDoesNotExist" xml:space="preserve">
|
||||
<value>FreeSql: Not updatable, the record does not exist in the database: {entityString}</value>
|
||||
</data>
|
||||
<data name="ConfigureUseFreeSql" xml:space="preserve">
|
||||
<value>FreeSql: Please configure UseFreeSql in OnConfiguring or AddFreeDbContext</value>
|
||||
</data>
|
||||
<data name="DbSetAsType_NotSupport_Object" xml:space="preserve">
|
||||
<value>FreeSql: DbSet. AsType parameter error, please pass in the correct entity type</value>
|
||||
</data>
|
||||
<data name="EntityType_CannotConvert" xml:space="preserve">
|
||||
<value>FreeSql: Entity type {EntityTypeName} cannot be converted to {name} and cannot use this method</value>
|
||||
</data>
|
||||
<data name="EntityType_PrimaryKeyError" xml:space="preserve">
|
||||
<value>FreeSql: Entity type {EntityTypeName} Primary key type is not {fullName} and cannot be used with this method</value>
|
||||
</data>
|
||||
<data name="EntityType_PrimaryKeyIsNotOne" xml:space="preserve">
|
||||
<value>FreeSql: Entity type {EntityTypeName} Primary key number is not 1 and cannot be used with this method</value>
|
||||
</data>
|
||||
<data name="FailedSetFilter_NotBelongIRpository" xml:space="preserve">
|
||||
<value>FreeSql: FreeSql. Repository failed to set filter because object does not belong to IRepository</value>
|
||||
</data>
|
||||
<data name="Incomparable_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>FreeSql: Not comparable, entity has no primary key: {entityString}</value>
|
||||
</data>
|
||||
<data name="Incomparable_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>FreeSql: Non-comparable, no value for primary key set: {entityString}</value>
|
||||
</data>
|
||||
<data name="InsertError_Filter" xml:space="preserve">
|
||||
<value>FreeSql: FreeSql.Repository Insert failed because the filter {filterKey}: {filterValueExpression} was set and the inserted data does not conform to {entityString}</value>
|
||||
</data>
|
||||
<data name="ISelectAsType_ParameterError" xml:space="preserve">
|
||||
<value>FreeSql: ISelect. AsType parameter does not support specifying as object</value>
|
||||
</data>
|
||||
<data name="NotFound_Property" xml:space="preserve">
|
||||
<value>FreeSql: Property {propertyName} does not exist for {tableTypeFullName}</value>
|
||||
</data>
|
||||
<data name="NotFoundMethod_StatesRemoveByObjects" xml:space="preserve">
|
||||
<value>FreeSql: Method DbSet<> not found. StatesRemoveByObjects</value>
|
||||
</data>
|
||||
<data name="ParameterDataTypeError" xml:space="preserve">
|
||||
<value>FreeSql: Parameter data type error {entityTypeFullName}</value>
|
||||
</data>
|
||||
<data name="ParameterError" xml:space="preserve">
|
||||
<value>FreeSql: Parameter error {param}</value>
|
||||
</data>
|
||||
<data name="ParameterError_CannotBeNull" xml:space="preserve">
|
||||
<value>FreeSql: Parameter error {param} cannot be null</value>
|
||||
</data>
|
||||
<data name="ParameterError_IsNot_CollectionProperties" xml:space="preserve">
|
||||
<value>FreeSql: Parameter error {many} is not a collection property</value>
|
||||
</data>
|
||||
<data name="ParameterError_NotFound_CollectionProperties" xml:space="preserve">
|
||||
<value>FreeSql: Parameter error {many} Collection property does not exist</value>
|
||||
</data>
|
||||
<data name="ParameterError_NotFound_Property" xml:space="preserve">
|
||||
<value>FreeSql: Parameter error {one} attribute does not exist</value>
|
||||
</data>
|
||||
<data name="Propagation_Mandatory" xml:space="preserve">
|
||||
<value>FreeSql: Propagation_ Mandatory: With the current transaction, throw an exception if there is no current transaction</value>
|
||||
</data>
|
||||
<data name="Propagation_Never" xml:space="preserve">
|
||||
<value>FreeSql: Propagation_ Never: Perform the operation non-transactionally and throw an exception if the current transaction exists</value>
|
||||
</data>
|
||||
<data name="PropertyOfType_IsNot_OneToManyOrManyToMany" xml:space="preserve">
|
||||
<value>FreeSql: Property {propertyName} of type {tableTypeFullName} is not OneToMany or ManyToMany attribute</value>
|
||||
</data>
|
||||
<data name="SpecialError_BatchAdditionFailed" xml:space="preserve">
|
||||
<value>FreeSql: Special error: Bulk add failed, {dataType} returned data, does not match the number added</value>
|
||||
</data>
|
||||
<data name="SpecialError_UpdateFailedDataNotTracked" xml:space="preserve">
|
||||
<value>FreeSql: Special error: Update failed, data not tracked: {entityString}</value>
|
||||
</data>
|
||||
<data name="TransactionHasBeenStarted" xml:space="preserve">
|
||||
<value>FreeSql: Transaction opened, unit of work cannot be disabled</value>
|
||||
</data>
|
||||
<data name="TypeHasSetProperty_IgnoreAttribute" xml:space="preserve">
|
||||
<value>FreeSql: The {tableTypeFullName} type has set the property {propertyName} Ignore the attribute</value>
|
||||
</data>
|
||||
<data name="UnitOfWorkManager_Construction_CannotBeNull" xml:space="preserve">
|
||||
<value>FreeSql: The {unitOfWorkManager} constructor parameter {fsql} cannot be null</value>
|
||||
</data>
|
||||
<data name="UpdateError_Filter" xml:space="preserve">
|
||||
<value>FreeSql: FreeSql.Repository Update failed because the filter {filterKey}: {filterValueExpression} is set and the updated data does not conform to {entityString}</value>
|
||||
</data>
|
||||
</root>
|
246
FreeSql.DbContext/Properties/DbContextStrings.zh-Hans.resx
Normal file
246
FreeSql.DbContext/Properties/DbContextStrings.zh-Hans.resx
Normal file
@ -0,0 +1,246 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AddFreeDbContextError_CheckConstruction" xml:space="preserve">
|
||||
<value>AddFreeDbContext 发生错误,请检查 {dbContextTypeName} 的构造参数都已正确注入</value>
|
||||
</data>
|
||||
<data name="CannotAdd_AlreadyExistsInStateManagement" xml:space="preserve">
|
||||
<value>不可添加,已存在于状态管理:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAdd_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>不可添加,实体没有主键:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAdd_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>不可添加,未设置主键的值:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAdd_SelfIncreasingHasValue" xml:space="preserve">
|
||||
<value>不可添加,自增属性有值:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAttach_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>不可附加,实体没有主键:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotAttach_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>不可附加,未设置主键的值:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotDelete_DataNotTracked_ShouldQuery" xml:space="preserve">
|
||||
<value>不可删除,数据未被跟踪,应该先查询:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotDelete_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>不可删除,实体没有主键:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotDelete_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>不可删除,未设置主键的值:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotEdit_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>不可进行编辑,实体没有主键:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotUpdate_DataShouldQueryOrAttach" xml:space="preserve">
|
||||
<value>不可更新,数据未被跟踪,应该先查询 或者 Attach:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotUpdate_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>不可更新,实体没有主键:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotUpdate_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>不可更新,未设置主键的值:{entityString}</value>
|
||||
</data>
|
||||
<data name="CannotUpdate_RecordDoesNotExist" xml:space="preserve">
|
||||
<value>不可更新,数据库不存在该记录:{entityString}</value>
|
||||
</data>
|
||||
<data name="ConfigureUseFreeSql" xml:space="preserve">
|
||||
<value>请在 OnConfiguring 或 AddFreeDbContext 中配置 UseFreeSql</value>
|
||||
</data>
|
||||
<data name="DbSetAsType_NotSupport_Object" xml:space="preserve">
|
||||
<value>DbSet.AsType 参数错误,请传入正确的实体类型</value>
|
||||
</data>
|
||||
<data name="EntityType_CannotConvert" xml:space="preserve">
|
||||
<value>实体类型 {EntityTypeName} 无法转换为 {name},无法使用该方法</value>
|
||||
</data>
|
||||
<data name="EntityType_PrimaryKeyError" xml:space="preserve">
|
||||
<value>实体类型 {EntityTypeName} 主键类型不为 {fullName},无法使用该方法</value>
|
||||
</data>
|
||||
<data name="EntityType_PrimaryKeyIsNotOne" xml:space="preserve">
|
||||
<value>实体类型 {EntityTypeName} 主键数量不为 1,无法使用该方法</value>
|
||||
</data>
|
||||
<data name="FailedSetFilter_NotBelongIRpository" xml:space="preserve">
|
||||
<value>FreeSql.Repository 设置过滤器失败,原因是对象不属于 IRepository</value>
|
||||
</data>
|
||||
<data name="Incomparable_EntityHasNo_PrimaryKey" xml:space="preserve">
|
||||
<value>不可比较,实体没有主键:{entityString}</value>
|
||||
</data>
|
||||
<data name="Incomparable_PrimaryKey_NotSet" xml:space="preserve">
|
||||
<value>不可比较,未设置主键的值:{entityString}</value>
|
||||
</data>
|
||||
<data name="InsertError_Filter" xml:space="preserve">
|
||||
<value>FreeSql.Repository Insert 失败,因为设置了过滤器 {filterKey}: {filterValueExpression},插入的数据不符合 {entityString}</value>
|
||||
</data>
|
||||
<data name="ISelectAsType_ParameterError" xml:space="preserve">
|
||||
<value>ISelect.AsType 参数不支持指定为 object</value>
|
||||
</data>
|
||||
<data name="NotFound_Property" xml:space="preserve">
|
||||
<value>{tableTypeFullName} 不存在属性 {propertyName}</value>
|
||||
</data>
|
||||
<data name="NotFoundMethod_StatesRemoveByObjects" xml:space="preserve">
|
||||
<value>找不到方法 DbSet<>.StatesRemoveByObjects</value>
|
||||
</data>
|
||||
<data name="ParameterDataTypeError" xml:space="preserve">
|
||||
<value>参数 data 类型错误 {entityTypeFullName} </value>
|
||||
</data>
|
||||
<data name="ParameterError" xml:space="preserve">
|
||||
<value>参数错误 {param}</value>
|
||||
</data>
|
||||
<data name="ParameterError_CannotBeNull" xml:space="preserve">
|
||||
<value>参数错误 {param} 不能为 null</value>
|
||||
</data>
|
||||
<data name="ParameterError_IsNot_CollectionProperties" xml:space="preserve">
|
||||
<value>参数错误 {many} 不是集合属性</value>
|
||||
</data>
|
||||
<data name="ParameterError_NotFound_CollectionProperties" xml:space="preserve">
|
||||
<value>参数错误 {many} 集合属性不存在</value>
|
||||
</data>
|
||||
<data name="ParameterError_NotFound_Property" xml:space="preserve">
|
||||
<value>参数错误 {one} 属性不存在</value>
|
||||
</data>
|
||||
<data name="Propagation_Mandatory" xml:space="preserve">
|
||||
<value>Propagation_Mandatory: 使用当前事务,如果没有当前事务,就抛出异常</value>
|
||||
</data>
|
||||
<data name="Propagation_Never" xml:space="preserve">
|
||||
<value>Propagation_Never: 以非事务方式执行操作,如果当前事务存在则抛出异常</value>
|
||||
</data>
|
||||
<data name="PropertyOfType_IsNot_OneToManyOrManyToMany" xml:space="preserve">
|
||||
<value>{tableTypeFullName} 类型的属性 {propertyName} 不是 OneToMany 或 ManyToMany 特性</value>
|
||||
</data>
|
||||
<data name="SpecialError_BatchAdditionFailed" xml:space="preserve">
|
||||
<value>特别错误:批量添加失败,{dataType} 的返回数据,与添加的数目不匹配</value>
|
||||
</data>
|
||||
<data name="SpecialError_UpdateFailedDataNotTracked" xml:space="preserve">
|
||||
<value>特别错误:更新失败,数据未被跟踪:{entityString}</value>
|
||||
</data>
|
||||
<data name="TransactionHasBeenStarted" xml:space="preserve">
|
||||
<value>已开启事务,不能禁用工作单元</value>
|
||||
</data>
|
||||
<data name="TypeHasSetProperty_IgnoreAttribute" xml:space="preserve">
|
||||
<value>{tableTypeFullName} 类型已设置属性 {propertyName} 忽略特性</value>
|
||||
</data>
|
||||
<data name="UnitOfWorkManager_Construction_CannotBeNull" xml:space="preserve">
|
||||
<value>{unitOfWorkManager} 构造参数 {fsql} 不能为 null</value>
|
||||
</data>
|
||||
<data name="UpdateError_Filter" xml:space="preserve">
|
||||
<value>FreeSql.Repository Update 失败,因为设置了过滤器 {filterKey}: {filterValueExpression},更新的数据不符合{entityString}</value>
|
||||
</data>
|
||||
</root>
|
@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
internal class RepositoryDbContext : DbContext
|
||||
{
|
||||
|
||||
protected IBaseRepository _repo;
|
||||
public RepositoryDbContext(IFreeSql orm, IBaseRepository repo) : base()
|
||||
{
|
||||
_ormScoped = DbContextScopedFreeSql.Create(orm, () => this, () => repo.UnitOfWork);
|
||||
_isUseUnitOfWork = false;
|
||||
UnitOfWork = repo.UnitOfWork;
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
static ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>> _dicGetRepositoryDbField = new ConcurrentDictionary<Type, ConcurrentDictionary<string, FieldInfo>>();
|
||||
static FieldInfo GetRepositoryDbField(Type type, string fieldName) => _dicGetRepositoryDbField.GetOrAdd(type, tp => new ConcurrentDictionary<string, FieldInfo>()).GetOrAdd(fieldName, fn =>
|
||||
typeof(BaseRepository<,>).MakeGenericType(type, typeof(int)).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic));
|
||||
public override IDbSet Set(Type entityType)
|
||||
{
|
||||
if (_dicSet.ContainsKey(entityType)) return _dicSet[entityType];
|
||||
|
||||
var tb = OrmOriginal.CodeFirst.GetTableByEntity(entityType);
|
||||
if (tb == null) return null;
|
||||
|
||||
object repo = _repo;
|
||||
if (entityType != _repo.EntityType)
|
||||
{
|
||||
repo = Activator.CreateInstance(typeof(DefaultRepository<,>).MakeGenericType(entityType, typeof(int)), _repo.Orm);
|
||||
(repo as IBaseRepository).UnitOfWork = _repo.UnitOfWork;
|
||||
GetRepositoryDbField(entityType, "_dbPriv").SetValue(repo, this);
|
||||
GetRepositoryDbField(entityType, "_asTablePriv").SetValue(repo,
|
||||
_repo.GetType().GetField("_asTablePriv", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(_repo));
|
||||
//GetRepositoryDbField(_repo.EntityType, "_asTablePriv").GetValue(_repo));
|
||||
|
||||
if (typeof(IBaseRepository<>).MakeGenericType(_repo.EntityType).IsAssignableFrom(_repo.GetType()))
|
||||
typeof(RepositoryDbContext).GetMethod("SetRepositoryDataFilter").MakeGenericMethod(_repo.EntityType)
|
||||
.Invoke(null, new object[] { repo, _repo });
|
||||
}
|
||||
|
||||
var sd = Activator.CreateInstance(typeof(RepositoryDbSet<>).MakeGenericType(entityType), repo) as IDbSet;
|
||||
_listSet.Add(sd);
|
||||
if (entityType != typeof(object)) _dicSet.Add(entityType, sd);
|
||||
return sd;
|
||||
}
|
||||
|
||||
public static void SetRepositoryDataFilter<TEntity>(object repo, IBaseRepository<TEntity> baseRepo) where TEntity : class
|
||||
{
|
||||
var filter = baseRepo.DataFilter as DataFilter<TEntity>;
|
||||
DataFilterUtil.SetRepositoryDataFilter(repo, fl =>
|
||||
{
|
||||
foreach (var f in filter._filters)
|
||||
fl.Apply<TEntity>(f.Key, f.Value.Expression);
|
||||
});
|
||||
}
|
||||
|
||||
int SaveChangesSuccess()
|
||||
{
|
||||
int ret;
|
||||
try
|
||||
{
|
||||
if (UnitOfWork?.EntityChangeReport != null)
|
||||
{
|
||||
UnitOfWork.EntityChangeReport.Report.AddRange(_entityChangeReport);
|
||||
if (UnitOfWork.EntityChangeReport.OnChange == null) UnitOfWork.EntityChangeReport.OnChange = Options.OnEntityChange;
|
||||
} else
|
||||
EmitOnEntityChange(_entityChangeReport);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_entityChangeReport.Clear();
|
||||
ret = _affrows;
|
||||
_affrows = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
public override int SaveChanges()
|
||||
{
|
||||
FlushCommand();
|
||||
return SaveChangesSuccess();
|
||||
}
|
||||
#if net40
|
||||
#else
|
||||
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await FlushCommandAsync(cancellationToken);
|
||||
return SaveChangesSuccess();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
93
FreeSql.DbContext/Repository/ContextSet/RepositoryDbSet.cs
Normal file
93
FreeSql.DbContext/Repository/ContextSet/RepositoryDbSet.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
internal class RepositoryDbSet<TEntity> : DbSet<TEntity> where TEntity : class
|
||||
{
|
||||
|
||||
protected BaseRepository<TEntity> _repo;
|
||||
public RepositoryDbSet(BaseRepository<TEntity> repo)
|
||||
{
|
||||
_db = repo._db;
|
||||
_uow = repo.UnitOfWork;
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
IUnitOfWork _uowPriv;
|
||||
internal override IUnitOfWork _uow
|
||||
{
|
||||
get => _uowPriv;
|
||||
set
|
||||
{
|
||||
_uowPriv = value;
|
||||
foreach (var dbset in _dicDbSetObjects.Values) //配合 UnitOfWorkManager
|
||||
dbset._uow = _uowPriv;
|
||||
}
|
||||
}
|
||||
|
||||
protected override ISelect<TEntity> OrmSelect(object dywhere)
|
||||
{
|
||||
var select = base.OrmSelect(dywhere);
|
||||
if (_repo._asTablePriv != null) select.AsTable(_repo._asTablePriv);
|
||||
var filters = (_repo.DataFilter as DataFilter<TEntity>)._filters;
|
||||
foreach (var filter in filters.Where(a => a.Value.IsEnabled == true)) select.Where(filter.Value.Expression);
|
||||
var disableFilter = filters.Where(a => a.Value.IsEnabled == false).Select(a => a.Key).ToList();
|
||||
disableFilter.AddRange((_repo.DataFilter as DataFilter<TEntity>)._filtersByOrm.Where(a => a.Value.IsEnabled == false).Select(a => a.Key));
|
||||
if (disableFilter.Any()) select.DisableGlobalFilter(disableFilter.ToArray());
|
||||
return select;
|
||||
}
|
||||
internal ISelect<TEntity> OrmSelectInternal(object dywhere) => OrmSelect(dywhere);
|
||||
protected override IUpdate<TEntity> OrmUpdate(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
var update = base.OrmUpdate(entitys);
|
||||
if (_repo._asTablePriv != null) update.AsTable(old => _repo._asTablePriv(_entityType, old));
|
||||
var filters = (_repo.DataFilter as DataFilter<TEntity>)._filters;
|
||||
foreach (var filter in filters.Where(a => a.Value.IsEnabled == true)) update.Where(filter.Value.Expression);
|
||||
var disableFilter = filters.Where(a => a.Value.IsEnabled == false).Select(a => a.Key).ToList();
|
||||
disableFilter.AddRange((_repo.DataFilter as DataFilter<TEntity>)._filtersByOrm.Where(a => a.Value.IsEnabled == false).Select(a => a.Key));
|
||||
if (disableFilter.Any()) update.DisableGlobalFilter(disableFilter.ToArray());
|
||||
return update;
|
||||
}
|
||||
internal IUpdate<TEntity> OrmUpdateInternal(IEnumerable<TEntity> entitys) => OrmUpdate(entitys);
|
||||
protected override IDelete<TEntity> OrmDelete(object dywhere)
|
||||
{
|
||||
var delete = base.OrmDelete(dywhere);
|
||||
if (_repo._asTablePriv != null) delete.AsTable(old => _repo._asTablePriv(_entityType, old));
|
||||
var filters = (_repo.DataFilter as DataFilter<TEntity>)._filters;
|
||||
foreach (var filter in filters.Where(a => a.Value.IsEnabled == true)) delete.Where(filter.Value.Expression);
|
||||
var disableFilter = filters.Where(a => a.Value.IsEnabled == false).Select(a => a.Key).ToList();
|
||||
disableFilter.AddRange((_repo.DataFilter as DataFilter<TEntity>)._filtersByOrm.Where(a => a.Value.IsEnabled == false).Select(a => a.Key));
|
||||
if (disableFilter.Any()) delete.DisableGlobalFilter(disableFilter.ToArray());
|
||||
return delete;
|
||||
}
|
||||
internal IDelete<TEntity> OrmDeleteInternal(object dywhere) => OrmDelete(dywhere);
|
||||
|
||||
protected override IDelete<object> OrmDeleteAsType(Type entityType)
|
||||
{
|
||||
var delete = base.OrmDeleteAsType(entityType);
|
||||
if (_repo._asTablePriv != null) delete.AsTable(old => _repo._asTablePriv(_entityType, old));
|
||||
return delete;
|
||||
}
|
||||
|
||||
protected override IInsert<TEntity> OrmInsert(TEntity entity) => OrmInsert(new[] { entity });
|
||||
protected override IInsert<TEntity> OrmInsert(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
var insert = base.OrmInsert(entitys);
|
||||
if (_repo._asTablePriv != null) insert.AsTable(old => _repo._asTablePriv(_entityType, old));
|
||||
var filters = (_repo.DataFilter as DataFilter<TEntity>)._filters.Where(a => a.Value.IsEnabled == true);
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
if (entitys != null)
|
||||
foreach (var entity in entitys)
|
||||
if (filter.Value.ExpressionDelegate?.Invoke(entity) == false)
|
||||
throw new Exception(DbContextStrings.InsertError_Filter(filter.Key, filter.Value.Expression, _db.OrmOriginal.GetEntityString(_entityType, entity)));
|
||||
}
|
||||
return insert;
|
||||
}
|
||||
internal IInsert<TEntity> OrmInsertInternal(TEntity entity) => OrmInsert(entity);
|
||||
internal IInsert<TEntity> OrmInsertInternal(IEnumerable<TEntity> entitys) => OrmInsert(entitys);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
|
||||
public interface IRepositoryUnitOfWork : IUnitOfWork
|
||||
{
|
||||
/// <summary>
|
||||
/// 在工作单元内创建默认仓库类,工作单元下的仓储操作具有事务特点
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <param name="filter">数据过滤 + 验证</param>
|
||||
/// <returns></returns>
|
||||
IBaseRepository<TEntity, TKey> GetRepository<TEntity, TKey>(Expression<Func<TEntity, bool>> filter = null) where TEntity : class;
|
||||
|
||||
/// <summary>
|
||||
/// 在工作单元内创建联合主键的仓储类,工作单元下的仓储操作具有事务特点
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="filter">数据过滤 + 验证</param>
|
||||
/// <returns></returns>
|
||||
IBaseRepository<TEntity> GetRepository<TEntity>(Expression<Func<TEntity, bool>> filter = null) where TEntity : class;
|
||||
|
||||
/// <summary>
|
||||
/// 在工作单元内创建仓库类,工作单元下的仓储操作具有事务特点
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="filter">数据过滤 + 验证</param>
|
||||
/// <param name="asTable">分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository</param>
|
||||
/// <returns></returns>
|
||||
IBaseRepository<TEntity, Guid> GetGuidRepository<TEntity>(Expression<Func<TEntity, bool>> filter = null, Func<string, string> asTable = null) where TEntity : class;
|
||||
}
|
||||
|
||||
class RepositoryUnitOfWork : UnitOfWork, IRepositoryUnitOfWork
|
||||
{
|
||||
|
||||
public RepositoryUnitOfWork(IFreeSql fsql) : base(fsql)
|
||||
{
|
||||
}
|
||||
|
||||
public IBaseRepository<TEntity, Guid> GetGuidRepository<TEntity>(Expression<Func<TEntity, bool>> filter = null, Func<string, string> asTable = null) where TEntity : class
|
||||
{
|
||||
var repo = new GuidRepository<TEntity>(_fsql, filter, asTable);
|
||||
repo.UnitOfWork = this;
|
||||
return repo;
|
||||
}
|
||||
|
||||
public IBaseRepository<TEntity, TKey> GetRepository<TEntity, TKey>(Expression<Func<TEntity, bool>> filter = null) where TEntity : class
|
||||
{
|
||||
var repo = new DefaultRepository<TEntity, TKey>(_fsql, filter);
|
||||
repo.UnitOfWork = this;
|
||||
return repo;
|
||||
}
|
||||
|
||||
public IBaseRepository<TEntity> GetRepository<TEntity>(Expression<Func<TEntity, bool>> filter = null) where TEntity : class
|
||||
{
|
||||
var repo = new DefaultRepository<TEntity, int>(_fsql, filter);
|
||||
repo.UnitOfWork = this;
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
}
|
278
FreeSql.DbContext/Repository/DataFilter/DataFilter.cs
Normal file
278
FreeSql.DbContext/Repository/DataFilter/DataFilter.cs
Normal file
@ -0,0 +1,278 @@
|
||||
using FreeSql.Internal;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public interface IDataFilter<TEntity> : IDisposable where TEntity : class
|
||||
{
|
||||
|
||||
IDataFilter<TEntity> Apply(string filterName, Expression<Func<TEntity, bool>> filterAndValidateExp);
|
||||
|
||||
/// <summary>
|
||||
/// 开启过滤器,若使用 using 则使用完后,恢复为原有状态
|
||||
/// </summary>
|
||||
/// <param name="filterName">过滤器名称</param>
|
||||
/// <returns></returns>
|
||||
IDisposable Enable(params string[] filterName);
|
||||
/// <summary>
|
||||
/// 开启所有过滤器,若使用 using 则使用完后,恢复为原有状态
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IDisposable EnableAll();
|
||||
|
||||
/// <summary>
|
||||
/// 禁用过滤器,若使用 using 则使用完后,恢复为原有状态
|
||||
/// </summary>
|
||||
/// <param name="filterName"></param>
|
||||
/// <returns></returns>
|
||||
IDisposable Disable(params string[] filterName);
|
||||
/// <summary>
|
||||
/// 禁用所有过滤器,若使用 using 则使用完后,恢复为原有状态
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IDisposable DisableAll();
|
||||
|
||||
bool IsEnabled(string filterName);
|
||||
}
|
||||
|
||||
internal class DataFilter<TEntity> : IDataFilter<TEntity> where TEntity : class
|
||||
{
|
||||
|
||||
internal class FilterItem
|
||||
{
|
||||
public Expression<Func<TEntity, bool>> Expression { get; set; }
|
||||
Func<TEntity, bool> _expressionDelegate;
|
||||
public Func<TEntity, bool> ExpressionDelegate => _expressionDelegate ?? (_expressionDelegate = Expression?.Compile());
|
||||
public bool IsEnabled { get; set; }
|
||||
}
|
||||
internal class FilterItemByOrm
|
||||
{
|
||||
public GlobalFilter.Item Filter { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
}
|
||||
|
||||
internal ConcurrentDictionary<string, FilterItem> _filters = new ConcurrentDictionary<string, FilterItem>(StringComparer.CurrentCultureIgnoreCase);
|
||||
internal ConcurrentDictionary<string, FilterItemByOrm> _filtersByOrm = new ConcurrentDictionary<string, FilterItemByOrm>(StringComparer.CurrentCultureIgnoreCase);
|
||||
public IDataFilter<TEntity> Apply(string filterName, Expression<Func<TEntity, bool>> filterAndValidateExp)
|
||||
{
|
||||
|
||||
if (filterName == null)
|
||||
throw new ArgumentNullException(nameof(filterName));
|
||||
if (filterAndValidateExp == null) return this;
|
||||
|
||||
var filterItem = new FilterItem { Expression = filterAndValidateExp, IsEnabled = true };
|
||||
_filters.AddOrUpdate(filterName, filterItem, (k, v) => filterItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDisposable Disable(params string[] filterName)
|
||||
{
|
||||
if (filterName == null || filterName.Any() == false) return new UsingAny(() => { });
|
||||
|
||||
List<string> restore = new List<string>();
|
||||
List<string> restoreByOrm = new List<string>();
|
||||
foreach (var name in filterName)
|
||||
{
|
||||
if (_filters.TryGetValue(name, out var tryfi))
|
||||
{
|
||||
if (tryfi.IsEnabled)
|
||||
{
|
||||
restore.Add(name);
|
||||
tryfi.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
if (_filtersByOrm.TryGetValue(name, out var tryfiByOrm))
|
||||
{
|
||||
if (tryfiByOrm.IsEnabled)
|
||||
{
|
||||
restoreByOrm.Add(name);
|
||||
tryfiByOrm.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new UsingAny(() =>
|
||||
{
|
||||
restore.ForEach(name =>
|
||||
{
|
||||
if (_filters.TryGetValue(name, out var tryfi) && tryfi.IsEnabled == false)
|
||||
tryfi.IsEnabled = true;
|
||||
});
|
||||
restoreByOrm.ForEach(name =>
|
||||
{
|
||||
if (_filtersByOrm.TryGetValue(name, out var tryfiByOrm) && tryfiByOrm.IsEnabled == false)
|
||||
tryfiByOrm.IsEnabled = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
public IDisposable DisableAll()
|
||||
{
|
||||
List<string> restore = new List<string>();
|
||||
List<string> restoreByOrm = new List<string>();
|
||||
foreach (var val in _filters)
|
||||
{
|
||||
if (val.Value.IsEnabled)
|
||||
{
|
||||
restore.Add(val.Key);
|
||||
val.Value.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
foreach (var val in _filtersByOrm)
|
||||
{
|
||||
if (val.Value.IsEnabled)
|
||||
{
|
||||
restoreByOrm.Add(val.Key);
|
||||
val.Value.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
return new UsingAny(() =>
|
||||
{
|
||||
restore.ForEach(name =>
|
||||
{
|
||||
if (_filters.TryGetValue(name, out var tryfi) && tryfi.IsEnabled == false)
|
||||
tryfi.IsEnabled = true;
|
||||
});
|
||||
restoreByOrm.ForEach(name =>
|
||||
{
|
||||
if (_filtersByOrm.TryGetValue(name, out var tryfiByOrm) && tryfiByOrm.IsEnabled == false)
|
||||
tryfiByOrm.IsEnabled = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
class UsingAny : IDisposable
|
||||
{
|
||||
Action _ondis;
|
||||
public UsingAny(Action ondis)
|
||||
{
|
||||
_ondis = ondis;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
_ondis?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Enable(params string[] filterName)
|
||||
{
|
||||
if (filterName == null || filterName.Any() == false) return new UsingAny(() => { });
|
||||
|
||||
List<string> restore = new List<string>();
|
||||
List<string> restoreByOrm = new List<string>();
|
||||
foreach (var name in filterName)
|
||||
{
|
||||
if (_filters.TryGetValue(name, out var tryfi))
|
||||
{
|
||||
if (tryfi.IsEnabled == false)
|
||||
{
|
||||
restore.Add(name);
|
||||
tryfi.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
if (_filtersByOrm.TryGetValue(name, out var tryfiByOrm))
|
||||
{
|
||||
if (tryfiByOrm.IsEnabled == false)
|
||||
{
|
||||
restoreByOrm.Add(name);
|
||||
tryfiByOrm.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new UsingAny(() =>
|
||||
{
|
||||
restore.ForEach(name =>
|
||||
{
|
||||
if (_filters.TryGetValue(name, out var tryfi) && tryfi.IsEnabled == true)
|
||||
tryfi.IsEnabled = false;
|
||||
});
|
||||
restoreByOrm.ForEach(name =>
|
||||
{
|
||||
if (_filtersByOrm.TryGetValue(name, out var tryfiByOrm) && tryfiByOrm.IsEnabled == true)
|
||||
tryfiByOrm.IsEnabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
public IDisposable EnableAll()
|
||||
{
|
||||
List<string> restore = new List<string>();
|
||||
List<string> restoreByOrm = new List<string>();
|
||||
foreach (var val in _filters)
|
||||
{
|
||||
if (val.Value.IsEnabled == false)
|
||||
{
|
||||
restore.Add(val.Key);
|
||||
val.Value.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
foreach (var val in _filtersByOrm)
|
||||
{
|
||||
if (val.Value.IsEnabled == false)
|
||||
{
|
||||
restoreByOrm.Add(val.Key);
|
||||
val.Value.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
return new UsingAny(() =>
|
||||
{
|
||||
restore.ForEach(name =>
|
||||
{
|
||||
if (_filters.TryGetValue(name, out var tryfi) && tryfi.IsEnabled == true)
|
||||
tryfi.IsEnabled = false;
|
||||
});
|
||||
restoreByOrm.ForEach(name =>
|
||||
{
|
||||
if (_filtersByOrm.TryGetValue(name, out var tryfiByOrm) && tryfiByOrm.IsEnabled == true)
|
||||
tryfiByOrm.IsEnabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsEnabled(string filterName)
|
||||
{
|
||||
if (filterName == null) return false;
|
||||
return _filters.TryGetValue(filterName, out var tryfi) ? tryfi.IsEnabled :
|
||||
_filtersByOrm.TryGetValue(filterName, out var tryfiByOrm) ? tryfiByOrm.IsEnabled : false;
|
||||
}
|
||||
|
||||
~DataFilter() => this.Dispose();
|
||||
public void Dispose()
|
||||
{
|
||||
_filters.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public class FluentDataFilter : IDisposable
|
||||
{
|
||||
internal class FilterInfo
|
||||
{
|
||||
public Type type { get; }
|
||||
public string name { get; }
|
||||
public LambdaExpression exp { get; }
|
||||
public FilterInfo(Type type, string name, LambdaExpression exp)
|
||||
{
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.exp = exp;
|
||||
}
|
||||
}
|
||||
internal List<FilterInfo> _filters = new List<FilterInfo>();
|
||||
|
||||
public FluentDataFilter Apply<TEntity>(string filterName, Expression<Func<TEntity, bool>> filterAndValidateExp) where TEntity : class
|
||||
{
|
||||
if (filterName == null)
|
||||
throw new ArgumentNullException(nameof(filterName));
|
||||
if (filterAndValidateExp == null) return this;
|
||||
|
||||
_filters.Add(new FilterInfo(typeof(TEntity), filterName, filterAndValidateExp));
|
||||
return this;
|
||||
}
|
||||
|
||||
~FluentDataFilter() => this.Dispose();
|
||||
public void Dispose()
|
||||
{
|
||||
_filters.Clear();
|
||||
}
|
||||
}
|
||||
}
|
104
FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs
Normal file
104
FreeSql.DbContext/Repository/DataFilter/DataFilterUtil.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
|
||||
internal class DataFilterUtil
|
||||
{
|
||||
|
||||
internal static Action<FluentDataFilter> _globalDataFilter;
|
||||
|
||||
static ConcurrentDictionary<Type, Delegate> _dicSetRepositoryDataFilterApplyDataFilterFunc = new ConcurrentDictionary<Type, Delegate>();
|
||||
static ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>> _dicSetRepositoryDataFilterConvertFilterNotExists = new ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>>();
|
||||
internal static void SetRepositoryDataFilter(object repos, Action<FluentDataFilter> scopedDataFilter)
|
||||
{
|
||||
if (scopedDataFilter != null)
|
||||
{
|
||||
SetRepositoryDataFilter(repos, null);
|
||||
}
|
||||
if (scopedDataFilter == null)
|
||||
{
|
||||
scopedDataFilter = _globalDataFilter;
|
||||
}
|
||||
if (scopedDataFilter == null) return;
|
||||
using (var globalFilter = new FluentDataFilter())
|
||||
{
|
||||
scopedDataFilter(globalFilter);
|
||||
|
||||
var type = repos.GetType();
|
||||
Type entityType = (repos as IBaseRepository).EntityType;
|
||||
if (entityType == null) throw new Exception(DbContextStrings.FailedSetFilter_NotBelongIRpository);
|
||||
|
||||
var notExists = _dicSetRepositoryDataFilterConvertFilterNotExists.GetOrAdd(type, t => new ConcurrentDictionary<string, bool>());
|
||||
var newFilter = new Dictionary<string, LambdaExpression>();
|
||||
foreach (var gf in globalFilter._filters)
|
||||
{
|
||||
if (notExists.ContainsKey(gf.name)) continue;
|
||||
|
||||
LambdaExpression newExp = null;
|
||||
var filterParameter1 = Expression.Parameter(entityType, gf.exp.Parameters[0].Name);
|
||||
try
|
||||
{
|
||||
newExp = Expression.Lambda(
|
||||
typeof(Func<,>).MakeGenericType(entityType, typeof(bool)),
|
||||
new ReplaceVisitor().Modify(gf.exp.Body, filterParameter1),
|
||||
filterParameter1
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
notExists.TryAdd(gf.name, true); //防止第二次错误
|
||||
continue;
|
||||
}
|
||||
newFilter.Add(gf.name, newExp);
|
||||
}
|
||||
if (newFilter.Any() == false) return;
|
||||
|
||||
var del = _dicSetRepositoryDataFilterApplyDataFilterFunc.GetOrAdd(type, t =>
|
||||
{
|
||||
var reposParameter = Expression.Parameter(type);
|
||||
var nameParameter = Expression.Parameter(typeof(string));
|
||||
var expressionParameter = Expression.Parameter(
|
||||
typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(entityType, typeof(bool)))
|
||||
);
|
||||
return Expression.Lambda(
|
||||
Expression.Block(
|
||||
Expression.Call(reposParameter, type.GetMethod("ApplyDataFilter", BindingFlags.Instance | BindingFlags.NonPublic), nameParameter, expressionParameter)
|
||||
),
|
||||
new[] {
|
||||
reposParameter, nameParameter, expressionParameter
|
||||
}
|
||||
).Compile();
|
||||
});
|
||||
foreach (var nf in newFilter)
|
||||
{
|
||||
del.DynamicInvoke(repos, nf.Key, nf.Value);
|
||||
}
|
||||
newFilter.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceVisitor : ExpressionVisitor
|
||||
{
|
||||
private ParameterExpression parameter;
|
||||
|
||||
public Expression Modify(Expression expression, ParameterExpression parameter)
|
||||
{
|
||||
this.parameter = parameter;
|
||||
return Visit(expression);
|
||||
}
|
||||
|
||||
protected override Expression VisitMember(MemberExpression node)
|
||||
{
|
||||
if (node.Expression?.NodeType == ExpressionType.Parameter)
|
||||
return Expression.Property(parameter, node.Member.Name);
|
||||
return base.VisitMember(node);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
#if netcoreapp
|
||||
using FreeSql;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class FreeSqlRepositoryDependencyInjection
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 批量注入 Repository,可以参考代码自行调整
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="globalDataFilter"></param>
|
||||
/// <param name="assemblies"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddFreeRepository(this IServiceCollection services, Action<FluentDataFilter> globalDataFilter = null, params Assembly[] assemblies)
|
||||
{
|
||||
if (globalDataFilter != null)
|
||||
{
|
||||
DataFilterUtil._globalDataFilter = globalDataFilter;
|
||||
//如果看到了这里的代码,想自己调整,但因为 _globalDataFilter 是内部属性,无法修改?
|
||||
//请考虑改用 fsql.GlobalFilter.Apply
|
||||
}
|
||||
|
||||
services.AddScoped(typeof(IBaseRepository<>), typeof(GuidRepository<>));
|
||||
services.AddScoped(typeof(BaseRepository<>), typeof(GuidRepository<>));
|
||||
|
||||
services.AddScoped(typeof(IBaseRepository<,>), typeof(DefaultRepository<,>));
|
||||
services.AddScoped(typeof(BaseRepository<,>), typeof(DefaultRepository<,>));
|
||||
|
||||
if (assemblies?.Any() == true)
|
||||
foreach (var asse in assemblies)
|
||||
foreach (var repo in asse.GetTypes().Where(a => a.IsAbstract == false && typeof(IBaseRepository).IsAssignableFrom(a)))
|
||||
services.AddScoped(repo);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,56 @@
|
||||
using FreeSql;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
partial class FreeSqlDbContextExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 返回默认仓库类
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <param name="that"></param>
|
||||
/// <param name="filter">数据过滤 + 验证</param>
|
||||
/// <returns></returns>
|
||||
public static IBaseRepository<TEntity, TKey> GetRepository<TEntity, TKey>(this IFreeSql that, Expression<Func<TEntity, bool>> filter = null) where TEntity : class
|
||||
{
|
||||
return new DefaultRepository<TEntity, TKey>(that, filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回默认仓库类,适用联合主键的仓储类
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="that"></param>
|
||||
/// <param name="filter">数据过滤 + 验证</param>
|
||||
/// <returns></returns>
|
||||
public static IBaseRepository<TEntity> GetRepository<TEntity>(this IFreeSql that, Expression<Func<TEntity, bool>> filter = null) where TEntity : class
|
||||
{
|
||||
return new DefaultRepository<TEntity, int>(that, filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回仓库类
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="that"></param>
|
||||
/// <param name="filter">数据过滤 + 验证</param>
|
||||
/// <param name="asTable">分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository</param>
|
||||
/// <returns></returns>
|
||||
public static IBaseRepository<TEntity, Guid> GetGuidRepository<TEntity>(this IFreeSql that, Expression<Func<TEntity, bool>> filter = null, Func<string, string> asTable = null) where TEntity : class
|
||||
{
|
||||
return new GuidRepository<TEntity>(that, filter, asTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建基于仓储功能的工作单元,务必使用 using 包含使用
|
||||
/// </summary>
|
||||
/// <param name="that"></param>
|
||||
/// <returns></returns>
|
||||
public static IRepositoryUnitOfWork CreateUnitOfWork(this IFreeSql that)
|
||||
{
|
||||
return new RepositoryUnitOfWork(that);
|
||||
}
|
||||
}
|
220
FreeSql.DbContext/Repository/Repository/BaseRepository.cs
Normal file
220
FreeSql.DbContext/Repository/Repository/BaseRepository.cs
Normal file
@ -0,0 +1,220 @@
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public abstract partial class BaseRepository<TEntity> : IBaseRepository<TEntity>
|
||||
where TEntity : class
|
||||
{
|
||||
|
||||
internal RepositoryDbContext _dbPriv; //这个不能私有化,有地方需要反射获取它
|
||||
internal RepositoryDbContext _db => _dbPriv ?? (_dbPriv = new RepositoryDbContext(OrmOriginal, this));
|
||||
internal RepositoryDbSet<TEntity> _dbsetPriv;
|
||||
internal RepositoryDbSet<TEntity> _dbset => _dbsetPriv ?? (_dbsetPriv = _db.Set<TEntity>() as RepositoryDbSet<TEntity>);
|
||||
|
||||
public IDataFilter<TEntity> DataFilter { get; } = new DataFilter<TEntity>();
|
||||
internal Func<Type, string, string> _asTablePriv;
|
||||
|
||||
protected void ApplyDataFilter(string name, Expression<Func<TEntity, bool>> exp) => DataFilter.Apply(name, exp);
|
||||
|
||||
protected BaseRepository(IFreeSql fsql, Expression<Func<TEntity, bool>> filter, Func<string, string> asTable = null)
|
||||
{
|
||||
_ormScoped = DbContextScopedFreeSql.Create(fsql, () => _db, () => UnitOfWork);
|
||||
DataFilterUtil.SetRepositoryDataFilter(this, null);
|
||||
DataFilter.Apply("", filter);
|
||||
AsTable(asTable);
|
||||
|
||||
fsql?.GlobalFilter?.GetAllFilters().ForEach(gf =>
|
||||
{
|
||||
(DataFilter as DataFilter<TEntity>)._filtersByOrm.TryAdd(gf.Name, new DataFilter<TEntity>.FilterItemByOrm
|
||||
{
|
||||
Filter = gf,
|
||||
IsEnabled = true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
~BaseRepository() => this.Dispose();
|
||||
int _disposeCounter;
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Increment(ref _disposeCounter) != 1) return;
|
||||
try
|
||||
{
|
||||
_dbsetPriv?.Dispose();
|
||||
_dbPriv?.Dispose();
|
||||
this.DataFilter?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
public Type EntityType => _dbsetPriv?.EntityType ?? typeof(TEntity);
|
||||
public void AsType(Type entityType) => _dbset.AsType(entityType);
|
||||
public void AsTable(Func<string, string> rule)
|
||||
{
|
||||
if (rule == null)
|
||||
{
|
||||
_asTablePriv = null;
|
||||
return;
|
||||
}
|
||||
_asTablePriv = (a, b) => a == EntityType ? rule(b) : null;
|
||||
}
|
||||
public void AsTable(Func<Type, string, string> rule)
|
||||
{
|
||||
_asTablePriv = rule;
|
||||
}
|
||||
public DbContextOptions DbContextOptions { get => _db.Options; set => _db.Options = value; }
|
||||
|
||||
internal DbContextScopedFreeSql _ormScoped;
|
||||
internal IFreeSql OrmOriginal => _ormScoped?._originalFsql;
|
||||
public IFreeSql Orm => _ormScoped;
|
||||
IUnitOfWork _unitOfWork;
|
||||
public IUnitOfWork UnitOfWork
|
||||
{
|
||||
set
|
||||
{
|
||||
_unitOfWork = value;
|
||||
if (_dbsetPriv != null) _dbsetPriv._uow = _unitOfWork; //防止 dbset 对象已经存在,再次设置 UnitOfWork 无法生效,所以作此判断重新设置
|
||||
if (_dbPriv != null) _dbPriv.UnitOfWork = _unitOfWork;
|
||||
}
|
||||
get => _unitOfWork;
|
||||
}
|
||||
public IUpdate<TEntity> UpdateDiy => _dbset.OrmUpdateInternal(null);
|
||||
|
||||
public virtual ISelect<TEntity> Select => _dbset.OrmSelectInternal(null);
|
||||
public ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => Select.Where(exp);
|
||||
public ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => Select.WhereIf(condition, exp);
|
||||
|
||||
public virtual int Delete(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
var delete = _dbset.OrmDeleteInternal(null).Where(predicate);
|
||||
var sql = delete.ToSql();
|
||||
var affrows = delete.ExecuteAffrows();
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = EntityType, Object = sql, Type = DbContext.EntityChangeType.SqlRaw });
|
||||
return affrows;
|
||||
}
|
||||
public virtual int Delete(TEntity entity)
|
||||
{
|
||||
_dbset.Remove(entity);
|
||||
return _db.SaveChanges();
|
||||
}
|
||||
public virtual int Delete(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
_dbset.RemoveRange(entitys);
|
||||
return _db.SaveChanges();
|
||||
}
|
||||
public virtual List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
var list = _dbset.RemoveCascadeByDatabase(predicate);
|
||||
var affrows = _db.SaveChanges();
|
||||
return list;
|
||||
}
|
||||
|
||||
public virtual TEntity Insert(TEntity entity)
|
||||
{
|
||||
_dbset.Add(entity);
|
||||
_db.SaveChanges();
|
||||
return entity;
|
||||
}
|
||||
public virtual List<TEntity> Insert(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
_dbset.AddRange(entitys);
|
||||
_db.SaveChanges();
|
||||
return entitys.ToList();
|
||||
}
|
||||
|
||||
public virtual int Update(TEntity entity)
|
||||
{
|
||||
_dbset.Update(entity);
|
||||
return _db.SaveChanges();
|
||||
}
|
||||
public virtual int Update(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
_dbset.UpdateRange(entitys);
|
||||
return _db.SaveChanges();
|
||||
}
|
||||
|
||||
public void Attach(TEntity data) => _dbset.Attach(data);
|
||||
public void Attach(IEnumerable<TEntity> data) => _dbset.AttachRange(data);
|
||||
public IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data)
|
||||
{
|
||||
_dbset.AttachOnlyPrimary(data);
|
||||
return this;
|
||||
}
|
||||
public void FlushState() => _dbset.FlushState();
|
||||
public Dictionary<string, object[]> CompareState(TEntity newdata) => _dbset.CompareState(newdata);
|
||||
|
||||
public virtual TEntity InsertOrUpdate(TEntity entity)
|
||||
{
|
||||
_dbset.AddOrUpdate(entity);
|
||||
_db.SaveChanges();
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual void SaveMany(TEntity entity, string propertyName)
|
||||
{
|
||||
_dbset.SaveMany(entity, propertyName);
|
||||
_db.SaveChanges();
|
||||
}
|
||||
|
||||
public virtual void BeginEdit(List<TEntity> data) => _dbset.BeginEdit(data);
|
||||
public virtual int EndEdit(List<TEntity> data = null)
|
||||
{
|
||||
_db.FlushCommand();
|
||||
if (UnitOfWork?.GetOrBeginTransaction(true) == null && _db.OrmOriginal.Ado.TransactionCurrentThread == null)
|
||||
{
|
||||
int affrows = 0;
|
||||
IUnitOfWork olduow = UnitOfWork;
|
||||
UnitOfWork = new UnitOfWork(_db.OrmOriginal);
|
||||
try
|
||||
{
|
||||
affrows = _dbset.EndEdit(data);
|
||||
UnitOfWork.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
UnitOfWork.Rollback();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnitOfWork.Dispose();
|
||||
UnitOfWork = olduow;
|
||||
}
|
||||
_db.SaveChanges();
|
||||
return affrows;
|
||||
}
|
||||
_db.SaveChanges();
|
||||
return _dbset.EndEdit(data);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract partial class BaseRepository<TEntity, TKey> : BaseRepository<TEntity>, IBaseRepository<TEntity, TKey>
|
||||
where TEntity : class
|
||||
{
|
||||
public BaseRepository(IFreeSql fsql, Expression<Func<TEntity, bool>> filter, Func<string, string> asTable = null) : base(fsql, filter, asTable) { }
|
||||
|
||||
TEntity CheckTKeyAndReturnIdEntity(TKey id)
|
||||
{
|
||||
var tb = _db.OrmOriginal.CodeFirst.GetTableByEntity(EntityType);
|
||||
if (tb.Primarys.Length != 1) throw new Exception(DbContextStrings.EntityType_PrimaryKeyIsNotOne(EntityType.Name));
|
||||
if (tb.Primarys[0].CsType.NullableTypeOrThis() != typeof(TKey).NullableTypeOrThis()) throw new Exception(DbContextStrings.EntityType_PrimaryKeyError(EntityType.Name, typeof(TKey).FullName));
|
||||
var obj = tb.Type.CreateInstanceGetDefaultValue();
|
||||
_db.OrmOriginal.SetEntityValueWithPropertyName(tb.Type, obj, tb.Primarys[0].CsName, id);
|
||||
var ret = obj as TEntity;
|
||||
if (ret == null) throw new Exception(DbContextStrings.EntityType_CannotConvert(EntityType.Name, typeof(TEntity).Name));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public virtual int Delete(TKey id) => Delete(CheckTKeyAndReturnIdEntity(id));
|
||||
public virtual TEntity Find(TKey id) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOne();
|
||||
public virtual TEntity Get(TKey id) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOne();
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#if net40
|
||||
#else
|
||||
namespace FreeSql
|
||||
{
|
||||
partial class BaseRepository<TEntity>
|
||||
where TEntity : class
|
||||
{
|
||||
|
||||
public virtual async Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var delete = _dbset.OrmDeleteInternal(null).Where(predicate);
|
||||
var sql = delete.ToSql();
|
||||
var affrows = await delete.ExecuteAffrowsAsync(cancellationToken);
|
||||
_db._entityChangeReport.Add(new DbContext.EntityChangeReport.ChangeInfo { EntityType = EntityType, Object = sql, Type = DbContext.EntityChangeType.SqlRaw });
|
||||
return affrows;
|
||||
}
|
||||
public virtual Task<int> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_dbset.Remove(entity);
|
||||
return _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
public virtual Task<int> DeleteAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_dbset.RemoveRange(entitys);
|
||||
return _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
public virtual async Task<List<object>> DeleteCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var list = await _dbset.RemoveCascadeByDatabaseAsync(predicate, cancellationToken);
|
||||
var affrows = await _db.SaveChangesAsync(cancellationToken);
|
||||
return list;
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _dbset.AddAsync(entity, cancellationToken);
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
return entity;
|
||||
}
|
||||
public virtual async Task<List<TEntity>> InsertAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _dbset.AddRangeAsync(entitys, cancellationToken);
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
return entitys.ToList();
|
||||
}
|
||||
|
||||
public virtual Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_dbset.Update(entity);
|
||||
return _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
public virtual Task<int> UpdateAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_dbset.UpdateRange(entitys);
|
||||
return _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public virtual async Task<TEntity> InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _dbset.AddOrUpdateAsync(entity, cancellationToken);
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual async Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _dbset.SaveManyAsync(entity, propertyName, cancellationToken);
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
partial class BaseRepository<TEntity, TKey>
|
||||
{
|
||||
public virtual Task<int> DeleteAsync(TKey id, CancellationToken cancellationToken = default) => DeleteAsync(CheckTKeyAndReturnIdEntity(id), cancellationToken);
|
||||
public virtual Task<TEntity> FindAsync(TKey id, CancellationToken cancellationToken = default) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOneAsync(cancellationToken);
|
||||
public virtual Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default) => _dbset.OrmSelectInternal(CheckTKeyAndReturnIdEntity(id)).ToOneAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
#endif
|
25
FreeSql.DbContext/Repository/Repository/DefaultRepository.cs
Normal file
25
FreeSql.DbContext/Repository/Repository/DefaultRepository.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public class DefaultRepository<TEntity, TKey> : BaseRepository<TEntity, TKey> where TEntity : class
|
||||
{
|
||||
public DefaultRepository(IFreeSql fsql) : base(fsql, null, null) { }
|
||||
public DefaultRepository(IFreeSql fsql, Expression<Func<TEntity, bool>> filter) : base(fsql, filter, null) { }
|
||||
public DefaultRepository(IFreeSql fsql, UnitOfWorkManager uowManger) : base(uowManger?.Orm ?? fsql, null, null)
|
||||
{
|
||||
uowManger?.Binding(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class GuidRepository<TEntity> : BaseRepository<TEntity, Guid> where TEntity : class
|
||||
{
|
||||
public GuidRepository(IFreeSql fsql) : this(fsql, null, null) { }
|
||||
public GuidRepository(IFreeSql fsql, Expression<Func<TEntity, bool>> filter, Func<string, string> asTable) : base(fsql, filter, asTable) { }
|
||||
public GuidRepository(IFreeSql fsql, UnitOfWorkManager uowManger) : base(uowManger?.Orm ?? fsql, null, null)
|
||||
{
|
||||
uowManger?.Binding(this);
|
||||
}
|
||||
}
|
||||
}
|
148
FreeSql.DbContext/Repository/Repository/IBaseRepository.cs
Normal file
148
FreeSql.DbContext/Repository/Repository/IBaseRepository.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
|
||||
public interface IBaseRepository : IDisposable
|
||||
{
|
||||
Type EntityType { get; }
|
||||
IUnitOfWork UnitOfWork { get; set; }
|
||||
IFreeSql Orm { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 动态Type,在使用 Repository<object> 后使用本方法,指定实体类型
|
||||
/// </summary>
|
||||
/// <param name="entityType"></param>
|
||||
/// <returns></returns>
|
||||
void AsType(Type entityType);
|
||||
/// <summary>
|
||||
/// 分表规则,参数:旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository
|
||||
/// </summary>
|
||||
/// <param name="rule"></param>
|
||||
void AsTable(Func<string, string> rule);
|
||||
/// <summary>
|
||||
/// 分表规则,参数:实体类型、旧表名;返回:新表名 https://github.com/2881099/FreeSql/wiki/Repository
|
||||
/// </summary>
|
||||
/// <param name="rule"></param>
|
||||
void AsTable(Func<Type, string, string> rule);
|
||||
|
||||
/// <summary>
|
||||
/// 设置 DbContext 选项
|
||||
/// </summary>
|
||||
DbContextOptions DbContextOptions { get; set; }
|
||||
}
|
||||
|
||||
public interface IBaseRepository<TEntity> : IBaseRepository
|
||||
where TEntity : class
|
||||
{
|
||||
IDataFilter<TEntity> DataFilter { get; }
|
||||
ISelect<TEntity> Select { get; }
|
||||
|
||||
ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp);
|
||||
ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp);
|
||||
|
||||
TEntity Insert(TEntity entity);
|
||||
List<TEntity> Insert(IEnumerable<TEntity> entitys);
|
||||
|
||||
/// <summary>
|
||||
/// 清空状态数据
|
||||
/// </summary>
|
||||
void FlushState();
|
||||
/// <summary>
|
||||
/// 附加实体,可用于不查询就更新或删除
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
void Attach(TEntity entity);
|
||||
void Attach(IEnumerable<TEntity> entity);
|
||||
/// <summary>
|
||||
/// 附加实体,并且只附加主键值,可用于不更新属性值为null或默认值的字段
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data);
|
||||
/// <summary>
|
||||
/// 比较实体,计算出值发生变化的属性,以及属性变化的前后值
|
||||
/// </summary>
|
||||
/// <param name="newdata">最新的实体对象,它将与附加实体的状态对比</param>
|
||||
/// <returns>key: 属性名, value: [旧值, 新值]</returns>
|
||||
Dictionary<string, object[]> CompareState(TEntity newdata);
|
||||
|
||||
int Update(TEntity entity);
|
||||
int Update(IEnumerable<TEntity> entitys);
|
||||
|
||||
TEntity InsertOrUpdate(TEntity entity);
|
||||
/// <summary>
|
||||
/// 保存实体的指定 ManyToMany/OneToMany 导航属性(完整对比)<para></para>
|
||||
/// 场景:在关闭级联保存功能之后,手工使用本方法<para></para>
|
||||
/// 例子:保存商品的 OneToMany 集合属性,SaveMany(goods, "Skus")<para></para>
|
||||
/// 当 goods.Skus 为空(非null)时,会删除表中已存在的所有数据<para></para>
|
||||
/// 当 goods.Skus 不为空(非null)时,添加/更新后,删除表中不存在 Skus 集合属性的所有记录
|
||||
/// </summary>
|
||||
/// <param name="entity">实体对象</param>
|
||||
/// <param name="propertyName">属性名</param>
|
||||
void SaveMany(TEntity entity, string propertyName);
|
||||
|
||||
IUpdate<TEntity> UpdateDiy { get; }
|
||||
|
||||
int Delete(TEntity entity);
|
||||
int Delete(IEnumerable<TEntity> entitys);
|
||||
int Delete(Expression<Func<TEntity, bool>> predicate);
|
||||
/// <summary>
|
||||
/// 根据设置的 OneToOne/OneToMany/ManyToMany 导航属性,级联查询所有的数据库记录,删除并返回它们
|
||||
/// </summary>
|
||||
/// <param name="predicate"></param>
|
||||
/// <returns></returns>
|
||||
List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// 开始编辑数据,然后调用方法 EndEdit 分析出添加、修改、删除 SQL 语句进行执行<para></para>
|
||||
/// 场景:winform 加载表数据后,一顿添加、修改、删除操作之后,最后才点击【保存】<para></para><para></para>
|
||||
/// 示例:https://github.com/dotnetcore/FreeSql/issues/397<para></para>
|
||||
/// 注意:* 本方法只支持单表操作,不支持导航属性级联保存
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
void BeginEdit(List<TEntity> data);
|
||||
/// <summary>
|
||||
/// 完成编辑数据,进行保存动作<para></para>
|
||||
/// 该方法根据 BeginEdit 传入的数据状态分析出添加、修改、删除 SQL 语句<para></para>
|
||||
/// 注意:* 本方法只支持单表操作,不支持导航属性级联保存
|
||||
/// </summary>
|
||||
/// <param name="data">可选参数:手工传递最终的 data 值进行对比<para></para>默认:如果不传递,则使用 BeginEdit 传入的 data 引用进行对比</param>
|
||||
/// <returns></returns>
|
||||
int EndEdit(List<TEntity> data = null);
|
||||
|
||||
#if net40
|
||||
#else
|
||||
Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default);
|
||||
Task<List<TEntity>> InsertAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
|
||||
Task<int> UpdateAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default);
|
||||
Task<TEntity> InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
|
||||
Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<int> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default);
|
||||
Task<int> DeleteAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default);
|
||||
Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
|
||||
Task<List<object>> DeleteCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
|
||||
#endif
|
||||
}
|
||||
|
||||
public interface IBaseRepository<TEntity, TKey> : IBaseRepository<TEntity>
|
||||
where TEntity : class
|
||||
{
|
||||
TEntity Get(TKey id);
|
||||
TEntity Find(TKey id);
|
||||
int Delete(TKey id);
|
||||
|
||||
#if net40
|
||||
#else
|
||||
Task<TEntity> GetAsync(TKey id, CancellationToken cancellationToken = default);
|
||||
Task<TEntity> FindAsync(TKey id, CancellationToken cancellationToken = default);
|
||||
Task<int> DeleteAsync(TKey id, CancellationToken cancellationToken = default);
|
||||
#endif
|
||||
}
|
||||
}
|
41
FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs
Normal file
41
FreeSql.DbContext/UnitOfWork/IUnitOfWork.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
/// <summary>
|
||||
/// 工作单元
|
||||
/// </summary>
|
||||
public interface IUnitOfWork : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 该对象 Select/Delete/Insert/Update/InsertOrUpdate 与工作单元事务保持一致,可省略传递 WithTransaction
|
||||
/// </summary>
|
||||
IFreeSql Orm { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 开启事务,或者返回已开启的事务
|
||||
/// </summary>
|
||||
/// <param name="isCreate">若未开启事务,则开启</param>
|
||||
/// <returns></returns>
|
||||
DbTransaction GetOrBeginTransaction(bool isCreate = true);
|
||||
|
||||
IsolationLevel? IsolationLevel { get; set; }
|
||||
|
||||
void Commit();
|
||||
|
||||
void Rollback();
|
||||
|
||||
/// <summary>
|
||||
/// 工作单元内的实体变化跟踪
|
||||
/// </summary>
|
||||
DbContext.EntityChangeReport EntityChangeReport { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义的状态数据,便于扩展
|
||||
/// </summary>
|
||||
Dictionary<string, object> States { get; }
|
||||
}
|
||||
}
|
185
FreeSql.DbContext/UnitOfWork/UnitOfWork.cs
Normal file
185
FreeSql.DbContext/UnitOfWork/UnitOfWork.cs
Normal file
@ -0,0 +1,185 @@
|
||||
using FreeSql.Internal.ObjectPool;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public class UnitOfWork : IUnitOfWork
|
||||
{
|
||||
static int _seed;
|
||||
/// <summary>
|
||||
/// 正在使用中的工作单元(调试)
|
||||
/// </summary>
|
||||
public static ConcurrentDictionary<string, UnitOfWork> DebugBeingUsed { get; } = new ConcurrentDictionary<string, UnitOfWork>();
|
||||
|
||||
protected IFreeSql _fsql;
|
||||
protected Object<DbConnection> _conn;
|
||||
protected DbTransaction _tran;
|
||||
protected Aop.TraceBeforeEventArgs _tranBefore;
|
||||
protected Aop.TraceBeforeEventArgs _uowBefore;
|
||||
|
||||
/// <summary>
|
||||
/// 开启事务后有值,是 UnitOfWork 的唯一标识<para></para>
|
||||
/// 格式:yyyyMMdd_HHmmss_种子id<para></para>
|
||||
/// 例如:20191121_214504_1
|
||||
/// </summary>
|
||||
public string Id { get; private set; }
|
||||
|
||||
public UnitOfWork(IFreeSql fsql)
|
||||
{
|
||||
_fsql = fsql;
|
||||
if (_fsql == null) throw new ArgumentNullException(nameof(fsql));
|
||||
|
||||
_uowBefore = new Aop.TraceBeforeEventArgs("UnitOfWork", null);
|
||||
_fsql.Aop.TraceBeforeHandler?.Invoke(this, _uowBefore);
|
||||
}
|
||||
|
||||
void ReturnObject()
|
||||
{
|
||||
if (string.IsNullOrEmpty(this.Id) == false && DebugBeingUsed.TryRemove(this.Id, out var old))
|
||||
this.Id = null;
|
||||
|
||||
_fsql.Ado.MasterPool.Return(_conn);
|
||||
_tran = null;
|
||||
_conn = null;
|
||||
EntityChangeReport?.Report.Clear();
|
||||
}
|
||||
|
||||
public bool Enable { get; private set; } = true;
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_tran != null)
|
||||
throw new Exception(DbContextStrings.TransactionHasBeenStarted);
|
||||
|
||||
Enable = false;
|
||||
}
|
||||
public void Open()
|
||||
{
|
||||
Enable = true;
|
||||
}
|
||||
|
||||
DbContextScopedFreeSql _ormScoped;
|
||||
public IFreeSql Orm => _ormScoped ?? (_ormScoped = DbContextScopedFreeSql.Create(_fsql, null, () => this));
|
||||
|
||||
public IsolationLevel? IsolationLevel { get; set; }
|
||||
|
||||
public DbTransaction GetOrBeginTransaction(bool isCreate = true)
|
||||
{
|
||||
if (_tran != null) return _tran;
|
||||
if (isCreate == false) return null;
|
||||
if (!Enable) return null;
|
||||
if (_conn != null) _fsql.Ado.MasterPool.Return(_conn);
|
||||
|
||||
_tranBefore = new Aop.TraceBeforeEventArgs("BeginTransaction", IsolationLevel);
|
||||
_fsql?.Aop.TraceBeforeHandler?.Invoke(this, _tranBefore);
|
||||
try
|
||||
{
|
||||
_conn = _fsql.Ado.MasterPool.Get();
|
||||
try
|
||||
{
|
||||
_tran = IsolationLevel == null ?
|
||||
_conn.Value.BeginTransaction() :
|
||||
_conn.Value.BeginTransaction(IsolationLevel.Value);
|
||||
|
||||
this.Id = $"{DateTime.Now.ToString("yyyyMMdd_HHmmss")}_{Interlocked.Increment(ref _seed)}";
|
||||
DebugBeingUsed.TryAdd(this.Id, this);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ReturnObject();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_fsql?.Aop.TraceAfterHandler?.Invoke(this, new Aop.TraceAfterEventArgs(_tranBefore, "失败", ex));
|
||||
#pragma warning disable CA2200 // 再次引发以保留堆栈详细信息
|
||||
throw ex;
|
||||
#pragma warning restore CA2200 // 再次引发以保留堆栈详细信息
|
||||
}
|
||||
return _tran;
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
var isCommited = false;
|
||||
try
|
||||
{
|
||||
if (_tran != null)
|
||||
{
|
||||
if (_tran.Connection != null) _tran.Commit();
|
||||
isCommited = true;
|
||||
_fsql?.Aop.TraceAfterHandler?.Invoke(this, new Aop.TraceAfterEventArgs(_tranBefore, "提交", null));
|
||||
|
||||
if (EntityChangeReport != null && EntityChangeReport.OnChange != null && EntityChangeReport.Report.Any() == true)
|
||||
EntityChangeReport.OnChange.Invoke(EntityChangeReport.Report);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (isCommited == false)
|
||||
_fsql?.Aop.TraceAfterHandler?.Invoke(this, new Aop.TraceAfterEventArgs(_tranBefore, "提交失败", ex));
|
||||
#pragma warning disable CA2200 // 再次引发以保留堆栈详细信息
|
||||
throw ex;
|
||||
#pragma warning restore CA2200 // 再次引发以保留堆栈详细信息
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReturnObject();
|
||||
_tranBefore = null;
|
||||
}
|
||||
}
|
||||
public void Rollback()
|
||||
{
|
||||
var isRollbacked = false;
|
||||
try
|
||||
{
|
||||
if (_tran != null)
|
||||
{
|
||||
if (_tran.Connection != null) _tran.Rollback();
|
||||
isRollbacked = true;
|
||||
_fsql?.Aop.TraceAfterHandler?.Invoke(this, new Aop.TraceAfterEventArgs(_tranBefore, "回滚", null));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (isRollbacked == false)
|
||||
_fsql?.Aop.TraceAfterHandler?.Invoke(this, new Aop.TraceAfterEventArgs(_tranBefore, "回滚失败", ex));
|
||||
#pragma warning disable CA2200 // 再次引发以保留堆栈详细信息
|
||||
throw ex;
|
||||
#pragma warning restore CA2200 // 再次引发以保留堆栈详细信息
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReturnObject();
|
||||
_tranBefore = null;
|
||||
}
|
||||
}
|
||||
|
||||
public DbContext.EntityChangeReport EntityChangeReport { get; } = new DbContext.EntityChangeReport();
|
||||
|
||||
public Dictionary<string, object> States { get; } = new Dictionary<string, object>();
|
||||
|
||||
~UnitOfWork() => this.Dispose();
|
||||
int _disposeCounter;
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Increment(ref _disposeCounter) != 1) return;
|
||||
try
|
||||
{
|
||||
this.Rollback();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_fsql?.Aop.TraceAfterHandler?.Invoke(this, new Aop.TraceAfterEventArgs(_uowBefore, "释放", null));
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
270
FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs
Normal file
270
FreeSql.DbContext/UnitOfWork/UnitOfWorkManager.cs
Normal file
@ -0,0 +1,270 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
/// <summary>
|
||||
/// 工作单元管理器
|
||||
/// </summary>
|
||||
public class UnitOfWorkManager : IDisposable
|
||||
{
|
||||
internal DbContextScopedFreeSql _ormScoped;
|
||||
internal IFreeSql OrmOriginal => _ormScoped?._originalFsql;
|
||||
public IFreeSql Orm => _ormScoped;
|
||||
List<UowInfo> _rawUows = new List<UowInfo>();
|
||||
List<UowInfo> _allUows = new List<UowInfo>();
|
||||
List<RepoInfo> _repos = new List<RepoInfo>();
|
||||
|
||||
public UnitOfWorkManager(IFreeSql fsql)
|
||||
{
|
||||
if (fsql == null) throw new ArgumentNullException(DbContextStrings.UnitOfWorkManager_Construction_CannotBeNull(nameof(UnitOfWorkManager), nameof(fsql)));
|
||||
_ormScoped = DbContextScopedFreeSql.Create(fsql, null, () => this.Current);
|
||||
}
|
||||
|
||||
#region Dispose
|
||||
~UnitOfWorkManager() => this.Dispose();
|
||||
int _disposeCounter;
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Increment(ref _disposeCounter) != 1) return;
|
||||
try
|
||||
{
|
||||
Exception exception = null;
|
||||
for (var a = _rawUows.Count - 1; a >= 0; a--)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (exception == null) _rawUows[a].Uow.Commit();
|
||||
else _rawUows[a].Uow.Rollback();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (exception == null) exception = ex;
|
||||
}
|
||||
}
|
||||
if (exception != null) throw exception;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rawUows.Clear();
|
||||
_allUows.Clear();
|
||||
_repos.Clear();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 当前的工作单元
|
||||
/// </summary>
|
||||
public IUnitOfWork Current => _allUows.LastOrDefault()?.Uow;
|
||||
|
||||
/// <summary>
|
||||
/// 将仓储的事务交给我管理
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
public void Binding(IBaseRepository repository)
|
||||
{
|
||||
var repoInfo = new RepoInfo(repository);
|
||||
repository.UnitOfWork = Current;
|
||||
if (_repos.Any(a => a.Repository == repository)) return;
|
||||
_repos.Add(repoInfo);
|
||||
}
|
||||
void SetAllRepositoryUow()
|
||||
{
|
||||
foreach (var repo in _repos)
|
||||
repo.Repository.UnitOfWork = Current ?? repo.OrginalUow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建工作单元
|
||||
/// </summary>
|
||||
/// <param name="propagation">事务传播方式</param>
|
||||
/// <param name="isolationLevel">事务隔离级别</param>
|
||||
/// <returns></returns>
|
||||
public IUnitOfWork Begin(Propagation propagation = Propagation.Required, IsolationLevel? isolationLevel = null)
|
||||
{
|
||||
switch (propagation)
|
||||
{
|
||||
case Propagation.Required: return FindedUowCreateVirtual() ?? CreateUow(isolationLevel);
|
||||
case Propagation.Supports: return FindedUowCreateVirtual() ?? CreateUowNothing(_allUows.LastOrDefault()?.IsNotSupported ?? false);
|
||||
case Propagation.Mandatory: return FindedUowCreateVirtual() ?? throw new Exception(DbContextStrings.Propagation_Mandatory);
|
||||
case Propagation.NotSupported: return CreateUowNothing(true);
|
||||
case Propagation.Never:
|
||||
var isNotSupported = _allUows.LastOrDefault()?.IsNotSupported ?? false;
|
||||
if (isNotSupported == false)
|
||||
{
|
||||
for (var a = _rawUows.Count - 1; a >= 0; a--)
|
||||
if (_rawUows[a].Uow.GetOrBeginTransaction(false) != null)
|
||||
throw new Exception(DbContextStrings.Propagation_Never);
|
||||
}
|
||||
return CreateUowNothing(isNotSupported);
|
||||
case Propagation.Nested: return CreateUow(isolationLevel);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
IUnitOfWork FindedUowCreateVirtual()
|
||||
{
|
||||
var isNotSupported = _allUows.LastOrDefault()?.IsNotSupported ?? false;
|
||||
if (isNotSupported == false)
|
||||
{
|
||||
for (var a = _rawUows.Count - 1; a >= 0; a--)
|
||||
if (_rawUows[a].Uow.GetOrBeginTransaction(false) != null)
|
||||
{
|
||||
var uow = new UnitOfWorkVirtual(_rawUows[a].Uow);
|
||||
var uowInfo = new UowInfo(uow, UowInfo.UowType.Virtual, isNotSupported);
|
||||
uow.OnDispose = () => _allUows.Remove(uowInfo);
|
||||
_allUows.Add(uowInfo);
|
||||
SetAllRepositoryUow();
|
||||
return uow;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
IUnitOfWork CreateUowNothing(bool isNotSupported)
|
||||
{
|
||||
var uow = new UnitOfWorkNothing(Orm);
|
||||
var uowInfo = new UowInfo(uow, UowInfo.UowType.Nothing, isNotSupported);
|
||||
uow.OnDispose = () => _allUows.Remove(uowInfo);
|
||||
_allUows.Add(uowInfo);
|
||||
SetAllRepositoryUow();
|
||||
return uow;
|
||||
}
|
||||
IUnitOfWork CreateUow(IsolationLevel? isolationLevel)
|
||||
{
|
||||
var uow = new UnitOfWorkOrginal(new UnitOfWork(OrmOriginal));
|
||||
var uowInfo = new UowInfo(uow, UowInfo.UowType.Orginal, false);
|
||||
if (isolationLevel != null) uow.IsolationLevel = isolationLevel.Value;
|
||||
try { uow.GetOrBeginTransaction(); }
|
||||
catch { uow.Dispose(); throw; }
|
||||
|
||||
uow.OnDispose = () =>
|
||||
{
|
||||
_rawUows.Remove(uowInfo);
|
||||
_allUows.Remove(uowInfo);
|
||||
SetAllRepositoryUow();
|
||||
};
|
||||
_rawUows.Add(uowInfo);
|
||||
_allUows.Add(uowInfo);
|
||||
SetAllRepositoryUow();
|
||||
return uow;
|
||||
}
|
||||
|
||||
class RepoInfo
|
||||
{
|
||||
public IBaseRepository Repository;
|
||||
public IUnitOfWork OrginalUow;
|
||||
|
||||
public RepoInfo(IBaseRepository repository)
|
||||
{
|
||||
this.Repository = repository;
|
||||
this.OrginalUow = repository.UnitOfWork;
|
||||
}
|
||||
}
|
||||
class UowInfo
|
||||
{
|
||||
public IUnitOfWork Uow;
|
||||
public UowType Type;
|
||||
public bool IsNotSupported;
|
||||
public enum UowType { Orginal, Virtual, Nothing }
|
||||
|
||||
public UowInfo(IUnitOfWork uow, UowType type, bool isNotSupported)
|
||||
{
|
||||
this.Uow = uow;
|
||||
this.Type = type;
|
||||
this.IsNotSupported = isNotSupported;
|
||||
}
|
||||
}
|
||||
class UnitOfWorkOrginal : IUnitOfWork
|
||||
{
|
||||
IUnitOfWork _baseUow;
|
||||
internal Action OnDispose;
|
||||
public UnitOfWorkOrginal(IUnitOfWork baseUow) => _baseUow = baseUow;
|
||||
public IFreeSql Orm => _baseUow.Orm;
|
||||
public IsolationLevel? IsolationLevel { get => _baseUow.IsolationLevel; set => _baseUow.IsolationLevel = value; }
|
||||
public DbContext.EntityChangeReport EntityChangeReport => _baseUow.EntityChangeReport;
|
||||
public Dictionary<string, object> States => _baseUow.States;
|
||||
|
||||
public DbTransaction GetOrBeginTransaction(bool isCreate = true) => _baseUow.GetOrBeginTransaction(isCreate);
|
||||
public void Commit() => _baseUow.Commit();
|
||||
public void Rollback() => _baseUow.Rollback();
|
||||
public void Dispose()
|
||||
{
|
||||
_baseUow.Dispose();
|
||||
OnDispose?.Invoke();
|
||||
}
|
||||
}
|
||||
class UnitOfWorkVirtual : IUnitOfWork
|
||||
{
|
||||
IUnitOfWork _baseUow;
|
||||
internal Action OnDispose;
|
||||
public UnitOfWorkVirtual(IUnitOfWork baseUow) => _baseUow = baseUow;
|
||||
public IFreeSql Orm => _baseUow.Orm;
|
||||
public IsolationLevel? IsolationLevel { get => _baseUow.IsolationLevel; set { } }
|
||||
public DbContext.EntityChangeReport EntityChangeReport => _baseUow.EntityChangeReport;
|
||||
public Dictionary<string, object> States => _baseUow.States;
|
||||
|
||||
public DbTransaction GetOrBeginTransaction(bool isCreate = true) => _baseUow.GetOrBeginTransaction(isCreate);
|
||||
public void Commit() { }
|
||||
public void Rollback() => _baseUow.Rollback();
|
||||
public void Dispose() => OnDispose?.Invoke();
|
||||
}
|
||||
class UnitOfWorkNothing : IUnitOfWork
|
||||
{
|
||||
internal IFreeSql _fsql;
|
||||
internal Action OnDispose;
|
||||
public UnitOfWorkNothing(IFreeSql fsql) => _fsql = fsql;
|
||||
public IFreeSql Orm => _fsql;
|
||||
public IsolationLevel? IsolationLevel { get; set; }
|
||||
public DbContext.EntityChangeReport EntityChangeReport { get; } = new DbContext.EntityChangeReport();
|
||||
public Dictionary<string, object> States { get; } = new Dictionary<string, object>();
|
||||
|
||||
public DbTransaction GetOrBeginTransaction(bool isCreate = true) => null;
|
||||
public void Commit()
|
||||
{
|
||||
if (EntityChangeReport != null && EntityChangeReport.OnChange != null && EntityChangeReport.Report.Any() == true)
|
||||
EntityChangeReport.OnChange.Invoke(EntityChangeReport.Report);
|
||||
EntityChangeReport?.Report.Clear();
|
||||
}
|
||||
public void Rollback() => EntityChangeReport?.Report.Clear();
|
||||
public void Dispose() => OnDispose?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事务传播方式
|
||||
/// </summary>
|
||||
public enum Propagation
|
||||
{
|
||||
/// <summary>
|
||||
/// 如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。
|
||||
/// </summary>
|
||||
Required,
|
||||
/// <summary>
|
||||
/// 支持当前事务,如果没有当前事务,就以非事务方法执行。
|
||||
/// </summary>
|
||||
Supports,
|
||||
/// <summary>
|
||||
/// 使用当前事务,如果没有当前事务,就抛出异常。
|
||||
/// </summary>
|
||||
Mandatory,
|
||||
/// <summary>
|
||||
/// 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
|
||||
/// </summary>
|
||||
NotSupported,
|
||||
/// <summary>
|
||||
/// 以非事务方式执行操作,如果当前事务存在则抛出异常。
|
||||
/// </summary>
|
||||
Never,
|
||||
/// <summary>
|
||||
/// 以嵌套事务方式执行。
|
||||
/// </summary>
|
||||
Nested
|
||||
}
|
||||
}
|
BIN
FreeSql.DbContext/key.snk
Normal file
BIN
FreeSql.DbContext/key.snk
Normal file
Binary file not shown.
216
FreeSql.DbContext/readme.md
Normal file
216
FreeSql.DbContext/readme.md
Normal file
@ -0,0 +1,216 @@
|
||||
FreeSql.DbContext 实现类似 EFCore 使用习惯,跟踪对象状态,最终通过 SaveChanges 方法提交事务。
|
||||
|
||||
## 安装
|
||||
|
||||
> dotnet add package FreeSql.DbContext
|
||||
|
||||
## 如何使用
|
||||
|
||||
0、通用方法,为啥是0???
|
||||
```
|
||||
using (var ctx = fsql.CreateDbContext()) {
|
||||
//var db1 = ctx.Set<Song>();
|
||||
//var db2 = ctx.Set<Tag>();
|
||||
|
||||
var item = new Song { };
|
||||
ctx.Add(item);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:DbContext 对象多线程不安全
|
||||
|
||||
1、在 OnConfiguring 方法上配置与 IFreeSql 关联
|
||||
|
||||
```csharp
|
||||
public class SongContext : DbContext {
|
||||
|
||||
public DbSet<Song> Songs { get; set; }
|
||||
public DbSet<Song> Tags { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder builder) {
|
||||
builder.UseFreeSql(dbcontext_01.Startup.Fsql);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Song {
|
||||
[Column(IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
public DateTime? Create_time { get; set; }
|
||||
public bool? Is_deleted { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Url { get; set; }
|
||||
|
||||
public virtual ICollection<Tag> Tags { get; set; }
|
||||
}
|
||||
public class Song_tag {
|
||||
public int Song_id { get; set; }
|
||||
public virtual Song Song { get; set; }
|
||||
|
||||
public int Tag_id { get; set; }
|
||||
public virtual Tag Tag { get; set; }
|
||||
}
|
||||
|
||||
public class Tag {
|
||||
[Column(IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
public int? Parent_id { get; set; }
|
||||
public virtual Tag Parent { get; set; }
|
||||
|
||||
public decimal? Ddd { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public virtual ICollection<Song> Songs { get; set; }
|
||||
public virtual ICollection<Tag> Tags { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
使用的时候与 EFCore 类似:
|
||||
|
||||
```csharp
|
||||
long id = 0;
|
||||
|
||||
using (var ctx = new SongContext()) {
|
||||
|
||||
var song = new Song { };
|
||||
await ctx.Songs.AddAsync(song);
|
||||
id = song.Id;
|
||||
|
||||
var adds = Enumerable.Range(0, 100)
|
||||
.Select(a => new Song { Create_time = DateTime.Now, Is_deleted = false, Title = "xxxx" + a, Url = "url222" })
|
||||
.ToList();
|
||||
await ctx.Songs.AddRangeAsync(adds);
|
||||
|
||||
for (var a = 0; a < adds.Count; a++)
|
||||
adds[a].Title = "dkdkdkdk" + a;
|
||||
|
||||
ctx.Songs.UpdateRange(adds);
|
||||
|
||||
ctx.Songs.RemoveRange(adds.Skip(10).Take(20).ToList());
|
||||
|
||||
//ctx.Songs.Update(adds.First());
|
||||
|
||||
adds.Last().Url = "skldfjlksdjglkjjcccc";
|
||||
ctx.Songs.Update(adds.Last());
|
||||
|
||||
//throw new Exception("回滚");
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
```
|
||||
|
||||
2、注入方式使用
|
||||
|
||||
```csharp
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IFreeSql>(Fsql);
|
||||
services.AddFreeDbContext<SongContext>(options => options.UseFreeSql(Fsql));
|
||||
}
|
||||
```
|
||||
|
||||
在 mvc 中获取:
|
||||
|
||||
```csharp
|
||||
IFreeSql _orm;
|
||||
public ValuesController(SongContext songContext) {
|
||||
}
|
||||
```
|
||||
|
||||
## 优先级
|
||||
|
||||
OnConfiguring > AddFreeDbContext
|
||||
|
||||
## 乐观锁
|
||||
|
||||
更新实体数据,在并发情况下极容易造成旧数据将新的记录更新。FreeSql 核心部分已经支持乐观锁。
|
||||
|
||||
乐观锁的原理,是利用实体某字段,如:long version,更新前先查询数据,此时 version 为 1,更新时产生的 SQL 会附加 where version = 1,当修改失败时(即 Affrows == 0)抛出异常。
|
||||
|
||||
每个实体只支持一个乐观锁,在属性前标记特性:[Column(IsVersion = true)] 即可。
|
||||
|
||||
> 无论是使用 FreeSql/FreeSql.Repository/FreeSql.DbContext,每次更新 version 的值都会增加 1
|
||||
|
||||
## 说明
|
||||
|
||||
- DbContext 操作的数据在最后 SaveChanges 时才批量保存;
|
||||
- DbContext 内所有操作,使用同一个事务;
|
||||
- 当实体存在自增时,或者 Add/AddRange 的时候主键值为空,会提前开启事务;
|
||||
- 支持同步/异步方法;
|
||||
|
||||
## 合并机制
|
||||
|
||||
db.Add(new Xxx());
|
||||
db.Add(new Xxx());
|
||||
db.Add(new Xxx());
|
||||
|
||||
这三步,会合并成一个批量插入的语句执行,前提是它们没有自增属性。
|
||||
|
||||
适用 Guid 主键,Guid 主键的值不用设置,交给 FreeSql 处理即可,空着的 Guid 主键会在插入时获取有序不重值的 Guid 值。
|
||||
|
||||
又比如:
|
||||
|
||||
db.Add(new Xxx());
|
||||
db.Add(new Xxx());
|
||||
db.Update(xxx);
|
||||
db.Add(new Xxx());
|
||||
|
||||
Guid Id 的情况下,执行三次命令:前两次插入合并执行,update 为一次,后面的 add 为一次。
|
||||
|
||||
## 联级保存
|
||||
|
||||
请移步文档[《联级保存》](https://github.com/2881099/FreeSql/wiki/%e8%81%94%e7%ba%a7%e4%bf%9d%e5%ad%98)
|
||||
|
||||
## 实体变化事件
|
||||
|
||||
全局设置:
|
||||
|
||||
```csharp
|
||||
fsql.SetDbContextOptions(opt => {
|
||||
opt.OnEntityChange = report => {
|
||||
Console.WriteLine(report);
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
单独设置 DbContext 或者 UnitOfWork:
|
||||
|
||||
```csharp
|
||||
var ctx = fsql.CreateDbContext();
|
||||
ctx.Options.OnEntityChange = report => {
|
||||
Console.WriteLine(report);
|
||||
};
|
||||
|
||||
var uow = fsql.CreateUnitOfWork();
|
||||
uow.OnEntityChange = report => {
|
||||
Console.WriteLine(report);
|
||||
};
|
||||
```
|
||||
|
||||
参数 report 是一个 List 集合,集合元素的类型定义如下:
|
||||
|
||||
```csharp
|
||||
public class ChangeInfo
|
||||
{
|
||||
public object Object { get; set; }
|
||||
public EntityChangeType Type { get; set; }
|
||||
}
|
||||
public enum EntityChangeType { Insert, Update, Delete, SqlRaw }
|
||||
```
|
||||
|
||||
| 变化类型 | 说明 |
|
||||
| -- | -- |
|
||||
| Insert | 实体对象被插入 |
|
||||
| Update | 实体对象被更新 |
|
||||
| Delete | 实体对象被删除 |
|
||||
| SqlRaw | 执行了SQL语句 |
|
||||
|
||||
SqlRaw 目前有两处地方比较特殊:
|
||||
- 多对多联级更新导航属性的时候,对中间表的全部删除操作;
|
||||
- 通用仓储类 BaseRepository 有一个 Delete 方法,参数为表达式,而并非实体;
|
||||
```csharp
|
||||
int Delete(Expression<Func<TEntity, bool>> predicate);
|
||||
```
|
||||
|
||||
DbContext.SaveChanges,或者 Repository 对实体的 Insert/Update/Delete,或者 UnitOfWork.Commit 操作都会最多触发一次该事件。
|
Reference in New Issue
Block a user