mirror of
https://github.com/nsnail/FreeSql.git
synced 2025-08-02 09:55:57 +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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user