initial commit

This commit is contained in:
tk
2024-11-13 18:18:28 +08:00
commit 013f35e296
1500 changed files with 443723 additions and 0 deletions

View 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);
}
}
}
}

View 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

View 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; }
}
}

View 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;
}
}
}

View 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());
}
}
}

View 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;
}
}
}
}

View File

@ -0,0 +1,13 @@

namespace FreeSql
{
public class FreeContext : DbContext
{
public FreeContext(IFreeSql orm)
{
_ormScoped = DbContextScopedFreeSql.Create(orm, () => this, () => UnitOfWork);
}
}
}

View 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&lt;object&gt; 后使用本方法,指定实体类型
/// </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
}
}

View 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

View 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
}
}

View 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;
}
}
}

View 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&lt;T&gt;的配置类
/// </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
}

View 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;
}
}
}

View 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;
}
}
}

View File

@ -0,0 +1,7 @@
namespace FreeSql.Extensions.EfCoreFluentApi
{
public interface IEntityTypeConfiguration<TEntity> where TEntity : class
{
void Configure(EfCoreTableFluent<TEntity> model);
}
}

View 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

View 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>();
}

View 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>

View 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&lt;&gt;.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;
}
}
}

View File

@ -0,0 +1,5 @@
<#
Session["ResourceFile"] = "DbContextStrings.resx";
Session["AccessModifier"] = "public";
#>
<#@ include file="../../FreeSql/Properties/Resources.tt" #>

View 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&lt;&gt; 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>

View 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&lt;&gt;.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>

View File

@ -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
}
}

View 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);
}
}

View File

@ -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;
}
}
}

View 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();
}
}
}

View 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);
}
}
}

View File

@ -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

View File

@ -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);
}
}

View 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();
}
}

View File

@ -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

View 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);
}
}
}

View 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&lt;object&gt; 后使用本方法,指定实体类型
/// </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
}
}

View 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; }
}
}

View 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);
}
}
}
}

View 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

Binary file not shown.

216
FreeSql.DbContext/readme.md Normal file
View 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 操作都会最多触发一次该事件。