mirror of
https://github.com/nsnail/FreeSql.git
synced 2025-08-01 17:35:58 +08:00
initial commit
This commit is contained in:
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace FreeSql.DataAnnotations
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 设置 AggregateRootRepository 边界范围<para></para>
|
||||
/// 在边界范围之内的规则 :<para></para>
|
||||
/// 1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改<para></para>
|
||||
/// 2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略)<para></para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
|
||||
public class AggregateRootBoundaryAttribute : Attribute
|
||||
{
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// 边界是否终止
|
||||
/// </summary>
|
||||
public bool Break { get; set; }
|
||||
/// <summary>
|
||||
/// 边界是否终止向下探测
|
||||
/// </summary>
|
||||
public bool BreakThen { get; set; }
|
||||
|
||||
public AggregateRootBoundaryAttribute(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
}
|
||||
public AggregateRootBoundaryAttribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FreeSql.Internal.Model
|
||||
{
|
||||
|
||||
public class AggregateRootTrackingChangeInfo
|
||||
{
|
||||
|
||||
public List<NativeTuple<Type, object>> InsertLog { get; } = new List<NativeTuple<Type, object>>();
|
||||
|
||||
public List<NativeTuple<Type, object, object, List<string>>> UpdateLog { get; } = new List<NativeTuple<Type, object, object, List<string>>>();
|
||||
|
||||
public List<NativeTuple<Type, object[]>> DeleteLog { get; } = new List<NativeTuple<Type, object[]>>();
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public interface IAggregateRootRepository<TEntity>: IBaseRepository<TEntity> where TEntity : class
|
||||
{
|
||||
IBaseRepository<TEntity> ChangeBoundary(string name);
|
||||
}
|
||||
|
||||
public partial class AggregateRootRepository<TEntity> : IAggregateRootRepository<TEntity> where TEntity : class
|
||||
{
|
||||
readonly IBaseRepository<TEntity> _repository;
|
||||
public AggregateRootRepository(IFreeSql fsql)
|
||||
{
|
||||
if (fsql == null) throw new ArgumentNullException(nameof(fsql));
|
||||
_repository = fsql.GetRepository<TEntity>();
|
||||
_repository.DbContextOptions.EnableCascadeSave = false;
|
||||
}
|
||||
public AggregateRootRepository(IFreeSql fsql, UnitOfWorkManager uowManager) : this(uowManager?.Orm ?? fsql)
|
||||
{
|
||||
uowManager?.Binding(_repository);
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeChildRepositorys();
|
||||
_repository.FlushState();
|
||||
_repository.Dispose();
|
||||
FlushState();
|
||||
}
|
||||
|
||||
string _boundaryName = "";
|
||||
public IBaseRepository<TEntity> ChangeBoundary(string name)
|
||||
{
|
||||
DisposeChildRepositorys();
|
||||
_repository.FlushState();
|
||||
FlushState();
|
||||
_boundaryName = string.Concat(name).Trim();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IFreeSql Orm => _repository.Orm;
|
||||
public IUnitOfWork UnitOfWork { get => _repository.UnitOfWork; set => _repository.UnitOfWork = value; }
|
||||
public DbContextOptions DbContextOptions
|
||||
{
|
||||
get => _repository.DbContextOptions;
|
||||
set
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException(nameof(DbContextOptions));
|
||||
_repository.DbContextOptions = value;
|
||||
_repository.DbContextOptions.EnableCascadeSave = false;
|
||||
}
|
||||
}
|
||||
public void AsType(Type entityType) => _repository.AsType(entityType);
|
||||
Func<Type, string, string> _asTableRule;
|
||||
public void AsTable(Func<string, string> rule)
|
||||
{
|
||||
_repository.AsTable(rule);
|
||||
if (rule == null)
|
||||
{
|
||||
_asTableRule = null;
|
||||
return;
|
||||
}
|
||||
_asTableRule = (t, old) => t == EntityType ? rule(old) : null;
|
||||
}
|
||||
public void AsTable(Func<Type, string, string> rule)
|
||||
{
|
||||
_repository.AsTable(rule);
|
||||
_asTableRule = rule;
|
||||
}
|
||||
public Type EntityType => _repository.EntityType;
|
||||
public IDataFilter<TEntity> DataFilter => _repository.DataFilter;
|
||||
|
||||
public void Attach(TEntity entity)
|
||||
{
|
||||
var state = CreateEntityState(entity);
|
||||
if (_states.ContainsKey(state.Key))
|
||||
_states[state.Key] = state;
|
||||
else
|
||||
_states.Add(state.Key, state);
|
||||
}
|
||||
public void Attach(IEnumerable<TEntity> entity)
|
||||
{
|
||||
foreach (var item in entity)
|
||||
Attach(item);
|
||||
}
|
||||
public IBaseRepository<TEntity> AttachOnlyPrimary(TEntity data) => _repository.AttachOnlyPrimary(data);
|
||||
public Dictionary<string, object[]> CompareState(TEntity newdata)
|
||||
{
|
||||
if (newdata == null) return null;
|
||||
var _table = Orm.CodeFirst.GetTableByEntity(EntityType);
|
||||
if (_table.Primarys.Any() == false) throw new Exception(DbContextStrings.Incomparable_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, newdata)));
|
||||
var key = Orm.GetEntityKeyString(EntityType, newdata, false);
|
||||
if (string.IsNullOrEmpty(key)) throw new Exception(DbContextStrings.Incomparable_PrimaryKey_NotSet(Orm.GetEntityString(EntityType, newdata)));
|
||||
if (_states.TryGetValue(key, out var oldState) == false || oldState == null) throw new Exception($"不可对比,数据未被跟踪:{Orm.GetEntityString(EntityType, newdata)}");
|
||||
AggregateRootTrackingChangeInfo tracking = new AggregateRootTrackingChangeInfo();
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, oldState, newdata, null, tracking);
|
||||
return new Dictionary<string, object[]>
|
||||
{
|
||||
["Insert"] = tracking.InsertLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray(),
|
||||
["Delete"] = tracking.DeleteLog.Select(a => new object[] { a.Item1, a.Item2 }).ToArray(),
|
||||
["Update"] = tracking.UpdateLog.Select(a => new object[] { a.Item1, a.Item2, a.Item3, a.Item4 }).ToArray(),
|
||||
};
|
||||
}
|
||||
public void FlushState()
|
||||
{
|
||||
DisposeChildRepositorys();
|
||||
_repository.FlushState();
|
||||
_states.Clear();
|
||||
}
|
||||
|
||||
public IUpdate<TEntity> UpdateDiy => _repository.UpdateDiy;
|
||||
public ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => Select.Where(exp);
|
||||
public ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => Select.WhereIf(condition, exp);
|
||||
|
||||
readonly Dictionary<Type, IBaseRepository<object>> _childRepositorys = new Dictionary<Type, IBaseRepository<object>>();
|
||||
protected virtual IFreeSql GetChildFreeSql(Type type) => Orm;
|
||||
IBaseRepository<object> GetChildRepository(Type type)
|
||||
{
|
||||
if (_childRepositorys.TryGetValue(type, out var repo) == false)
|
||||
{
|
||||
repo = GetChildFreeSql(type).GetRepository<object>();
|
||||
repo.AsType(type);
|
||||
_childRepositorys.Add(type, repo);
|
||||
}
|
||||
repo.UnitOfWork = UnitOfWork;
|
||||
repo.DbContextOptions = DbContextOptions;
|
||||
repo.DbContextOptions.EnableCascadeSave = false;
|
||||
repo.AsTable(_asTableRule);
|
||||
return repo;
|
||||
}
|
||||
void DisposeChildRepositorys()
|
||||
{
|
||||
foreach (var repo in _childRepositorys.Values)
|
||||
{
|
||||
repo.FlushState();
|
||||
repo.Dispose();
|
||||
}
|
||||
_childRepositorys.Clear();
|
||||
}
|
||||
|
||||
#region 状态管理
|
||||
protected Dictionary<string, EntityState> _states = new Dictionary<string, EntityState>();
|
||||
protected class EntityState
|
||||
{
|
||||
public EntityState(TEntity value, string key)
|
||||
{
|
||||
this.Value = value;
|
||||
this.Key = key;
|
||||
this.Time = DateTime.Now;
|
||||
}
|
||||
public TEntity OldValue { get; set; }
|
||||
public TEntity Value { get; set; }
|
||||
public string Key { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
}
|
||||
EntityState CreateEntityState(TEntity data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
var key = Orm.GetEntityKeyString(EntityType, data, false);
|
||||
var state = new EntityState((TEntity)EntityType.CreateInstanceGetDefaultValue(), key);
|
||||
AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, data, state.Value);
|
||||
return state;
|
||||
}
|
||||
bool? ExistsInStates(object data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
var key = Orm.GetEntityKeyString(EntityType, data, false);
|
||||
if (string.IsNullOrEmpty(key)) return null;
|
||||
return _states.ContainsKey(key);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 查询数据
|
||||
/// <summary>
|
||||
/// 默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)<para></para>
|
||||
/// 重写:使用
|
||||
/// </summary>
|
||||
public virtual ISelect<TEntity> Select => SelectAggregateRoot;
|
||||
/// <summary>
|
||||
/// 创建查询对象(纯净)<para></para>
|
||||
/// _<para></para>
|
||||
/// 聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发<para></para>
|
||||
/// string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order))
|
||||
/// </summary>
|
||||
protected ISelect<TEntity> SelectDiy => _repository.Select.TrackToList(SelectAggregateRootTracking);
|
||||
/// <summary>
|
||||
/// 创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected ISelect<TEntity> SelectAggregateRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
var query = _repository.Select.TrackToList(SelectAggregateRootTracking);
|
||||
query = AggregateRootUtils.GetAutoIncludeQuery(_boundaryName, query);
|
||||
return query;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// ISelect.TrackToList 委托,数据返回后自动 Attach
|
||||
/// </summary>
|
||||
/// <param name="list"></param>
|
||||
protected void SelectAggregateRootTracking(object list)
|
||||
{
|
||||
if (list == null) return;
|
||||
if (list is IEnumerable<TEntity> entities)
|
||||
{
|
||||
Attach(entities);
|
||||
return;
|
||||
}
|
||||
var ie = list as IEnumerable;
|
||||
if (ie == null) return;
|
||||
var isfirst = true;
|
||||
foreach (var item in ie)
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (isfirst)
|
||||
{
|
||||
isfirst = false;
|
||||
var itemType = item.GetType();
|
||||
if (itemType == typeof(object)) return;
|
||||
if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType;
|
||||
if (Orm.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return;
|
||||
if (itemType.GetConstructor(Type.EmptyTypes) == null) return;
|
||||
}
|
||||
if (item is TEntity item2) Attach(item2);
|
||||
else return;
|
||||
}
|
||||
}
|
||||
//void SelectAggregateRootNavigateReader<T1>(ISelect<T1> currentQuery, Type entityType, string navigatePath, Stack<Type> ignores)
|
||||
//{
|
||||
// if (ignores.Any(a => a == entityType)) return;
|
||||
// ignores.Push(entityType);
|
||||
// var table = Orm.CodeFirst.GetTableByEntity(entityType);
|
||||
// if (table == null) return;
|
||||
// if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}.";
|
||||
// foreach (var tr in table.GetAllTableRef())
|
||||
// {
|
||||
// var tbref = tr.Value;
|
||||
// if (tbref.Exception != null) continue;
|
||||
// var navigateExpression = $"{navigatePath}{tr.Key}";
|
||||
// switch (tbref.RefType)
|
||||
// {
|
||||
// case TableRefType.OneToOne:
|
||||
// if (ignores.Any(a => a == tbref.RefEntityType)) break;
|
||||
// currentQuery.IncludeByPropertyName(navigateExpression);
|
||||
// SelectAggregateRootNavigateReader(currentQuery, tbref.RefEntityType, navigateExpression, ignores);
|
||||
// break;
|
||||
// case TableRefType.OneToMany:
|
||||
// var ignoresCopy = new Stack<Type>(ignores.ToArray());
|
||||
// currentQuery.IncludeByPropertyName(navigateExpression, then =>
|
||||
// SelectAggregateRootNavigateReader(then, tbref.RefEntityType, "", ignoresCopy)); //variable 'then' of type 'FreeSql.ISelect`1[System.Object]' referenced from scope '', but it is not defined
|
||||
// break;
|
||||
// case TableRefType.ManyToMany:
|
||||
// currentQuery.IncludeByPropertyName(navigateExpression);
|
||||
// break;
|
||||
// case TableRefType.PgArrayToMany:
|
||||
// break;
|
||||
// case TableRefType.ManyToOne:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// ignores.Pop();
|
||||
//}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,321 @@
|
||||
#if net40
|
||||
#else
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
using FreeSql.Internal;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
partial class AggregateRootRepository<TEntity>
|
||||
{
|
||||
|
||||
#region InsertAsync
|
||||
async public virtual Task<TEntity> InsertAsync(TEntity entity, CancellationToken cancellationToken = default) => (await InsertAsync(new[] { entity }, cancellationToken)).FirstOrDefault();
|
||||
async public virtual Task<List<TEntity>> InsertAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var repos = new Dictionary<Type, object>();
|
||||
try
|
||||
{
|
||||
var ret = await InsertWithinBoundaryStaticAsync(_boundaryName, _repository, GetChildRepository, entitys, null, cancellationToken);
|
||||
Attach(ret);
|
||||
return ret;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeChildRepositorys();
|
||||
_repository.FlushState();
|
||||
}
|
||||
}
|
||||
async Task<List<T1>> InsertWithinBoundaryStaticAsync<T1>(string boundaryName, IBaseRepository<T1> rootRepository, Func<Type, IBaseRepository<object>> getChildRepository, IEnumerable<T1> rootEntitys, int[] affrows, CancellationToken cancellationToken) where T1 : class
|
||||
{
|
||||
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||
Dictionary<Type, IBaseRepository<object>> repos = new Dictionary<Type, IBaseRepository<object>>();
|
||||
var localAffrows = 0;
|
||||
try
|
||||
{
|
||||
rootRepository.DbContextOptions.EnableCascadeSave = false;
|
||||
return await LocalInsertAsync(rootRepository, rootEntitys, true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (affrows != null) affrows[0] = localAffrows;
|
||||
}
|
||||
|
||||
bool LocalCanInsert(Type entityType, object entity, bool isadd)
|
||||
{
|
||||
var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false);
|
||||
if (string.IsNullOrEmpty(stateKey)) return true;
|
||||
if (ignores.TryGetValue(entityType, out var stateKeys) == false)
|
||||
{
|
||||
if (isadd)
|
||||
{
|
||||
ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||
stateKeys.Add(stateKey, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (stateKeys.ContainsKey(stateKey) == false)
|
||||
{
|
||||
if (isadd) stateKeys.Add(stateKey, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async Task<List<T2>> LocalInsertAsync<T2>(IBaseRepository<T2> repository, IEnumerable<T2> entitys, bool cascade) where T2 : class
|
||||
{
|
||||
var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType);
|
||||
if (table.Primarys.Any(col => col.Attribute.IsIdentity))
|
||||
{
|
||||
foreach (var entity in entitys)
|
||||
repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity);
|
||||
}
|
||||
var ret = await repository.InsertAsync(entitys, cancellationToken);
|
||||
localAffrows += ret.Count;
|
||||
foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true);
|
||||
if (cascade == false) return ret;
|
||||
|
||||
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||
{
|
||||
var tbref = tr.Value;
|
||||
if (tbref.Exception != null) continue;
|
||||
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||
var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||
if (boundaryAttr?.Break == true) continue;
|
||||
switch (tbref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
var otoList = ret.Select(entity =>
|
||||
{
|
||||
var otoItem = table.GetPropertyValue(entity, prop.Name);
|
||||
if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null;
|
||||
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem);
|
||||
return otoItem;
|
||||
}).Where(entity => entity != null).ToArray();
|
||||
if (otoList.Any())
|
||||
{
|
||||
var repo = getChildRepository(tbref.RefEntityType);
|
||||
await LocalInsertAsync(repo, otoList, boundaryAttr?.BreakThen != true);
|
||||
}
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
var otmList = ret.Select(entity =>
|
||||
{
|
||||
var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
|
||||
if (otmEach == null) return null;
|
||||
var otmItems = new List<object>();
|
||||
foreach (var otmItem in otmEach)
|
||||
{
|
||||
if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue;
|
||||
otmItems.Add(otmItem);
|
||||
}
|
||||
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems);
|
||||
return otmItems;
|
||||
}).Where(entity => entity != null).SelectMany(entity => entity).ToArray();
|
||||
if (otmList.Any())
|
||||
{
|
||||
var repo = getChildRepository(tbref.RefEntityType);
|
||||
await LocalInsertAsync(repo, otmList, boundaryAttr?.BreakThen != true);
|
||||
}
|
||||
break;
|
||||
case TableRefType.ManyToMany:
|
||||
var mtmMidList = new List<object>();
|
||||
ret.ForEach(entity =>
|
||||
{
|
||||
var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop);
|
||||
if (mids != null) mtmMidList.AddRange(mids);
|
||||
});
|
||||
if (mtmMidList.Any())
|
||||
{
|
||||
var repo = getChildRepository(tbref.RefMiddleEntityType);
|
||||
await LocalInsertAsync(repo, mtmMidList, false);
|
||||
}
|
||||
break;
|
||||
case TableRefType.PgArrayToMany:
|
||||
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
async public virtual Task<TEntity> InsertOrUpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||
if (entity == null) throw new ArgumentNullException(nameof(entity));
|
||||
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
|
||||
if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity)));
|
||||
|
||||
var flagExists = ExistsInStates(entity);
|
||||
if (flagExists == false)
|
||||
{
|
||||
var olddata = await Select.WhereDynamic(entity).FirstAsync(cancellationToken);
|
||||
flagExists = olddata != null;
|
||||
}
|
||||
if (flagExists == true)
|
||||
{
|
||||
var affrows = await UpdateAsync(entity, cancellationToken);
|
||||
return entity;
|
||||
}
|
||||
if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length)
|
||||
Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity);
|
||||
return await InsertAsync(entity, cancellationToken);
|
||||
}
|
||||
|
||||
public virtual Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) => UpdateAsync(new[] { entity }, cancellationToken);
|
||||
async public virtual Task<int> UpdateAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tracking = new AggregateRootTrackingChangeInfo();
|
||||
foreach (var entity in entitys)
|
||||
{
|
||||
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}");
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking);
|
||||
}
|
||||
var affrows = await SaveTrackingChangeAsync(tracking, cancellationToken);
|
||||
foreach (var entity in entitys)
|
||||
Attach(entity);
|
||||
return affrows;
|
||||
}
|
||||
|
||||
|
||||
public virtual Task<int> DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(new[] { entity }, null, cancellationToken);
|
||||
public virtual Task<int> DeleteAsync(IEnumerable<TEntity> entitys, CancellationToken cancellationToken = default) => DeleteWithinBoundaryAsync(entitys, null, cancellationToken);
|
||||
async public virtual Task<int> DeleteAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) => await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), null, cancellationToken);
|
||||
async public virtual Task<List<object>> DeleteCascadeByDatabaseAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var deletedOutput = new List<object>();
|
||||
await DeleteWithinBoundaryAsync(await SelectAggregateRoot.Where(predicate).ToListAsync(cancellationToken), deletedOutput, cancellationToken);
|
||||
return deletedOutput;
|
||||
}
|
||||
async Task<int> DeleteWithinBoundaryAsync(IEnumerable<TEntity> entitys, List<object> deletedOutput, CancellationToken cancellationToken)
|
||||
{
|
||||
var tracking = new AggregateRootTrackingChangeInfo();
|
||||
foreach (var entity in entitys)
|
||||
{
|
||||
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking);
|
||||
_states.Remove(stateKey);
|
||||
}
|
||||
var affrows = 0;
|
||||
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
|
||||
{
|
||||
var delete = Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1);
|
||||
if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old));
|
||||
affrows += await delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken);
|
||||
if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2);
|
||||
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
|
||||
new DbContext.EntityChangeReport.ChangeInfo
|
||||
{
|
||||
Type = DbContext.EntityChangeType.Delete,
|
||||
EntityType = tracking.DeleteLog[a].Item1,
|
||||
Object = x
|
||||
}));
|
||||
}
|
||||
return affrows;
|
||||
}
|
||||
|
||||
async public virtual Task SaveManyAsync(TEntity entity, string propertyName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tracking = new AggregateRootTrackingChangeInfo();
|
||||
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}");
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking);
|
||||
await SaveTrackingChangeAsync(tracking, cancellationToken);
|
||||
Attach(entity); //应该只存储 propertyName 内容
|
||||
}
|
||||
|
||||
|
||||
async Task<int> SaveTrackingChangeAsync(AggregateRootTrackingChangeInfo tracking, CancellationToken cancellationToken)
|
||||
{
|
||||
var affrows = 0;
|
||||
DisposeChildRepositorys();
|
||||
var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray());
|
||||
foreach (var il in insertLogDict)
|
||||
{
|
||||
var repo = GetChildRepository(il.Key);
|
||||
var affrowsOut = new int[1];
|
||||
await InsertWithinBoundaryStaticAsync(_boundaryName, repo, GetChildRepository, il.Value, affrowsOut, cancellationToken);
|
||||
affrows += affrowsOut[0];
|
||||
}
|
||||
|
||||
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
|
||||
{
|
||||
var delete = Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1);
|
||||
if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old));
|
||||
affrows += await delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrowsAsync(cancellationToken);
|
||||
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
|
||||
new DbContext.EntityChangeReport.ChangeInfo
|
||||
{
|
||||
Type = DbContext.EntityChangeType.Delete,
|
||||
EntityType = tracking.DeleteLog[a].Item1,
|
||||
Object = x
|
||||
}));
|
||||
}
|
||||
|
||||
if (_repository.DbContextOptions.AuditValue != null)
|
||||
{
|
||||
foreach (var log in tracking.UpdateLog)
|
||||
{
|
||||
var table = Orm.CodeFirst.GetTableByEntity(log.Item1);
|
||||
_repository.DbContextOptions.AuditValue(new DbContextAuditValueEventArgs(Aop.AuditValueType.Update, log.Item1, log.Item3));
|
||||
log.Item4.Clear();
|
||||
foreach (var col in table.ColumnsByCs.Values)
|
||||
{
|
||||
if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue;
|
||||
if (table.ColumnsByCs.ContainsKey(col.CsName))
|
||||
{
|
||||
if (col.Attribute.IsVersion) continue;
|
||||
var propvalBefore = table.GetPropertyValue(log.Item2, col.CsName);
|
||||
var propvalAfter = table.GetPropertyValue(log.Item3, col.CsName);
|
||||
if (AggregateRootUtils.CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) log.Item4.Add(col.CsName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key && b.Item4.Any()).Select(b => new
|
||||
{
|
||||
BeforeObject = b.Item2,
|
||||
AfterObject = b.Item3,
|
||||
UpdateColumns = b.Item4,
|
||||
UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c))
|
||||
}).ToArray());
|
||||
var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a =>
|
||||
a.Value.GroupBy(b => b.UpdateColumnsString).ToDictionary(b => b.Key, b => a.Value.Where(c => c.UpdateColumnsString == b.Key).ToArray()));
|
||||
foreach (var dl in updateLogDict2)
|
||||
{
|
||||
foreach (var dl2 in dl.Value)
|
||||
{
|
||||
var update = Orm.Update<object>().AsType(dl.Key);
|
||||
if (_asTableRule != null) update.AsTable(old => _asTableRule(dl.Key, old));
|
||||
affrows += await update
|
||||
.SetSource(dl2.Value.Select(a => a.AfterObject).ToArray())
|
||||
.UpdateColumns(dl2.Value.First().UpdateColumns.ToArray())
|
||||
.ExecuteAffrowsAsync(cancellationToken);
|
||||
UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x =>
|
||||
new DbContext.EntityChangeReport.ChangeInfo
|
||||
{
|
||||
Type = DbContext.EntityChangeType.Update,
|
||||
EntityType = dl.Key,
|
||||
Object = x.AfterObject,
|
||||
BeforeObject = x.BeforeObject
|
||||
}));
|
||||
}
|
||||
}
|
||||
DisposeChildRepositorys();
|
||||
return affrows;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,371 @@
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
using FreeSql.Internal;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
partial class AggregateRootRepository<TEntity>
|
||||
{
|
||||
|
||||
#region BeginEdit/EndEdit
|
||||
List<TEntity> _dataEditing;
|
||||
ConcurrentDictionary<string, EntityState> _statesEditing = new ConcurrentDictionary<string, EntityState>();
|
||||
public virtual void BeginEdit(List<TEntity> data)
|
||||
{
|
||||
if (data == null) return;
|
||||
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
|
||||
if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotEdit_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, data.First())));
|
||||
_statesEditing.Clear();
|
||||
_dataEditing = data;
|
||||
foreach (var item in data)
|
||||
{
|
||||
var key = Orm.GetEntityKeyString(EntityType, item, false);
|
||||
if (string.IsNullOrEmpty(key)) continue;
|
||||
|
||||
_statesEditing.AddOrUpdate(key, k => CreateEntityState(item), (k, ov) =>
|
||||
{
|
||||
AggregateRootUtils.MapEntityValue(_boundaryName, Orm, EntityType, item, ov.Value);
|
||||
ov.Time = DateTime.Now;
|
||||
return ov;
|
||||
});
|
||||
}
|
||||
}
|
||||
public virtual int EndEdit(List<TEntity> data = null)
|
||||
{
|
||||
if (data == null) data = _dataEditing;
|
||||
if (data == null) return 0;
|
||||
var tracking = new AggregateRootTrackingChangeInfo();
|
||||
try
|
||||
{
|
||||
var addList = new List<TEntity>();
|
||||
var ediList = new List<TEntity>();
|
||||
foreach (var item in data)
|
||||
{
|
||||
var key = Orm.GetEntityKeyString(EntityType, item, false);
|
||||
if (_statesEditing.TryRemove(key, out var state) == false)
|
||||
{
|
||||
tracking.InsertLog.Add(NativeTuple.Create(EntityType, (object)item));
|
||||
continue;
|
||||
}
|
||||
_states[key] = state;
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, item, null, tracking);
|
||||
}
|
||||
foreach (var item in _statesEditing.Values.OrderBy(a => a.Time))
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, item, null, null, tracking);
|
||||
|
||||
return SaveTrackingChange(tracking);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dataEditing = null;
|
||||
_statesEditing.Clear();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Insert
|
||||
public virtual TEntity Insert(TEntity entity) => Insert(new[] { entity }).FirstOrDefault();
|
||||
public virtual List<TEntity> Insert(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
var repos = new Dictionary<Type, object>();
|
||||
try
|
||||
{
|
||||
var ret = InsertWithinBoundaryStatic(_boundaryName, _repository, GetChildRepository, entitys, out var affrows);
|
||||
Attach(ret);
|
||||
return ret;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeChildRepositorys();
|
||||
_repository.FlushState();
|
||||
}
|
||||
}
|
||||
static List<T1> InsertWithinBoundaryStatic<T1>(string boundaryName, IBaseRepository<T1> rootRepository, Func<Type, IBaseRepository<object>> getChildRepository, IEnumerable<T1> rootEntitys, out int affrows) where T1 : class {
|
||||
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||
Dictionary<Type, IBaseRepository<object>> repos = new Dictionary<Type, IBaseRepository<object>>();
|
||||
var localAffrows = 0;
|
||||
try
|
||||
{
|
||||
rootRepository.DbContextOptions.EnableCascadeSave = false;
|
||||
return LocalInsert(rootRepository, rootEntitys, true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
affrows = localAffrows;
|
||||
}
|
||||
|
||||
bool LocalCanInsert(Type entityType, object entity, bool isadd)
|
||||
{
|
||||
var stateKey = rootRepository.Orm.GetEntityKeyString(entityType, entity, false);
|
||||
if (string.IsNullOrEmpty(stateKey)) return true;
|
||||
if (ignores.TryGetValue(entityType, out var stateKeys) == false)
|
||||
{
|
||||
if (isadd)
|
||||
{
|
||||
ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||
stateKeys.Add(stateKey, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (stateKeys.ContainsKey(stateKey) == false)
|
||||
{
|
||||
if (isadd) stateKeys.Add(stateKey, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
List<T2> LocalInsert<T2>(IBaseRepository<T2> repository, IEnumerable<T2> entitys, bool cascade) where T2 : class
|
||||
{
|
||||
var table = repository.Orm.CodeFirst.GetTableByEntity(repository.EntityType);
|
||||
if (table.Primarys.Any(col => col.Attribute.IsIdentity))
|
||||
{
|
||||
foreach (var entity in entitys)
|
||||
repository.Orm.ClearEntityPrimaryValueWithIdentity(repository.EntityType, entity);
|
||||
}
|
||||
var ret = repository.Insert(entitys);
|
||||
localAffrows += ret.Count;
|
||||
foreach (var entity in entitys) LocalCanInsert(repository.EntityType, entity, true);
|
||||
if (cascade == false) return ret;
|
||||
|
||||
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||
{
|
||||
var tbref = tr.Value;
|
||||
if (tbref.Exception != null) continue;
|
||||
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||
var boundaryAttr = AggregateRootUtils.GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||
if (boundaryAttr?.Break == true) continue;
|
||||
switch (tbref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
var otoList = ret.Select(entity =>
|
||||
{
|
||||
var otoItem = table.GetPropertyValue(entity, prop.Name);
|
||||
if (LocalCanInsert(tbref.RefEntityType, otoItem, false) == false) return null;
|
||||
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otoItem);
|
||||
return otoItem;
|
||||
}).Where(entity => entity != null).ToArray();
|
||||
if (otoList.Any())
|
||||
{
|
||||
var repo = getChildRepository(tbref.RefEntityType);
|
||||
LocalInsert(repo, otoList, boundaryAttr?.BreakThen != true);
|
||||
}
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
var otmList = ret.Select(entity =>
|
||||
{
|
||||
var otmEach = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
|
||||
if (otmEach == null) return null;
|
||||
var otmItems = new List<object>();
|
||||
foreach (var otmItem in otmEach)
|
||||
{
|
||||
if (LocalCanInsert(tbref.RefEntityType, otmItem, false) == false) continue;
|
||||
otmItems.Add(otmItem);
|
||||
}
|
||||
AggregateRootUtils.SetNavigateRelationshipValue(repository.Orm, tbref, table.Type, entity, otmItems);
|
||||
return otmItems;
|
||||
}).Where(entity => entity != null).SelectMany(entity => entity).ToArray();
|
||||
if (otmList.Any())
|
||||
{
|
||||
var repo = getChildRepository(tbref.RefEntityType);
|
||||
LocalInsert(repo, otmList, boundaryAttr?.BreakThen != true);
|
||||
}
|
||||
break;
|
||||
case TableRefType.ManyToMany:
|
||||
var mtmMidList = new List<object>();
|
||||
ret.ForEach(entity =>
|
||||
{
|
||||
var mids = AggregateRootUtils.GetManyToManyObjects(repository.Orm, table, tbref, entity, prop);
|
||||
if (mids != null) mtmMidList.AddRange(mids);
|
||||
});
|
||||
if (mtmMidList.Any())
|
||||
{
|
||||
var repo = getChildRepository(tbref.RefMiddleEntityType);
|
||||
LocalInsert(repo, mtmMidList, false);
|
||||
}
|
||||
break;
|
||||
case TableRefType.PgArrayToMany:
|
||||
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public virtual TEntity InsertOrUpdate(TEntity entity)
|
||||
{
|
||||
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||
if (entity == null) throw new ArgumentNullException(nameof(entity));
|
||||
var table = Orm.CodeFirst.GetTableByEntity(EntityType);
|
||||
if (table.Primarys.Any() == false) throw new Exception(DbContextStrings.CannotAdd_EntityHasNo_PrimaryKey(Orm.GetEntityString(EntityType, entity)));
|
||||
|
||||
var flagExists = ExistsInStates(entity);
|
||||
if (flagExists == false)
|
||||
{
|
||||
var olddata = Select.WhereDynamic(entity).First();
|
||||
flagExists = olddata != null;
|
||||
}
|
||||
if (flagExists == true)
|
||||
{
|
||||
var affrows = Update(entity);
|
||||
return entity;
|
||||
}
|
||||
if (table.Primarys.Where(a => a.Attribute.IsIdentity).Count() == table.Primarys.Length)
|
||||
Orm.ClearEntityPrimaryValueWithIdentity(EntityType, entity);
|
||||
return Insert(entity);
|
||||
}
|
||||
|
||||
public virtual int Update(TEntity entity) => Update(new[] { entity });
|
||||
public virtual int Update(IEnumerable<TEntity> entitys)
|
||||
{
|
||||
var tracking = new AggregateRootTrackingChangeInfo();
|
||||
foreach(var entity in entitys)
|
||||
{
|
||||
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以更新数据 {Orm.GetEntityString(EntityType, entity)}");
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, null, tracking);
|
||||
}
|
||||
var affrows = SaveTrackingChange(tracking);
|
||||
foreach (var entity in entitys)
|
||||
Attach(entity);
|
||||
return affrows;
|
||||
}
|
||||
|
||||
public virtual int Delete(TEntity entity) => DeleteWithinBoundary(new[] { entity }, null);
|
||||
public virtual int Delete(IEnumerable<TEntity> entitys) => DeleteWithinBoundary(entitys, null);
|
||||
public virtual int Delete(Expression<Func<TEntity, bool>> predicate) => DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), null);
|
||||
public virtual List<object> DeleteCascadeByDatabase(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
var deletedOutput = new List<object>();
|
||||
DeleteWithinBoundary(SelectAggregateRoot.Where(predicate).ToList(), deletedOutput);
|
||||
return deletedOutput;
|
||||
}
|
||||
int DeleteWithinBoundary(IEnumerable<TEntity> entitys, List<object> deletedOutput)
|
||||
{
|
||||
var tracking = new AggregateRootTrackingChangeInfo();
|
||||
foreach (var entity in entitys)
|
||||
{
|
||||
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, entity, null, null, tracking);
|
||||
_states.Remove(stateKey);
|
||||
}
|
||||
var affrows = 0;
|
||||
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
|
||||
{
|
||||
var delete = Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1);
|
||||
if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old));
|
||||
affrows += delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows();
|
||||
if (deletedOutput != null) deletedOutput.AddRange(tracking.DeleteLog[a].Item2);
|
||||
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
|
||||
new DbContext.EntityChangeReport.ChangeInfo
|
||||
{
|
||||
Type = DbContext.EntityChangeType.Delete,
|
||||
EntityType = tracking.DeleteLog[a].Item1,
|
||||
Object = x
|
||||
}));
|
||||
}
|
||||
return affrows;
|
||||
}
|
||||
|
||||
public virtual void SaveMany(TEntity entity, string propertyName)
|
||||
{
|
||||
var tracking = new AggregateRootTrackingChangeInfo();
|
||||
var stateKey = Orm.GetEntityKeyString(EntityType, entity, false);
|
||||
if (_states.TryGetValue(stateKey, out var state) == false) throw new Exception($"AggregateRootRepository 使用仓储对象查询后,才可以保存数据 {Orm.GetEntityString(EntityType, entity)}");
|
||||
AggregateRootUtils.CompareEntityValue(_boundaryName, Orm, EntityType, state.Value, entity, propertyName, tracking);
|
||||
SaveTrackingChange(tracking);
|
||||
Attach(entity); //应该只存储 propertyName 内容
|
||||
}
|
||||
|
||||
|
||||
int SaveTrackingChange(AggregateRootTrackingChangeInfo tracking)
|
||||
{
|
||||
var affrows = 0;
|
||||
DisposeChildRepositorys();
|
||||
var insertLogDict = tracking.InsertLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.InsertLog.Where(b => b.Item1 == a.Key).Select(b => b.Item2).ToArray());
|
||||
foreach (var il in insertLogDict)
|
||||
{
|
||||
var repo = GetChildRepository(il.Key);
|
||||
InsertWithinBoundaryStatic(_boundaryName, repo, GetChildRepository, il.Value, out var affrowsOut);
|
||||
affrows += affrowsOut;
|
||||
}
|
||||
|
||||
for (var a = tracking.DeleteLog.Count - 1; a >= 0; a--)
|
||||
{
|
||||
var delete = Orm.Delete<object>().AsType(tracking.DeleteLog[a].Item1);
|
||||
if (_asTableRule != null) delete.AsTable(old => _asTableRule(tracking.DeleteLog[a].Item1, old));
|
||||
affrows += delete.WhereDynamic(tracking.DeleteLog[a].Item2).ExecuteAffrows();
|
||||
UnitOfWork?.EntityChangeReport?.Report.AddRange(tracking.DeleteLog[a].Item2.Select(x =>
|
||||
new DbContext.EntityChangeReport.ChangeInfo
|
||||
{
|
||||
Type = DbContext.EntityChangeType.Delete,
|
||||
EntityType = tracking.DeleteLog[a].Item1,
|
||||
Object = x
|
||||
}));
|
||||
}
|
||||
|
||||
if (_repository.DbContextOptions.AuditValue != null)
|
||||
{
|
||||
foreach (var log in tracking.UpdateLog)
|
||||
{
|
||||
var table = Orm.CodeFirst.GetTableByEntity(log.Item1);
|
||||
_repository.DbContextOptions.AuditValue(new DbContextAuditValueEventArgs(Aop.AuditValueType.Update, log.Item1, log.Item3));
|
||||
log.Item4.Clear();
|
||||
foreach (var col in table.ColumnsByCs.Values)
|
||||
{
|
||||
if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue;
|
||||
if (table.ColumnsByCs.ContainsKey(col.CsName))
|
||||
{
|
||||
if (col.Attribute.IsVersion) continue;
|
||||
var propvalBefore = table.GetPropertyValue(log.Item2, col.CsName);
|
||||
var propvalAfter = table.GetPropertyValue(log.Item3, col.CsName);
|
||||
if (AggregateRootUtils.CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) log.Item4.Add(col.CsName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var updateLogDict = tracking.UpdateLog.GroupBy(a => a.Item1).ToDictionary(a => a.Key, a => tracking.UpdateLog.Where(b => b.Item1 == a.Key && b.Item4.Any()).Select(b => new
|
||||
{
|
||||
BeforeObject = b.Item2,
|
||||
AfterObject = b.Item3,
|
||||
UpdateColumns = b.Item4,
|
||||
UpdateColumnsString = string.Join(",", b.Item4.OrderBy(c => c))
|
||||
}).ToArray());
|
||||
var updateLogDict2 = updateLogDict.ToDictionary(a => a.Key, a =>
|
||||
a.Value.GroupBy(b => b.UpdateColumnsString).ToDictionary(b => b.Key, b => a.Value.Where(c => c.UpdateColumnsString == b.Key).ToArray()));
|
||||
foreach (var dl in updateLogDict2)
|
||||
{
|
||||
foreach (var dl2 in dl.Value)
|
||||
{
|
||||
var update = Orm.Update<object>().AsType(dl.Key);
|
||||
if (_asTableRule != null) update.AsTable(old => _asTableRule(dl.Key, old));
|
||||
affrows += update
|
||||
.SetSource(dl2.Value.Select(a => a.AfterObject).ToArray())
|
||||
.UpdateColumns(dl2.Value.First().UpdateColumns.ToArray())
|
||||
.ExecuteAffrows();
|
||||
UnitOfWork?.EntityChangeReport?.Report.AddRange(dl2.Value.Select(x =>
|
||||
new DbContext.EntityChangeReport.ChangeInfo
|
||||
{
|
||||
Type = DbContext.EntityChangeType.Update,
|
||||
EntityType = dl.Key,
|
||||
Object = x.AfterObject,
|
||||
BeforeObject = x.BeforeObject
|
||||
}));
|
||||
}
|
||||
}
|
||||
DisposeChildRepositorys();
|
||||
return affrows;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,656 @@
|
||||
using FreeSql;
|
||||
using FreeSql.DataAnnotations;
|
||||
using FreeSql.Extensions.EntityUtil;
|
||||
using FreeSql.Internal;
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public class AggregateRootUtils
|
||||
{
|
||||
static ConcurrentDictionary<PropertyInfo, ConcurrentDictionary<string, AggregateRootBoundaryAttribute>> _dicGetPropertyBoundaryAttribute = new ConcurrentDictionary<PropertyInfo, ConcurrentDictionary<string, AggregateRootBoundaryAttribute>>();
|
||||
public static AggregateRootBoundaryAttribute GetPropertyBoundaryAttribute(PropertyInfo prop, string boundaryName)
|
||||
{
|
||||
if (boundaryName == null) return null;
|
||||
return _dicGetPropertyBoundaryAttribute.GetOrAdd(prop, tp => new ConcurrentDictionary<string, AggregateRootBoundaryAttribute>())
|
||||
.GetOrAdd(boundaryName, bn =>
|
||||
{
|
||||
var attrs = prop.GetCustomAttributes(typeof(AggregateRootBoundaryAttribute), false);
|
||||
if (attrs == null || attrs.Any() == false) return null;
|
||||
return attrs.Select(a => a as AggregateRootBoundaryAttribute).Where(a => a.Name == bn).FirstOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
public static void CompareEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityBefore, object rootEntityAfter, string rootNavigatePropertyName, AggregateRootTrackingChangeInfo tracking)
|
||||
{
|
||||
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||
LocalCompareEntityValue(rootEntityType, rootEntityBefore, rootEntityAfter, rootNavigatePropertyName, true);
|
||||
ignores.Clear();
|
||||
|
||||
void LocalCompareEntityValue(Type entityType, object entityBefore, object entityAfter, string navigatePropertyName, bool cascade)
|
||||
{
|
||||
if (entityType == null) entityType = entityBefore?.GetType() ?? entityAfter?.GetType();
|
||||
|
||||
if (entityBefore != null)
|
||||
{
|
||||
var stateKey = $":before://{fsql.GetEntityKeyString(entityType, entityBefore, false)}";
|
||||
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||
if (stateKeys.ContainsKey(stateKey)) return;
|
||||
stateKeys.Add(stateKey, true);
|
||||
}
|
||||
if (entityAfter != null)
|
||||
{
|
||||
var stateKey = $":after://{fsql.GetEntityKeyString(entityType, entityAfter, false)}";
|
||||
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||
if (stateKeys.ContainsKey(stateKey)) return;
|
||||
stateKeys.Add(stateKey, true);
|
||||
}
|
||||
|
||||
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||
if (table == null) return;
|
||||
if (entityBefore == null && entityAfter == null) return;
|
||||
if (entityBefore == null && entityAfter != null)
|
||||
{
|
||||
tracking.InsertLog.Add(NativeTuple.Create(entityType, entityAfter));
|
||||
return;
|
||||
}
|
||||
if (entityBefore != null && entityAfter == null)
|
||||
{
|
||||
tracking.DeleteLog.Add(NativeTuple.Create(entityType, new[] { entityBefore }));
|
||||
NavigateReader(boundaryName, fsql, entityType, entityBefore, (path, tr, ct, stackvs) =>
|
||||
{
|
||||
var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() };
|
||||
tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist));
|
||||
});
|
||||
return;
|
||||
}
|
||||
var changes = new List<string>();
|
||||
foreach (var col in table.ColumnsByCs.Values)
|
||||
{
|
||||
if (table.ColumnsByCsIgnore.ContainsKey(col.CsName)) continue;
|
||||
if (table.ColumnsByCs.ContainsKey(col.CsName))
|
||||
{
|
||||
if (col.Attribute.IsVersion) continue;
|
||||
var propvalBefore = table.GetPropertyValue(entityBefore, col.CsName);
|
||||
var propvalAfter = table.GetPropertyValue(entityAfter, col.CsName);
|
||||
//if (object.Equals(propvalBefore, propvalAfter) == false) changes.Add(col.CsName);
|
||||
if (CompareEntityPropertyValue(col.CsType, propvalBefore, propvalAfter) == false) changes.Add(col.CsName);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (changes.Any()) tracking.UpdateLog.Add(NativeTuple.Create(entityType, entityBefore, entityAfter, changes));
|
||||
if (cascade == false) return;
|
||||
|
||||
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||
{
|
||||
var tbref = tr.Value;
|
||||
if (tbref.Exception != null) continue;
|
||||
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||
if (boundaryAttr?.Break == true) continue;
|
||||
if (navigatePropertyName != null && prop.Name != navigatePropertyName) continue;
|
||||
var propvalBefore = table.GetPropertyValue(entityBefore, prop.Name);
|
||||
var propvalAfter = table.GetPropertyValue(entityAfter, prop.Name);
|
||||
switch (tbref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore);
|
||||
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter);
|
||||
LocalCompareEntityValue(tbref.RefEntityType, propvalBefore, propvalAfter, null, boundaryAttr?.BreakThen != true);
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityBefore, propvalBefore);
|
||||
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityAfter, propvalAfter);
|
||||
LocalCompareEntityValueCollection(tbref.RefEntityType, propvalBefore as IEnumerable, propvalAfter as IEnumerable, boundaryAttr?.BreakThen != true);
|
||||
break;
|
||||
case TableRefType.ManyToMany:
|
||||
var middleValuesBefore = GetManyToManyObjects(fsql, table, tbref, entityBefore, prop);
|
||||
var middleValuesAfter = GetManyToManyObjects(fsql, table, tbref, entityAfter, prop);
|
||||
LocalCompareEntityValueCollection(tbref.RefMiddleEntityType, middleValuesBefore as IEnumerable, middleValuesAfter as IEnumerable, false);
|
||||
break;
|
||||
case TableRefType.PgArrayToMany:
|
||||
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void LocalCompareEntityValueCollection(Type elementType, IEnumerable collectionBefore, IEnumerable collectionAfter, bool cascade)
|
||||
{
|
||||
if (collectionBefore == null && collectionAfter == null) return;
|
||||
if (collectionBefore == null && collectionAfter != null)
|
||||
{
|
||||
foreach (var item in collectionAfter)
|
||||
tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
|
||||
return;
|
||||
}
|
||||
if (collectionBefore != null && collectionAfter == null)
|
||||
{
|
||||
//foreach (var item in collectionBefore)
|
||||
//{
|
||||
// changelog.DeleteLog.Add(NativeTuple.Create(elementType, new[] { item }));
|
||||
// NavigateReader(boundaryName, fsql, elementType, item, (path, tr, ct, stackvs) =>
|
||||
// {
|
||||
// var dellist = stackvs.Last() as object[] ?? new [] { stackvs.Last() };
|
||||
// changelog.DeleteLog.Add(NativeTuple.Create(ct, dellist));
|
||||
// });
|
||||
//}
|
||||
return;
|
||||
}
|
||||
var table = fsql.CodeFirst.GetTableByEntity(elementType);
|
||||
Dictionary<string, object> dictBefore = new Dictionary<string, object>();
|
||||
Dictionary<string, object> dictAfter = new Dictionary<string, object>();
|
||||
foreach (var item in collectionBefore)
|
||||
{
|
||||
var key = fsql.GetEntityKeyString(elementType, item, false);
|
||||
if (!string.IsNullOrEmpty(key)) dictBefore.Add(key, item);
|
||||
}
|
||||
foreach (var item in collectionAfter)
|
||||
{
|
||||
var key = fsql.GetEntityKeyString(elementType, item, false);
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
if (dictAfter.ContainsKey(key) == false)
|
||||
dictAfter.Add(key, item);
|
||||
else if (key == "0" && table.Primarys.Length == 1 &&
|
||||
new[] { typeof(long), typeof(int) }.Contains(table.Primarys[0].CsType))
|
||||
tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
|
||||
}
|
||||
else tracking.InsertLog.Add(NativeTuple.Create(elementType, item));
|
||||
}
|
||||
foreach (var key in dictBefore.Keys.ToArray())
|
||||
{
|
||||
if (dictAfter.ContainsKey(key) == false)
|
||||
{
|
||||
var value = dictBefore[key];
|
||||
tracking.DeleteLog.Add(NativeTuple.Create(elementType, new[] { value }));
|
||||
NavigateReader(boundaryName, fsql, elementType, value, (path, tr, ct, stackvs) =>
|
||||
{
|
||||
var dellist = stackvs.Last() as object[] ?? new[] { stackvs.Last() };
|
||||
tracking.DeleteLog.Add(NativeTuple.Create(ct, dellist));
|
||||
});
|
||||
dictBefore.Remove(key);
|
||||
}
|
||||
}
|
||||
foreach (var key in dictAfter.Keys.ToArray())
|
||||
{
|
||||
if (dictBefore.ContainsKey(key) == false)
|
||||
{
|
||||
tracking.InsertLog.Add(NativeTuple.Create(elementType, dictAfter[key]));
|
||||
dictAfter.Remove(key);
|
||||
}
|
||||
}
|
||||
foreach (var key in dictBefore.Keys)
|
||||
LocalCompareEntityValue(elementType, dictBefore[key], dictAfter[key], null, cascade);
|
||||
}
|
||||
}
|
||||
|
||||
static ConcurrentDictionary<Type, bool> _dicCompareEntityPropertyValue = new ConcurrentDictionary<Type, bool>
|
||||
{
|
||||
[typeof(string)] = true,
|
||||
[typeof(DateTime)] = true,
|
||||
[typeof(DateTime?)] = true,
|
||||
[typeof(DateTimeOffset)] = true,
|
||||
[typeof(DateTimeOffset?)] = true,
|
||||
[typeof(TimeSpan)] = true,
|
||||
[typeof(TimeSpan?)] = true,
|
||||
};
|
||||
public static bool CompareEntityPropertyValue(Type type, object propvalBefore, object propvalAfter)
|
||||
{
|
||||
if (propvalBefore == null && propvalAfter == null) return true;
|
||||
if (type.IsNumberType() ||
|
||||
_dicCompareEntityPropertyValue.ContainsKey(type) ||
|
||||
type.IsEnum ||
|
||||
type.IsValueType ||
|
||||
type.NullableTypeOrThis().IsEnum) return object.Equals(propvalBefore, propvalAfter);
|
||||
if (propvalBefore == null && propvalAfter != null) return false;
|
||||
if (propvalBefore != null && propvalAfter == null) return false;
|
||||
|
||||
if (FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(type)) {
|
||||
if (type.FullName.StartsWith("Newtonsoft."))
|
||||
return object.Equals(propvalBefore.ToString(), propvalAfter.ToString());
|
||||
|
||||
if (typeof(IDictionary).IsAssignableFrom(type))
|
||||
{
|
||||
var dictBefore = (propvalBefore as IDictionary);
|
||||
var dictAfter = (propvalAfter as IDictionary);
|
||||
if (dictBefore.Count != dictAfter.Count) return false;
|
||||
foreach (var key in dictBefore.Keys)
|
||||
{
|
||||
if (dictAfter.Contains(key) == false) return false;
|
||||
var valBefore = dictBefore[key];
|
||||
var valAfter = dictAfter[key];
|
||||
if (valBefore == null && valAfter == null) continue;
|
||||
if (valBefore == null && valAfter != null) return false;
|
||||
if (valBefore != null && valAfter == null) return false;
|
||||
if (CompareEntityPropertyValue(valBefore.GetType(), valBefore, valAfter) == false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.IsArrayOrList())
|
||||
{
|
||||
var enumableBefore = propvalBefore as IEnumerable;
|
||||
var enumableAfter = propvalAfter as IEnumerable;
|
||||
var itorBefore = enumableBefore.GetEnumerator();
|
||||
var itorAfter = enumableAfter.GetEnumerator();
|
||||
while (true)
|
||||
{
|
||||
var moveNextBefore = itorBefore.MoveNext();
|
||||
var moveNextAfter = itorAfter.MoveNext();
|
||||
if (moveNextBefore != moveNextAfter) return false;
|
||||
if (moveNextBefore == false) break;
|
||||
var currentBefore = itorBefore.Current;
|
||||
var currentAfter = itorAfter.Current;
|
||||
if (currentBefore == null && enumableAfter == null) continue;
|
||||
if (currentBefore == null && currentAfter != null) return false;
|
||||
if (currentBefore != null && currentAfter == null) return false;
|
||||
if (CompareEntityPropertyValue(currentBefore.GetType(), currentBefore, currentAfter) == false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.FullName.StartsWith("System.") ||
|
||||
type.FullName.StartsWith("Npgsql.") ||
|
||||
type.FullName.StartsWith("NetTopologySuite."))
|
||||
return object.Equals(propvalBefore, propvalAfter);
|
||||
|
||||
if (type.IsClass)
|
||||
{
|
||||
foreach (var prop in type.GetProperties())
|
||||
{
|
||||
var valBefore = prop.GetValue(propvalBefore, new object[0]);
|
||||
var valAfter = prop.GetValue(propvalAfter, new object[0]);
|
||||
if (CompareEntityPropertyValue(prop.PropertyType, valBefore, valAfter) == false) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return object.Equals(propvalBefore, propvalAfter);
|
||||
}
|
||||
|
||||
public static void NavigateReader(string boundaryName, IFreeSql fsql, Type rootType, object rootEntity, Action<string, TableRef, Type, List<object>> callback)
|
||||
{
|
||||
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||
var statckPath = new Stack<string>();
|
||||
var stackValues = new List<object>();
|
||||
statckPath.Push("_");
|
||||
stackValues.Add(rootEntity);
|
||||
LocalNavigateReader(rootType, rootEntity);
|
||||
ignores.Clear();
|
||||
|
||||
void LocalNavigateReader(Type entityType, object entity)
|
||||
{
|
||||
if (entity == null) return;
|
||||
if (entityType == null) entityType = entity.GetType();
|
||||
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||
if (table == null) return;
|
||||
|
||||
var stateKey = fsql.GetEntityKeyString(entityType, entity, false);
|
||||
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||
if (stateKeys.ContainsKey(stateKey)) return;
|
||||
stateKeys.Add(stateKey, true);
|
||||
|
||||
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||
{
|
||||
var tbref = tr.Value;
|
||||
if (tbref.Exception != null) continue;
|
||||
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||
if (boundaryAttr?.Break == true) continue;
|
||||
switch (tbref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
var propval = table.GetPropertyValue(entity, prop.Name);
|
||||
if (propval == null) continue;
|
||||
statckPath.Push(prop.Name);
|
||||
stackValues.Add(propval);
|
||||
SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propval);
|
||||
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues);
|
||||
if (boundaryAttr?.BreakThen != true)
|
||||
LocalNavigateReader(tbref.RefEntityType, propval);
|
||||
stackValues.RemoveAt(stackValues.Count - 1);
|
||||
statckPath.Pop();
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
var propvalOtm = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
|
||||
if (propvalOtm == null) continue;
|
||||
SetNavigateRelationshipValue(fsql, tbref, table.Type, entity, propvalOtm);
|
||||
var propvalOtmList = new List<object>();
|
||||
foreach (var val in propvalOtm)
|
||||
propvalOtmList.Add(val);
|
||||
statckPath.Push($"{prop.Name}[]");
|
||||
stackValues.Add(propvalOtmList.ToArray());
|
||||
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefEntityType, stackValues);
|
||||
if (boundaryAttr?.BreakThen != true)
|
||||
foreach (var val in propvalOtm)
|
||||
LocalNavigateReader(tbref.RefEntityType, val);
|
||||
stackValues.RemoveAt(stackValues.Count - 1);
|
||||
statckPath.Pop();
|
||||
break;
|
||||
case TableRefType.ManyToMany:
|
||||
var middleValues = GetManyToManyObjects(fsql, table, tbref, entity, prop)?.ToArray();
|
||||
if (middleValues == null) continue;
|
||||
statckPath.Push($"{prop.Name}[]");
|
||||
stackValues.Add(middleValues);
|
||||
callback?.Invoke(string.Join(".", statckPath), tbref, tbref.RefMiddleEntityType, stackValues);
|
||||
stackValues.RemoveAt(stackValues.Count - 1);
|
||||
statckPath.Pop();
|
||||
break;
|
||||
case TableRefType.PgArrayToMany:
|
||||
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapEntityValue(string boundaryName, IFreeSql fsql, Type rootEntityType, object rootEntityFrom, object rootEntityTo)
|
||||
{
|
||||
Dictionary<Type, Dictionary<string, bool>> ignores = new Dictionary<Type, Dictionary<string, bool>>();
|
||||
LocalMapEntityValue(rootEntityType, rootEntityFrom, rootEntityTo, true);
|
||||
ignores.Clear();
|
||||
|
||||
void LocalMapEntityValue(Type entityType, object entityFrom, object entityTo, bool cascade)
|
||||
{
|
||||
if (entityFrom == null || entityTo == null) return;
|
||||
if (entityType == null) entityType = entityFrom?.GetType() ?? entityTo?.GetType();
|
||||
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||
if (table == null) return;
|
||||
|
||||
var stateKey = fsql.GetEntityKeyString(entityType, entityFrom, false);
|
||||
if (ignores.TryGetValue(entityType, out var stateKeys) == false) ignores.Add(entityType, stateKeys = new Dictionary<string, bool>());
|
||||
if (stateKeys.ContainsKey(stateKey)) return;
|
||||
stateKeys.Add(stateKey, true);
|
||||
|
||||
foreach (var prop in table.Properties.Values)
|
||||
{
|
||||
if (table.ColumnsByCsIgnore.ContainsKey(prop.Name)) continue;
|
||||
if (table.ColumnsByCs.ContainsKey(prop.Name))
|
||||
{
|
||||
table.SetPropertyValue(entityTo, prop.Name, table.GetPropertyValue(entityFrom, prop.Name));
|
||||
continue;
|
||||
}
|
||||
if (cascade == false) continue;
|
||||
var tbref = table.GetTableRef(prop.Name, false, false);
|
||||
if (tbref == null) continue;
|
||||
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||
if (boundaryAttr?.Break == true) continue;
|
||||
var propvalFrom = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, entityType, entityFrom, prop.Name);
|
||||
if (propvalFrom == null)
|
||||
{
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, null);
|
||||
continue;
|
||||
}
|
||||
switch (tbref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
var propvalTo = tbref.RefEntityType.CreateInstanceGetDefaultValue();
|
||||
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom);
|
||||
LocalMapEntityValue(tbref.RefEntityType, propvalFrom, propvalTo, boundaryAttr?.BreakThen != true);
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo);
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
SetNavigateRelationshipValue(fsql, tbref, table.Type, entityFrom, propvalFrom);
|
||||
LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, boundaryAttr?.BreakThen != true);
|
||||
break;
|
||||
case TableRefType.ManyToMany:
|
||||
LocalMapEntityValueCollection(entityType, entityFrom, entityTo, tbref, propvalFrom as IEnumerable, prop, false);
|
||||
break;
|
||||
case TableRefType.PgArrayToMany:
|
||||
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void LocalMapEntityValueCollection(Type entityType, object entityFrom, object entityTo, TableRef tbref, IEnumerable propvalFrom, PropertyInfo prop, bool cascade)
|
||||
{
|
||||
var propvalTo = typeof(List<>).MakeGenericType(tbref.RefEntityType).CreateInstanceGetDefaultValue();
|
||||
var propvalToIList = propvalTo as IList;
|
||||
foreach (var fromItem in propvalFrom)
|
||||
{
|
||||
var toItem = tbref.RefEntityType.CreateInstanceGetDefaultValue();
|
||||
LocalMapEntityValue(tbref.RefEntityType, fromItem, toItem, cascade);
|
||||
propvalToIList.Add(toItem);
|
||||
}
|
||||
var propvalType = prop.PropertyType.GetGenericTypeDefinition();
|
||||
if (propvalType == typeof(List<>) || propvalType == typeof(ICollection<>))
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTo);
|
||||
else if (propvalType == typeof(ObservableCollection<>))
|
||||
{
|
||||
//var propvalTypeOcCtor = typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType).GetConstructor(new[] { typeof(List<>).MakeGenericType(tbref.RefEntityType) });
|
||||
var propvalTypeOc = Activator.CreateInstance(typeof(ObservableCollection<>).MakeGenericType(tbref.RefEntityType), new object[] { propvalTo });
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, entityType, entityTo, prop.Name, propvalTypeOc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ConcurrentDictionary<string, ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>> _dicGetAutoIncludeQuery = new ConcurrentDictionary<string, ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>>();
|
||||
public static ISelect<TEntity> GetAutoIncludeQuery<TEntity>(string boundaryName, ISelect<TEntity> select)
|
||||
{
|
||||
var select0p = select as Select0Provider;
|
||||
var table0Type = select0p._tables[0].Table.Type;
|
||||
var func = _dicGetAutoIncludeQuery.GetOrAdd(boundaryName ?? "", bn => new ConcurrentDictionary<Type, ConcurrentDictionary<Type, Action<ISelect0>>>())
|
||||
.GetOrAdd(typeof(TEntity), t => new ConcurrentDictionary<Type, Action<ISelect0>>())
|
||||
.GetOrAdd(table0Type, t =>
|
||||
{
|
||||
var parmExp1 = Expression.Parameter(typeof(ISelect0));
|
||||
var parmNavigateParameterExp = Expression.Parameter(typeof(TEntity), "a");
|
||||
var parmQueryExp = Expression.Convert(parmExp1, typeof(ISelect<>).MakeGenericType(typeof(TEntity)));
|
||||
var exp = LocalGetAutoIncludeQuery(parmQueryExp, 1, t, parmNavigateParameterExp, parmNavigateParameterExp, new Stack<Type>());
|
||||
return Expression.Lambda<Action<ISelect0>>(exp, parmExp1).Compile();
|
||||
});
|
||||
func(select);
|
||||
return select;
|
||||
Expression LocalGetAutoIncludeQuery(Expression queryExp, int depth, Type entityType, ParameterExpression navigateParameterExp, Expression navigatePathExp, Stack<Type> ignores)
|
||||
{
|
||||
if (ignores.Any(a => a == entityType)) return queryExp;
|
||||
ignores.Push(entityType);
|
||||
var table = select0p._orm.CodeFirst.GetTableByEntity(entityType);
|
||||
if (table == null) return queryExp;
|
||||
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||
{
|
||||
var tbref = tr.Value;
|
||||
if (tbref.Exception != null) continue;
|
||||
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||
if (boundaryAttr?.Break == true) continue;
|
||||
Expression navigateExp = Expression.MakeMemberAccess(navigatePathExp, prop);
|
||||
//var lambdaAlias = (char)((byte)'a' + (depth - 1));
|
||||
switch (tbref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
if (ignores.Any(a => a == tbref.RefEntityType)) break;
|
||||
LocalInclude(tbref, navigateExp);
|
||||
if (boundaryAttr?.BreakThen != true)
|
||||
queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores);
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen != true);
|
||||
break;
|
||||
case TableRefType.ManyToMany:
|
||||
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false);
|
||||
break;
|
||||
case TableRefType.PgArrayToMany:
|
||||
if (boundaryAttr?.Break == false)
|
||||
LocalIncludeMany(tbref, navigateExp, boundaryAttr?.BreakThen == false);
|
||||
break;
|
||||
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||
if (boundaryAttr?.Break == false)
|
||||
{
|
||||
LocalInclude(tbref, navigateExp);
|
||||
if (boundaryAttr?.BreakThen == false)
|
||||
queryExp = LocalGetAutoIncludeQuery(queryExp, depth, tbref.RefEntityType, navigateParameterExp, navigateExp, ignores);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
ignores.Pop();
|
||||
return queryExp;
|
||||
void LocalInclude(TableRef tbref, Expression exp)
|
||||
{
|
||||
var incMethod = queryExp.Type.GetMethod("Include");
|
||||
if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany.Replace("IncludeMany", "Include"));
|
||||
queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType),
|
||||
Expression.Lambda(typeof(Func<,>).MakeGenericType(entityType, tbref.RefEntityType), exp, navigateParameterExp));
|
||||
}
|
||||
void LocalIncludeMany(TableRef tbref, Expression exp, bool isthen)
|
||||
{
|
||||
var funcType = typeof(Func<,>).MakeGenericType(entityType, typeof(IEnumerable<>).MakeGenericType(tbref.RefEntityType));
|
||||
var navigateSelector = Expression.Lambda(funcType, exp, navigateParameterExp);
|
||||
var incMethod = queryExp.Type.GetMethod("IncludeMany");
|
||||
if (incMethod == null) throw new Exception(CoreStrings.RunTimeError_Reflection_IncludeMany);
|
||||
LambdaExpression navigateThen = null;
|
||||
var navigateThenType = typeof(Action<>).MakeGenericType(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType));
|
||||
var thenParameter = Expression.Parameter(typeof(ISelect<>).MakeGenericType(tbref.RefEntityType), "then");
|
||||
Expression paramQueryExp = thenParameter;
|
||||
var paramNavigateParameterExp = Expression.Parameter(tbref.RefEntityType, string.Concat((char)((byte)'a' + (depth - 1))));
|
||||
if (isthen) paramQueryExp = LocalGetAutoIncludeQuery(paramQueryExp, depth + 1, tbref.RefEntityType, paramNavigateParameterExp, paramNavigateParameterExp, ignores);
|
||||
navigateThen = Expression.Lambda(navigateThenType, paramQueryExp, thenParameter);
|
||||
queryExp = Expression.Call(queryExp, incMethod.MakeGenericMethod(tbref.RefEntityType), navigateSelector, navigateThen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetAutoIncludeQueryStaicCode(string boundaryName, IFreeSql fsql, Type rootEntityType)
|
||||
{
|
||||
return $"//fsql.Select<{rootEntityType.Name}>()\r\nSelectDiy{LocalGetAutoIncludeQueryStaicCode(1, rootEntityType, "", new Stack<Type>())}";
|
||||
string LocalGetAutoIncludeQueryStaicCode(int depth, Type entityType, string navigatePath, Stack<Type> ignores)
|
||||
{
|
||||
var code = new StringBuilder();
|
||||
if (ignores.Any(a => a == entityType)) return null;
|
||||
ignores.Push(entityType);
|
||||
var table = fsql.CodeFirst.GetTableByEntity(entityType);
|
||||
if (table == null) return null;
|
||||
if (!string.IsNullOrWhiteSpace(navigatePath)) navigatePath = $"{navigatePath}.";
|
||||
foreach (var tr in table.GetAllTableRef().OrderBy(a => a.Value.RefType).ThenBy(a => a.Key))
|
||||
{
|
||||
var tbref = tr.Value;
|
||||
if (tbref.Exception != null) continue;
|
||||
if (table.Properties.TryGetValue(tr.Key, out var prop) == false) continue;
|
||||
var boundaryAttr = GetPropertyBoundaryAttribute(prop, boundaryName);
|
||||
if (boundaryAttr?.Break == true) continue;
|
||||
var navigateExpression = $"{navigatePath}{tr.Key}";
|
||||
var depthTab = "".PadLeft(depth * 4);
|
||||
var lambdaAlias = (char)((byte)'a' + (depth - 1));
|
||||
var lambdaStr = $"{lambdaAlias} => {lambdaAlias}.";
|
||||
switch (tbref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
if (ignores.Any(a => a == tbref.RefEntityType)) break;
|
||||
code.Append("\r\n").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")");
|
||||
if (boundaryAttr?.BreakThen != true)
|
||||
code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores));
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
|
||||
if (boundaryAttr?.BreakThen != true)
|
||||
{
|
||||
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
|
||||
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
|
||||
}
|
||||
code.Append(")");
|
||||
break;
|
||||
case TableRefType.ManyToMany:
|
||||
code.Append("\r\n").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
|
||||
if (boundaryAttr?.BreakThen == false)
|
||||
{
|
||||
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
|
||||
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
|
||||
}
|
||||
code.Append(")");
|
||||
break;
|
||||
case TableRefType.PgArrayToMany:
|
||||
code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".IncludeMany(").Append(lambdaStr).Append(navigateExpression);
|
||||
if (boundaryAttr?.BreakThen == false)
|
||||
{
|
||||
var thencode = LocalGetAutoIncludeQueryStaicCode(depth + 1, tbref.RefEntityType, "", new Stack<Type>(ignores.ToArray()));
|
||||
if (thencode.Length > 0) code.Append(", then => then").Append(thencode);
|
||||
}
|
||||
code.Append(")");
|
||||
break;
|
||||
case TableRefType.ManyToOne: //ManyToOne、ManyToMany外部表、PgArrayToMany 不属于聚合根成员,可以查询,不能增删改
|
||||
code.Append("\r\n").Append(boundaryAttr != null ? "" : "//").Append(depthTab).Append(".Include(").Append(lambdaStr).Append(navigateExpression).Append(")");
|
||||
if (boundaryAttr?.BreakThen == false)
|
||||
code.Append(LocalGetAutoIncludeQueryStaicCode(depth, tbref.RefEntityType, navigateExpression, ignores));
|
||||
break;
|
||||
}
|
||||
}
|
||||
ignores.Pop();
|
||||
return code.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<object> GetManyToManyObjects(IFreeSql fsql, TableInfo table, TableRef tbref, object entity, PropertyInfo prop)
|
||||
{
|
||||
if (tbref.RefType != TableRefType.ManyToMany) return null;
|
||||
var rights = table.GetPropertyValue(entity, prop.Name) as IEnumerable;
|
||||
if (rights == null) return null;
|
||||
var middles = new List<object>();
|
||||
var leftpkvals = new object[tbref.Columns.Count];
|
||||
for (var x = 0; x < tbref.Columns.Count; x++)
|
||||
leftpkvals[x] = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, table.Type, entity, tbref.Columns[x].CsName));
|
||||
foreach (var right in rights)
|
||||
{
|
||||
var midval = tbref.RefMiddleEntityType.CreateInstanceGetDefaultValue();
|
||||
for (var x = 0; x < tbref.Columns.Count; x++)
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, leftpkvals[x]);
|
||||
|
||||
for (var x = tbref.Columns.Count; x < tbref.MiddleColumns.Count; x++)
|
||||
{
|
||||
var refcol = tbref.RefColumns[x - tbref.Columns.Count];
|
||||
var refval = EntityUtilExtensions.GetEntityValueWithPropertyName(fsql, tbref.RefEntityType, right, refcol.CsName);
|
||||
if (refval == refcol.CsType.CreateInstanceGetDefaultValue()) throw new Exception($"ManyToMany 关联对象的主键属性({tbref.RefEntityType.DisplayCsharp()}.{refcol.CsName})不能为空");
|
||||
refval = Utils.GetDataReaderValue(tbref.MiddleColumns[x].CsType, refval);
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(fsql, tbref.RefMiddleEntityType, midval, tbref.MiddleColumns[x].CsName, refval);
|
||||
}
|
||||
middles.Add(midval);
|
||||
}
|
||||
return middles;
|
||||
}
|
||||
|
||||
public static void SetNavigateRelationshipValue(IFreeSql orm, TableRef tbref, Type leftType, object leftItem, object rightItem)
|
||||
{
|
||||
switch (tbref.RefType)
|
||||
{
|
||||
case TableRefType.OneToOne:
|
||||
if (rightItem == null) return;
|
||||
for (var idx = 0; idx < tbref.Columns.Count; idx++)
|
||||
{
|
||||
var colval = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName));
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName, colval);
|
||||
}
|
||||
break;
|
||||
case TableRefType.OneToMany:
|
||||
if (rightItem == null) return;
|
||||
var rightEachOtm = rightItem as IEnumerable;
|
||||
if (rightEachOtm == null) break;
|
||||
var leftColValsOtm = new object[tbref.Columns.Count];
|
||||
for (var idx = 0; idx < tbref.Columns.Count; idx++)
|
||||
leftColValsOtm[idx] = Utils.GetDataReaderValue(tbref.RefColumns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName));
|
||||
foreach (var rightEle in rightEachOtm)
|
||||
for (var idx = 0; idx < tbref.Columns.Count; idx++)
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightEle, tbref.RefColumns[idx].CsName, leftColValsOtm[idx]);
|
||||
break;
|
||||
case TableRefType.ManyToOne:
|
||||
for (var idx = 0; idx < tbref.RefColumns.Count; idx++)
|
||||
{
|
||||
var colval = rightItem == null ?
|
||||
tbref.Columns[idx].CsType.CreateInstanceGetDefaultValue() :
|
||||
Utils.GetDataReaderValue(tbref.Columns[idx].CsType, EntityUtilExtensions.GetEntityValueWithPropertyName(orm, tbref.RefEntityType, rightItem, tbref.RefColumns[idx].CsName));
|
||||
EntityUtilExtensions.SetEntityValueWithPropertyName(orm, leftType, leftItem, tbref.Columns[idx].CsName, colval);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using FreeSql;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
public static class FreeSqlAggregateRootRepositoryGlobalExtensions
|
||||
{
|
||||
public static IBaseRepository<TEntity> GetAggregateRootRepository<TEntity>(this IFreeSql that) where TEntity : class
|
||||
{
|
||||
return new AggregateRootRepository<TEntity>(that);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||
<Description>FreeSql 扩展包,聚合根(实现室).</Description>
|
||||
<PackageProjectUrl>https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/dotnetcore/FreeSql/wiki/%E8%81%9A%E5%90%88%E6%A0%B9%EF%BC%88%E5%AE%9E%E9%AA%8C%E5%AE%A4%EF%BC%89</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageTags>FreeSql;ORM</PackageTags>
|
||||
<PackageId>$(AssemblyName)</PackageId>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
<Title>$(AssemblyName)</Title>
|
||||
<IsPackable>true</IsPackable>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<DelaySign>false</DelaySign>
|
||||
<Version>3.2.833</Version>
|
||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../readme.md" Pack="true" PackagePath="\" />
|
||||
<None Include="../../logo.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
|
||||
<DocumentationFile>FreeSql.Extensions.AggregateRoot.xml</DocumentationFile>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
|
||||
<DefineConstants>net40</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>FreeSql.Extensions.AggregateRoot</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute">
|
||||
<summary>
|
||||
设置 AggregateRootRepository 边界范围<para></para>
|
||||
在边界范围之内的规则 :<para></para>
|
||||
1、OneToOne/OneToMany/ManyToMany(中间表) 可以查询、可以增删改<para></para>
|
||||
2、ManyToOne/ManyToMany外部表/PgArrayToMany 只可以查询,不支持增删改(会被忽略)<para></para>
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute.Break">
|
||||
<summary>
|
||||
边界是否终止
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.DataAnnotations.AggregateRootBoundaryAttribute.BreakThen">
|
||||
<summary>
|
||||
边界是否终止向下探测
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.AggregateRootRepository`1.Select">
|
||||
<summary>
|
||||
默认:创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)<para></para>
|
||||
重写:使用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.AggregateRootRepository`1.SelectDiy">
|
||||
<summary>
|
||||
创建查询对象(纯净)<para></para>
|
||||
_<para></para>
|
||||
聚合根内关系较复杂时,获取 Include/IncludeMany 字符串代码,方便二次开发<para></para>
|
||||
string code = AggregateRootUtils.GetAutoIncludeQueryStaicCode(null, fsql, typeof(Order))
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.AggregateRootRepository`1.SelectAggregateRoot">
|
||||
<summary>
|
||||
创建查询对象(递归包含 Include/IncludeMany 边界之内的导航属性)
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.AggregateRootRepository`1.SelectAggregateRootTracking(System.Object)">
|
||||
<summary>
|
||||
ISelect.TrackToList 委托,数据返回后自动 Attach
|
||||
</summary>
|
||||
<param name="list"></param>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
BIN
Extensions/FreeSql.Extensions.AggregateRoot/key.snk
Normal file
BIN
Extensions/FreeSql.Extensions.AggregateRoot/key.snk
Normal file
Binary file not shown.
179
Extensions/FreeSql.Extensions.BaseEntity/BaseEntity.cs
Normal file
179
Extensions/FreeSql.Extensions.BaseEntity/BaseEntity.cs
Normal file
@ -0,0 +1,179 @@
|
||||
#if NET40
|
||||
using FreeSql.DataAnnotations;
|
||||
using System;
|
||||
|
||||
#else
|
||||
using FreeSql.DataAnnotations;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#endif
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace FreeSql
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity base class, including CreateTime/UpdateTime/IsDeleted, the CRUD methods, and ID primary key definition.
|
||||
/// <para></para>
|
||||
/// 包括 CreateTime/UpdateTime/IsDeleted、CRUD 方法、以及 ID 主键定义 的实体基类
|
||||
/// <para></para>
|
||||
/// When TKey is int/long, the Id is set to be an auto-incremented primary key
|
||||
/// <para></para>
|
||||
/// 当 TKey 为 int/long 时,Id 主键被设为自增值主键
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
[Table(DisableSyncStructure = true)]
|
||||
public abstract class BaseEntity<TEntity, TKey> : BaseEntity<TEntity> where TEntity : class
|
||||
{
|
||||
static BaseEntity()
|
||||
{
|
||||
var keyType = typeof(TKey).NullableTypeOrThis();
|
||||
if (keyType == typeof(int) || keyType == typeof(long))
|
||||
ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Primary key <br />
|
||||
/// 主键
|
||||
/// </summary>
|
||||
[Column(Position = 1)]
|
||||
public virtual TKey Id { get; set; }
|
||||
|
||||
#if !NET40
|
||||
/// <summary>
|
||||
/// Get data based on the value of the primary key <br />
|
||||
/// 根据主键值获取数据
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<TEntity> FindAsync(TKey id)
|
||||
{
|
||||
var item = await Select.WhereDynamic(id).FirstAsync();
|
||||
(item as BaseEntity<TEntity>)?.Attach();
|
||||
return item;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Get data based on the value of the primary key <br />
|
||||
/// 根据主键值获取数据
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public static TEntity Find(TKey id)
|
||||
{
|
||||
var item = Select.WhereDynamic(id).First();
|
||||
(item as BaseEntity<TEntity>)?.Attach();
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity base class, including CreateTime/UpdateTime/IsDeleted, and sync/async CRUD methods.
|
||||
/// <para></para>
|
||||
/// 包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步和同步方法的实体基类
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
[Table(DisableSyncStructure = true)]
|
||||
public abstract class BaseEntity<TEntity> : BaseEntityAsync<TEntity> where TEntity : class
|
||||
{
|
||||
bool UpdateIsDeleted(bool value)
|
||||
{
|
||||
if (Repository == null)
|
||||
return Orm.Update<TEntity>(this as TEntity)
|
||||
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
|
||||
.Set(a => (a as BaseEntity).IsDeleted, IsDeleted = value)
|
||||
.ExecuteAffrows() == 1;
|
||||
|
||||
IsDeleted = value;
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return Repository.Update(this as TEntity) == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To delete data <br />
|
||||
/// 删除数据
|
||||
/// </summary>
|
||||
/// <param name="physicalDelete">To flag whether to delete the physical level of the data</param>
|
||||
/// <returns></returns>
|
||||
public virtual bool Delete(bool physicalDelete = false)
|
||||
{
|
||||
if (physicalDelete == false)
|
||||
return UpdateIsDeleted(true);
|
||||
|
||||
if (Repository == null)
|
||||
return Orm.Delete<TEntity>(this as TEntity)
|
||||
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
|
||||
.ExecuteAffrows() == 1;
|
||||
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return Repository.Delete(this as TEntity) == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To recover deleted data <br />
|
||||
/// 恢复删除的数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool Restore() => UpdateIsDeleted(false);
|
||||
|
||||
/// <summary>
|
||||
/// To update data <br />
|
||||
/// 更新数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool Update()
|
||||
{
|
||||
UpdateTime = DateTime.Now;
|
||||
if (Repository == null)
|
||||
return Orm.Update<TEntity>()
|
||||
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
|
||||
.SetSource(this as TEntity)
|
||||
.ExecuteAffrows() == 1;
|
||||
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return Repository.Update(this as TEntity) == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To insert data <br />
|
||||
/// 插入数据
|
||||
/// </summary>
|
||||
public virtual TEntity Insert()
|
||||
{
|
||||
CreateTime = DateTime.Now;
|
||||
if (Repository == null)
|
||||
Repository = Orm.GetRepository<TEntity>();
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return Repository.Insert(this as TEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To insert or update data <br />
|
||||
/// 更新或插入
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual TEntity Save()
|
||||
{
|
||||
UpdateTime = DateTime.Now;
|
||||
if (Repository == null)
|
||||
Repository = Orm.GetRepository<TEntity>();
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return Repository.InsertOrUpdate(this as TEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To completely save the navigation properties of the entity in the form of sub-tables. <br />
|
||||
/// 【完整】保存导航属性,子表
|
||||
/// </summary>
|
||||
/// <param name="navigatePropertyName">Navigation property name</param>
|
||||
public virtual void SaveMany(string navigatePropertyName)
|
||||
{
|
||||
if (Repository == null)
|
||||
Repository = Orm.GetRepository<TEntity>();
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
Repository.SaveMany(this as TEntity, navigatePropertyName);
|
||||
}
|
||||
}
|
||||
}
|
167
Extensions/FreeSql.Extensions.BaseEntity/BaseEntityAsync.cs
Normal file
167
Extensions/FreeSql.Extensions.BaseEntity/BaseEntityAsync.cs
Normal file
@ -0,0 +1,167 @@
|
||||
#if NET40
|
||||
using FreeSql.DataAnnotations;
|
||||
|
||||
#else
|
||||
using FreeSql.DataAnnotations;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#endif
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace FreeSql
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity base class, including CreateTime/UpdateTime/IsDeleted, the async CRUD methods, and ID primary key definition.
|
||||
/// <para></para>
|
||||
/// 包括 CreateTime/UpdateTime/IsDeleted、CRUD 异步方法、以及 ID 主键定义 的实体基类
|
||||
/// <para></para>
|
||||
/// When TKey is int/long, the Id is set to be an auto-incremented primary key
|
||||
/// <para></para>
|
||||
/// 当 TKey 为 int/long 时,Id 主键被设为自增值主键
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
[Table(DisableSyncStructure = true)]
|
||||
public abstract class BaseEntityAsync<TEntity, TKey> : BaseEntityAsync<TEntity> where TEntity : class
|
||||
{
|
||||
static BaseEntityAsync()
|
||||
{
|
||||
var keyType = typeof(TKey).NullableTypeOrThis();
|
||||
if (keyType == typeof(int) || keyType == typeof(long))
|
||||
ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Primary key <br />
|
||||
/// 主键
|
||||
/// </summary>
|
||||
[Column(Position = 1)]
|
||||
public virtual TKey Id { get; set; }
|
||||
|
||||
#if !NET40
|
||||
/// <summary>
|
||||
/// Get data based on the value of the primary key <br />
|
||||
/// 根据主键值获取数据
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<TEntity> FindAsync(TKey id)
|
||||
{
|
||||
var item = await Select.WhereDynamic(id).FirstAsync();
|
||||
(item as BaseEntity<TEntity>)?.Attach();
|
||||
return item;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity base class, including CreateTime/UpdateTime/IsDeleted, and async CRUD methods.
|
||||
/// <para></para>
|
||||
/// 包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步方法的实体基类
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
[Table(DisableSyncStructure = true)]
|
||||
public abstract class BaseEntityAsync<TEntity> : BaseEntityReadOnly<TEntity> where TEntity : class
|
||||
{
|
||||
#if !NET40
|
||||
async Task<bool> UpdateIsDeletedAsync(bool value)
|
||||
{
|
||||
if (Repository == null)
|
||||
return await Orm.Update<TEntity>(this as TEntity)
|
||||
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
|
||||
.Set(a => (a as BaseEntity).IsDeleted, IsDeleted = value)
|
||||
.ExecuteAffrowsAsync() == 1;
|
||||
|
||||
IsDeleted = value;
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return await Repository.UpdateAsync(this as TEntity) == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To delete data <br />
|
||||
/// 删除数据
|
||||
/// </summary>
|
||||
/// <param name="physicalDelete">To flag whether to delete the physical level of the data</param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> DeleteAsync(bool physicalDelete = false)
|
||||
{
|
||||
if (physicalDelete == false)
|
||||
return await UpdateIsDeletedAsync(true);
|
||||
|
||||
if (Repository == null)
|
||||
return await Orm.Delete<TEntity>(this as TEntity)
|
||||
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
|
||||
.ExecuteAffrowsAsync() == 1;
|
||||
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return await Repository.DeleteAsync(this as TEntity) == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To recover deleted data <br />
|
||||
/// 恢复删除的数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual Task<bool> RestoreAsync() => UpdateIsDeletedAsync(false);
|
||||
|
||||
/// <summary>
|
||||
/// To update data <br />
|
||||
/// 更新数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<bool> UpdateAsync()
|
||||
{
|
||||
UpdateTime = DateTime.Now;
|
||||
if (Repository == null)
|
||||
return await Orm.Update<TEntity>()
|
||||
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction())
|
||||
.SetSource(this as TEntity)
|
||||
.ExecuteAffrowsAsync() == 1;
|
||||
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return await Repository.UpdateAsync(this as TEntity) == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To insert data <br />
|
||||
/// 插入数据
|
||||
/// </summary>
|
||||
public virtual Task<TEntity> InsertAsync()
|
||||
{
|
||||
CreateTime = DateTime.Now;
|
||||
if (Repository == null)
|
||||
Repository = Orm.GetRepository<TEntity>();
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return Repository.InsertAsync(this as TEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To insert or update data <br />
|
||||
/// 更新或插入
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual Task<TEntity> SaveAsync()
|
||||
{
|
||||
UpdateTime = DateTime.Now;
|
||||
if (Repository == null)
|
||||
Repository = Orm.GetRepository<TEntity>();
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return Repository.InsertOrUpdateAsync(this as TEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To completely save the navigation properties of the entity in the form of sub-tables. <br />
|
||||
/// 【完整】保存导航属性,子表
|
||||
/// </summary>
|
||||
/// <param name="navigatePropertyName">Navigation property name</param>
|
||||
public virtual Task SaveManyAsync(string navigatePropertyName)
|
||||
{
|
||||
if (Repository == null)
|
||||
Repository = Orm.GetRepository<TEntity>();
|
||||
Repository.UnitOfWork = _resolveUow?.Invoke();
|
||||
return Repository.SaveManyAsync(this as TEntity, navigatePropertyName);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
215
Extensions/FreeSql.Extensions.BaseEntity/BaseEntityReadOnly.cs
Normal file
215
Extensions/FreeSql.Extensions.BaseEntity/BaseEntityReadOnly.cs
Normal file
@ -0,0 +1,215 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable InconsistentlySynchronizedField
|
||||
namespace FreeSql
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity base class, including CreateTime/UpdateTime/IsDeleted.
|
||||
/// <para></para>
|
||||
/// 包括 CreateTime/UpdateTime/IsDeleted 的实体基类
|
||||
/// </summary>
|
||||
[Table(DisableSyncStructure = true)]
|
||||
public abstract class BaseEntity
|
||||
{
|
||||
static Func<IFreeSql> _resoleOrm;
|
||||
internal static Func<IUnitOfWork> _resolveUow;
|
||||
|
||||
public static IFreeSql Orm => _resoleOrm?.Invoke() ?? throw new Exception(CoreStrings.S_BaseEntity_Initialization_Error);
|
||||
|
||||
public static void Initialization(IFreeSql fsql, Func<IUnitOfWork> resolveUow) => Initialization(() => fsql, resolveUow);
|
||||
public static void Initialization(Func<IFreeSql> resoleOrm, Func<IUnitOfWork> resolveUow)
|
||||
{
|
||||
_resoleOrm = resoleOrm;
|
||||
_resolveUow = resolveUow;
|
||||
|
||||
if (_configEntityQueues.Any())
|
||||
{
|
||||
lock (_configEntityLock)
|
||||
{
|
||||
while (_configEntityQueues.TryDequeue(out var cei))
|
||||
Orm.CodeFirst.ConfigEntity(cei.EntityType, cei.Fluent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigEntityInfo
|
||||
{
|
||||
public Type EntityType;
|
||||
public Action<TableFluent> Fluent;
|
||||
}
|
||||
|
||||
static readonly ConcurrentQueue<ConfigEntityInfo> _configEntityQueues = new ConcurrentQueue<ConfigEntityInfo>();
|
||||
static readonly object _configEntityLock = new object();
|
||||
|
||||
internal static void ConfigEntity(Type entityType, Action<TableFluent> fluent)
|
||||
{
|
||||
lock (_configEntityLock)
|
||||
{
|
||||
if (_resoleOrm?.Invoke() == null)
|
||||
_configEntityQueues.Enqueue(new ConfigEntityInfo { EntityType = entityType, Fluent = fluent });
|
||||
else
|
||||
Orm.CodeFirst.ConfigEntity(entityType, fluent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Created time <br />
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
[Column(Position = -4)]
|
||||
public virtual DateTime CreateTime { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// Updated time <br />
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
[Column(Position = -3)]
|
||||
public virtual DateTime UpdateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Logical Delete <br />
|
||||
/// 逻辑删除
|
||||
/// </summary>
|
||||
[Column(Position = -2)]
|
||||
public virtual bool IsDeleted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sort <br />
|
||||
/// 排序
|
||||
/// </summary>
|
||||
[Column(Position = -1)]
|
||||
public virtual int Sort { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A readonly entity base class, including CreateTime/UpdateTime/IsDeleted.
|
||||
/// <para></para>
|
||||
/// 包括 CreateTime/UpdateTime/IsDeleted 的只读实体基类
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
[Table(DisableSyncStructure = true)]
|
||||
public abstract class BaseEntityReadOnly<TEntity> : BaseEntity where TEntity : class
|
||||
{
|
||||
/// <summary>
|
||||
/// To query data <br />
|
||||
/// 查询数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static ISelect<TEntity> Select
|
||||
{
|
||||
get
|
||||
{
|
||||
var select = Orm.Select<TEntity>()
|
||||
.TrackToList(TrackToList) //自动为每个元素 Attach
|
||||
.WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction(false));
|
||||
return select.WhereCascade(a => (a as BaseEntity).IsDeleted == false);
|
||||
}
|
||||
}
|
||||
|
||||
static void TrackToList(object list)
|
||||
{
|
||||
if (list == null) return;
|
||||
|
||||
var ls = list as IList<TEntity>;
|
||||
if (ls == null)
|
||||
{
|
||||
var ie = list as IEnumerable;
|
||||
if (ie == null) return;
|
||||
|
||||
var isFirst = true;
|
||||
IBaseRepository<TEntity> baseRepo = null;
|
||||
|
||||
foreach (var item in ie)
|
||||
{
|
||||
if (item == null) return;
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
var itemType = item.GetType();
|
||||
if (itemType == typeof(object)) return;
|
||||
if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType;
|
||||
if (Orm.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return;
|
||||
if (itemType.GetConstructor(Type.EmptyTypes) == null) return;
|
||||
if (item is BaseEntity<TEntity> == false) return;
|
||||
}
|
||||
|
||||
if (item is BaseEntity<TEntity> entity)
|
||||
{
|
||||
if (baseRepo == null) baseRepo = Orm.GetRepository<TEntity>();
|
||||
entity.Repository = baseRepo;
|
||||
entity.Attach();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ls.Any() == false) return;
|
||||
if (ls.FirstOrDefault() is BaseEntity<TEntity> == false) return;
|
||||
if (Orm.CodeFirst.GetTableByEntity(typeof(TEntity))?.Primarys.Any() != true) return;
|
||||
|
||||
IBaseRepository<TEntity> repo = null;
|
||||
foreach (var item in ls)
|
||||
{
|
||||
if (item is BaseEntity<TEntity> entity)
|
||||
{
|
||||
if (repo == null) repo = Orm.GetRepository<TEntity>();
|
||||
entity.Repository = repo;
|
||||
entity.Attach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query conditions <br />
|
||||
/// 查询条件,Where(a => a.Id> 10)
|
||||
/// <para></para>
|
||||
/// Support navigation object query <br />
|
||||
/// 支持导航对象查询,Where(a => a.Author.Email == "2881099@qq.com")
|
||||
/// </summary>
|
||||
/// <param name="exp">lambda表达式</param>
|
||||
/// <returns></returns>
|
||||
public static ISelect<TEntity> Where(Expression<Func<TEntity, bool>> exp) => Select.Where(exp);
|
||||
|
||||
/// <summary>
|
||||
/// Query conditions <br />
|
||||
/// 查询条件,Where(true, a => a.Id > 10)
|
||||
/// <para></para>
|
||||
/// Support navigation object query <br />
|
||||
/// 支导航对象查询,Where(true, a => a.Author.Email == "2881099@qq.com")
|
||||
/// </summary>
|
||||
/// <param name="condition">true 时生效</param>
|
||||
/// <param name="exp">lambda表达式</param>
|
||||
/// <returns></returns>
|
||||
public static ISelect<TEntity> WhereIf(bool condition, Expression<Func<TEntity, bool>> exp) => Select.WhereIf(condition, exp);
|
||||
|
||||
/// <summary>
|
||||
/// Repository object. <br />
|
||||
/// 仓储对象
|
||||
/// </summary>
|
||||
protected IBaseRepository<TEntity> Repository { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// To Attach entities. When updating data, only the changed part is updated. <br />
|
||||
/// 附加实体。在更新数据时,只更新变化的部分
|
||||
/// </summary>
|
||||
public TEntity Attach()
|
||||
{
|
||||
if (Repository == null)
|
||||
Repository = Orm.GetRepository<TEntity>();
|
||||
var item = this as TEntity;
|
||||
Repository.Attach(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||
<Description>BaseEntity 是一种极简单的 CodeFirst 开发方式,特别对单表或多表CRUD,利用继承节省了每个实体类的重复属性(创建时间、ID等字段),软件删除等功能,进行 crud 操作时不必时常考虑仓储的使用.</Description>
|
||||
<PackageProjectUrl>https://github.com/dotnetcore/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/dotnetcore/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageTags>FreeSql;ORM;BaseEntity</PackageTags>
|
||||
<PackageId>$(AssemblyName)</PackageId>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
<Title>$(AssemblyName)</Title>
|
||||
<IsPackable>true</IsPackable>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<DelaySign>false</DelaySign>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>3.2.833</Version>
|
||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
|
||||
<None Include="../../logo.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
|
||||
<DocumentationFile>FreeSql.Extensions.BaseEntity.xml</DocumentationFile>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FreeSql.DbContext\FreeSql.DbContext.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
|
||||
<DefineConstants>net40</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,251 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>FreeSql.Extensions.BaseEntity</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:FreeSql.BaseEntity`2">
|
||||
<summary>
|
||||
Entity base class, including CreateTime/UpdateTime/IsDeleted, the CRUD methods, and ID primary key definition.
|
||||
<para></para>
|
||||
包括 CreateTime/UpdateTime/IsDeleted、CRUD 方法、以及 ID 主键定义 的实体基类
|
||||
<para></para>
|
||||
When TKey is int/long, the Id is set to be an auto-incremented primary key
|
||||
<para></para>
|
||||
当 TKey 为 int/long 时,Id 主键被设为自增值主键
|
||||
</summary>
|
||||
<typeparam name="TEntity"></typeparam>
|
||||
<typeparam name="TKey"></typeparam>
|
||||
</member>
|
||||
<member name="P:FreeSql.BaseEntity`2.Id">
|
||||
<summary>
|
||||
Primary key <br />
|
||||
主键
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntity`2.FindAsync(`1)">
|
||||
<summary>
|
||||
Get data based on the value of the primary key <br />
|
||||
根据主键值获取数据
|
||||
</summary>
|
||||
<param name="id"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntity`2.Find(`1)">
|
||||
<summary>
|
||||
Get data based on the value of the primary key <br />
|
||||
根据主键值获取数据
|
||||
</summary>
|
||||
<param name="id"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="T:FreeSql.BaseEntity`1">
|
||||
<summary>
|
||||
Entity base class, including CreateTime/UpdateTime/IsDeleted, and sync/async CRUD methods.
|
||||
<para></para>
|
||||
包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步和同步方法的实体基类
|
||||
</summary>
|
||||
<typeparam name="TEntity"></typeparam>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntity`1.Delete(System.Boolean)">
|
||||
<summary>
|
||||
To delete data <br />
|
||||
删除数据
|
||||
</summary>
|
||||
<param name="physicalDelete">To flag whether to delete the physical level of the data</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntity`1.Restore">
|
||||
<summary>
|
||||
To recover deleted data <br />
|
||||
恢复删除的数据
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntity`1.Update">
|
||||
<summary>
|
||||
To update data <br />
|
||||
更新数据
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntity`1.Insert">
|
||||
<summary>
|
||||
To insert data <br />
|
||||
插入数据
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntity`1.Save">
|
||||
<summary>
|
||||
To insert or update data <br />
|
||||
更新或插入
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntity`1.SaveMany(System.String)">
|
||||
<summary>
|
||||
To completely save the navigation properties of the entity in the form of sub-tables. <br />
|
||||
【完整】保存导航属性,子表
|
||||
</summary>
|
||||
<param name="navigatePropertyName">Navigation property name</param>
|
||||
</member>
|
||||
<member name="T:FreeSql.BaseEntityAsync`2">
|
||||
<summary>
|
||||
Entity base class, including CreateTime/UpdateTime/IsDeleted, the async CRUD methods, and ID primary key definition.
|
||||
<para></para>
|
||||
包括 CreateTime/UpdateTime/IsDeleted、CRUD 异步方法、以及 ID 主键定义 的实体基类
|
||||
<para></para>
|
||||
When TKey is int/long, the Id is set to be an auto-incremented primary key
|
||||
<para></para>
|
||||
当 TKey 为 int/long 时,Id 主键被设为自增值主键
|
||||
</summary>
|
||||
<typeparam name="TEntity"></typeparam>
|
||||
<typeparam name="TKey"></typeparam>
|
||||
</member>
|
||||
<member name="P:FreeSql.BaseEntityAsync`2.Id">
|
||||
<summary>
|
||||
Primary key <br />
|
||||
主键
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityAsync`2.FindAsync(`1)">
|
||||
<summary>
|
||||
Get data based on the value of the primary key <br />
|
||||
根据主键值获取数据
|
||||
</summary>
|
||||
<param name="id"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="T:FreeSql.BaseEntityAsync`1">
|
||||
<summary>
|
||||
Entity base class, including CreateTime/UpdateTime/IsDeleted, and async CRUD methods.
|
||||
<para></para>
|
||||
包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步方法的实体基类
|
||||
</summary>
|
||||
<typeparam name="TEntity"></typeparam>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityAsync`1.DeleteAsync(System.Boolean)">
|
||||
<summary>
|
||||
To delete data <br />
|
||||
删除数据
|
||||
</summary>
|
||||
<param name="physicalDelete">To flag whether to delete the physical level of the data</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityAsync`1.RestoreAsync">
|
||||
<summary>
|
||||
To recover deleted data <br />
|
||||
恢复删除的数据
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityAsync`1.UpdateAsync">
|
||||
<summary>
|
||||
To update data <br />
|
||||
更新数据
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityAsync`1.InsertAsync">
|
||||
<summary>
|
||||
To insert data <br />
|
||||
插入数据
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityAsync`1.SaveAsync">
|
||||
<summary>
|
||||
To insert or update data <br />
|
||||
更新或插入
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityAsync`1.SaveManyAsync(System.String)">
|
||||
<summary>
|
||||
To completely save the navigation properties of the entity in the form of sub-tables. <br />
|
||||
【完整】保存导航属性,子表
|
||||
</summary>
|
||||
<param name="navigatePropertyName">Navigation property name</param>
|
||||
</member>
|
||||
<member name="T:FreeSql.BaseEntity">
|
||||
<summary>
|
||||
Entity base class, including CreateTime/UpdateTime/IsDeleted.
|
||||
<para></para>
|
||||
包括 CreateTime/UpdateTime/IsDeleted 的实体基类
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.BaseEntity.CreateTime">
|
||||
<summary>
|
||||
Created time <br />
|
||||
创建时间
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.BaseEntity.UpdateTime">
|
||||
<summary>
|
||||
Updated time <br />
|
||||
更新时间
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.BaseEntity.IsDeleted">
|
||||
<summary>
|
||||
Logical Delete <br />
|
||||
逻辑删除
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.BaseEntity.Sort">
|
||||
<summary>
|
||||
Sort <br />
|
||||
排序
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FreeSql.BaseEntityReadOnly`1">
|
||||
<summary>
|
||||
A readonly entity base class, including CreateTime/UpdateTime/IsDeleted.
|
||||
<para></para>
|
||||
包括 CreateTime/UpdateTime/IsDeleted 的只读实体基类
|
||||
</summary>
|
||||
<typeparam name="TEntity"></typeparam>
|
||||
</member>
|
||||
<member name="P:FreeSql.BaseEntityReadOnly`1.Select">
|
||||
<summary>
|
||||
To query data <br />
|
||||
查询数据
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityReadOnly`1.Where(System.Linq.Expressions.Expression{System.Func{`0,System.Boolean}})">
|
||||
<summary>
|
||||
Query conditions <br />
|
||||
查询条件,Where(a => a.Id> 10)
|
||||
<para></para>
|
||||
Support navigation object query <br />
|
||||
支持导航对象查询,Where(a => a.Author.Email == "2881099@qq.com")
|
||||
</summary>
|
||||
<param name="exp">lambda表达式</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityReadOnly`1.WhereIf(System.Boolean,System.Linq.Expressions.Expression{System.Func{`0,System.Boolean}})">
|
||||
<summary>
|
||||
Query conditions <br />
|
||||
查询条件,Where(true, a => a.Id > 10)
|
||||
<para></para>
|
||||
Support navigation object query <br />
|
||||
支导航对象查询,Where(true, a => a.Author.Email == "2881099@qq.com")
|
||||
</summary>
|
||||
<param name="condition">true 时生效</param>
|
||||
<param name="exp">lambda表达式</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="P:FreeSql.BaseEntityReadOnly`1.Repository">
|
||||
<summary>
|
||||
Repository object. <br />
|
||||
仓储对象
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.BaseEntityReadOnly`1.Attach">
|
||||
<summary>
|
||||
To Attach entities. When updating data, only the changed part is updated. <br />
|
||||
附加实体。在更新数据时,只更新变化的部分
|
||||
</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
133
Extensions/FreeSql.Extensions.BaseEntity/README.MD
Normal file
133
Extensions/FreeSql.Extensions.BaseEntity/README.MD
Normal file
@ -0,0 +1,133 @@
|
||||
[中文](README.zh-CN.MD) | **English**
|
||||
|
||||
# Preface
|
||||
|
||||
I have tried ADO.NET, Dapper, EF, and Repository storage, and even wrote a generator tool myself to do common CRUD operations.
|
||||
|
||||
Their operation is inconvenient:
|
||||
|
||||
- Need to declare before use;
|
||||
|
||||
- Each entity class corresponds to an operation class (or DAL, DbContext, Repository).
|
||||
|
||||
BaseEntity is a very simple way of CodeFirst development, especially for single-table or multi-table CRUD operations. BaseEntity uses "inheritance" to save the repetitive code (creation time, ID and other fields) and functions of each entity class, and at the same time, it is not necessary to consider the use of repository when performing CURD operations.
|
||||
|
||||
|
||||
This article will introduce a very simple CRUD operation method of BaseEntity.
|
||||
|
||||
# Features
|
||||
|
||||
- Automatically migrate the entity structure (CodeFirst) to the database;
|
||||
|
||||
- Directly perform CRUD operations on entities;
|
||||
|
||||
- Simplify user-defined entity types, eliminating hard-coded primary keys, common fields and their configuration (such as CreateTime, UpdateTime);
|
||||
|
||||
- Logic delete of single-table and multi-table query;
|
||||
|
||||
# Declaring
|
||||
|
||||
> dotnet add package FreeSql.Extensions.BaseEntity
|
||||
|
||||
> dotnet add package FreeSql.Provider.Sqlite
|
||||
|
||||
```csharp
|
||||
BaseEntity.Initialization(fsql, null);
|
||||
```
|
||||
|
||||
1. Define an auto-increment primary key of type `int`. When the `TKey` of `BaseEntity` is specified as `int/long`, the primary key will be considered as auto-increment;
|
||||
|
||||
```csharp
|
||||
public class UserGroup : BaseEntity<UserGroup, int>
|
||||
{
|
||||
public string GroupName { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
If you don't want the primary key to be an auto-increment key, you can override the attribute:
|
||||
|
||||
```csharp
|
||||
public class UserGroup : BaseEntity<UserGroup, int>
|
||||
{
|
||||
[Column(IsIdentity = false)]
|
||||
public override int Id { get; set; }
|
||||
public string GroupName { get; set; }
|
||||
}
|
||||
```
|
||||
> For more information about the attributes of entities, please refer to: https://github.com/dotnetcore/FreeSql/wiki/Entity-Attributes
|
||||
|
||||
2. Define an entity whose primary key is Guid type, when saving data, it will automatically generate ordered and non-repeated Guid values (you don't need to specify `Guid.NewGuid()` yourself);
|
||||
|
||||
```csharp
|
||||
public class User : BaseEntity<UserGroup, Guid>
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
# Usage of CRUD
|
||||
|
||||
```csharp
|
||||
//Insert Data
|
||||
var item = new UserGroup { GroupName = "Group One" };
|
||||
item.Insert();
|
||||
|
||||
//Update Data
|
||||
item.GroupName = "Group Two";
|
||||
item.Update();
|
||||
|
||||
//Insert or Update Data
|
||||
item.Save();
|
||||
|
||||
//Logic Delete
|
||||
item.Delete();
|
||||
|
||||
//Recover Logic Delete
|
||||
item.Restore();
|
||||
|
||||
//Get the object by the primary key
|
||||
var item = UserGroup.Find(1);
|
||||
|
||||
//Query Data
|
||||
var items = UserGroup.Where(a => a.Id > 10).ToList();
|
||||
```
|
||||
|
||||
`{ENTITY_TYPE}.Select` returns a query object, the same as `FreeSql.ISelect`.
|
||||
|
||||
In the multi-table query, the logic delete condition will be attached to the query of each table.
|
||||
|
||||
> For more information about query data, please refer to: https://github.com/2881099/FreeSql/wiki/Query-Data
|
||||
|
||||
# Transaction Suggestion
|
||||
|
||||
Because the `AsyncLocal` platform is not compatible, the transaction is managed by the outside.
|
||||
|
||||
```csharp
|
||||
static AsyncLocal<IUnitOfWork> _asyncUow = new AsyncLocal<IUnitOfWork>();
|
||||
|
||||
BaseEntity.Initialization(fsql, () => _asyncUow.Value);
|
||||
```
|
||||
|
||||
At the beginning of `Scoped`: `_asyncUow.Value = fsql.CreateUnitOfWork();` (You can also use the `UnitOfWorkManager` object to get uow)
|
||||
|
||||
At the end of `Scoped`: `_asyncUow.Value = null;`
|
||||
|
||||
as follows:
|
||||
|
||||
```csharp
|
||||
using (var uow = fsql.CreateUnitOfWork())
|
||||
{
|
||||
_asyncUow.Value = uow;
|
||||
|
||||
try
|
||||
{
|
||||
//todo ... BaseEntity internal CURD method keeps using uow transaction
|
||||
}
|
||||
finally
|
||||
{
|
||||
_asyncUow.Value = null;
|
||||
}
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
```
|
143
Extensions/FreeSql.Extensions.BaseEntity/README.zh-CN.MD
Normal file
143
Extensions/FreeSql.Extensions.BaseEntity/README.zh-CN.MD
Normal file
@ -0,0 +1,143 @@
|
||||
**中文** | [English](README.MD)
|
||||
|
||||
# 前言
|
||||
|
||||
尝试过 ado.net、dapper、ef,以及Repository仓储,甚至自己还写过生成器工具,以便做常规CRUD操作。
|
||||
|
||||
它们日常操作不方便之处:
|
||||
|
||||
- 每次使用前需要声明,再操作;
|
||||
|
||||
- 很多人一个实体类,对应一个操作类(或DAL、DbContext、Repository);
|
||||
|
||||
BaseEntity 是一种极简单的 CodeFirst 开发方式,特别对单表或多表CRUD,利用继承节省了每个实体类的重复属性(创建时间、ID等字段),软件删除等功能,进行 crud 操作时不必时常考虑仓储的使用;
|
||||
|
||||
本文介绍 BaseEntity 一种极简约的 CRUD 操作方法。
|
||||
|
||||
# 功能特点
|
||||
|
||||
- 自动迁移实体结构(CodeFirst),到数据库;
|
||||
|
||||
- 直接操作实体的方法,进行 CRUD 操作;
|
||||
|
||||
- 简化用户定义实体类型,省去主键、常用字段的配置(如CreateTime、UpdateTime);
|
||||
|
||||
- 实现单表、多表查询的软删除逻辑;
|
||||
|
||||
# 声明
|
||||
|
||||
> dotnet add package FreeSql.Extensions.BaseEntity
|
||||
|
||||
> dotnet add package FreeSql.Provider.Sqlite
|
||||
|
||||
```csharp
|
||||
BaseEntity.Initialization(fsql, null);
|
||||
```
|
||||
|
||||
1、定义一个主键 int 并且自增的实体类型,BaseEntity TKey 指定为 int/long 时,会认为主键是自增;
|
||||
|
||||
```csharp
|
||||
public class UserGroup : BaseEntity<UserGroup, int>
|
||||
{
|
||||
public string GroupName { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
如果不想主键是自增键,可以重写属性:
|
||||
|
||||
```csharp
|
||||
public class UserGroup : BaseEntity<UserGroup, int>
|
||||
{
|
||||
[Column(IsIdentity = false)]
|
||||
public override int Id { get; set; }
|
||||
public string GroupName { get; set; }
|
||||
}
|
||||
```
|
||||
> 有关更多实体的特性配置,请参考资料:https://github.com/dotnetcore/FreeSql/wiki/%e5%ae%9e%e4%bd%93%e7%89%b9%e6%80%a7
|
||||
|
||||
2、定义一个主键 Guid 的实体类型,保存数据时会自动产生有序不重复的 Guid 值(不用自己指定 Guid.NewGuid());
|
||||
|
||||
```csharp
|
||||
public class User : BaseEntity<UserGroup, Guid>
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
# CRUD 使用
|
||||
|
||||
```csharp
|
||||
//添加
|
||||
var item = new UserGroup { GroupName = "组一" };
|
||||
item.Insert();
|
||||
|
||||
//更新
|
||||
item.GroupName = "组二";
|
||||
item.Update();
|
||||
|
||||
//添加或更新
|
||||
item.Save();
|
||||
|
||||
//软删除
|
||||
item.Delete();
|
||||
|
||||
//恢复软删除
|
||||
item.Restore();
|
||||
|
||||
//根据主键获取对象
|
||||
var item = UserGroup.Find(1);
|
||||
|
||||
//查询数据
|
||||
var items = UserGroup.Where(a => a.Id > 10).ToList();
|
||||
```
|
||||
|
||||
实体类型.Select 是一个查询对象,使用方法和 FreeSql.ISelect 一样;
|
||||
|
||||
支持多表查询时,软删除条件会附加在每个表中;
|
||||
|
||||
> 有关更多查询方法,请参考资料:https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2
|
||||
|
||||
# 事务建议
|
||||
|
||||
1、同线程事务,不支持异步:
|
||||
|
||||
```c#
|
||||
fsql.Transaction(() =>
|
||||
{
|
||||
//todo ...
|
||||
})
|
||||
```;
|
||||
|
||||
2、如果你是异步控
|
||||
|
||||
由于 AsyncLocal 平台兼容不好,所以交给外部管理事务。
|
||||
|
||||
```csharp
|
||||
static AsyncLocal<IUnitOfWork> _asyncUow = new AsyncLocal<IUnitOfWork>();
|
||||
|
||||
BaseEntity.Initialization(fsql, () => _asyncUow.Value);
|
||||
```
|
||||
|
||||
在 Scoped 开始时:_asyncUow.Value = fsql.CreateUnitOfWork(); (也可以使用 UnitOfWorkManager 对象获取 uow)
|
||||
|
||||
在 Scoped 结束时:_asyncUow.Value = null;
|
||||
|
||||
如下:
|
||||
|
||||
```csharp
|
||||
using (var uow = fsql.CreateUnitOfWork())
|
||||
{
|
||||
_asyncUow.Value = uow;
|
||||
|
||||
try
|
||||
{
|
||||
//todo ... BaseEntity 内部 curd 方法保持使用 uow 事务
|
||||
}
|
||||
finally
|
||||
{
|
||||
_asyncUow.Value = null;
|
||||
}
|
||||
|
||||
uow.Commit();
|
||||
}
|
||||
```
|
BIN
Extensions/FreeSql.Extensions.BaseEntity/key.snk
Normal file
BIN
Extensions/FreeSql.Extensions.BaseEntity/key.snk
Normal file
Binary file not shown.
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace FreeSql.DataAnnotations
|
||||
{
|
||||
/// <summary>
|
||||
/// When the entity class property is <see cref="object"/>, map storage in JSON format. <br />
|
||||
/// 当实体类属性为【对象】时,以 JSON 形式映射存储
|
||||
/// </summary>
|
||||
public class JsonMapAttribute : Attribute { }
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||
<Description>FreeSql 扩展包,可实现实体类属性为对象时,以JSON形式映射存储.</Description>
|
||||
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageTags>FreeSql;ORM</PackageTags>
|
||||
<PackageId>$(AssemblyName)</PackageId>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
<Title>$(AssemblyName)</Title>
|
||||
<IsPackable>true</IsPackable>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<DelaySign>false</DelaySign>
|
||||
<Version>3.2.833</Version>
|
||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
|
||||
<None Include="../../logo.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
|
||||
<DocumentationFile>FreeSql.Extensions.JsonMap.xml</DocumentationFile>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>FreeSql.Extensions.JsonMap</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:FreeSql.DataAnnotations.JsonMapAttribute">
|
||||
<summary>
|
||||
When the entity class property is <see cref="T:System.Object"/>, map storage in JSON format. <br />
|
||||
当实体类属性为【对象】时,以 JSON 形式映射存储
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSqlJsonMapCoreExtensions.UseJsonMap(IFreeSql)">
|
||||
<summary>
|
||||
When the entity class property is <see cref="T:System.Object"/> and the attribute is marked as <see cref="T:FreeSql.DataAnnotations.JsonMapAttribute"/>, map storage in JSON format. <br />
|
||||
当实体类属性为【对象】时,并且标记特性 [JsonMap] 时,该属性将以JSON形式映射存储
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
79
Extensions/FreeSql.Extensions.JsonMap/JsonMapCore.cs
Normal file
79
Extensions/FreeSql.Extensions.JsonMap/JsonMapCore.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
public static class FreeSqlJsonMapCoreExtensions
|
||||
{
|
||||
static int _isAoped = 0;
|
||||
static ConcurrentDictionary<Type, bool> _dicTypes = new ConcurrentDictionary<Type, bool>();
|
||||
static MethodInfo MethodJsonConvertDeserializeObject = typeof(JsonConvert).GetMethod("DeserializeObject", new[] { typeof(string), typeof(Type) });
|
||||
static MethodInfo MethodJsonConvertSerializeObject = typeof(JsonConvert).GetMethod("SerializeObject", new[] { typeof(object), typeof(JsonSerializerSettings) });
|
||||
static ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>> _dicJsonMapFluentApi = new ConcurrentDictionary<Type, ConcurrentDictionary<string, bool>>();
|
||||
static object _concurrentObj = new object();
|
||||
|
||||
public static ColumnFluent JsonMap(this ColumnFluent col)
|
||||
{
|
||||
_dicJsonMapFluentApi.GetOrAdd(col._entityType, et => new ConcurrentDictionary<string, bool>())
|
||||
.GetOrAdd(col._property.Name, pn => true);
|
||||
return col;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the entity class property is <see cref="object"/> and the attribute is marked as <see cref="JsonMapAttribute"/>, map storage in JSON format. <br />
|
||||
/// 当实体类属性为【对象】时,并且标记特性 [JsonMap] 时,该属性将以JSON形式映射存储
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static void UseJsonMap(this IFreeSql that)
|
||||
{
|
||||
UseJsonMap(that, JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings());
|
||||
}
|
||||
|
||||
public static void UseJsonMap(this IFreeSql that, JsonSerializerSettings settings)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _isAoped, 1, 0) == 0)
|
||||
{
|
||||
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionSwitchTypeFullName.Add((LabelTarget returnTarget, Expression valueExp, Type type) =>
|
||||
{
|
||||
if (_dicTypes.ContainsKey(type)) return Expression.IfThenElse(
|
||||
Expression.TypeIs(valueExp, type),
|
||||
Expression.Return(returnTarget, valueExp),
|
||||
Expression.Return(returnTarget, Expression.TypeAs(Expression.Call(MethodJsonConvertDeserializeObject, Expression.Convert(valueExp, typeof(string)), Expression.Constant(type)), type))
|
||||
);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
that.Aop.ConfigEntityProperty += (s, e) =>
|
||||
{
|
||||
var isJsonMap = e.Property.GetCustomAttributes(typeof(JsonMapAttribute), false).Any() || _dicJsonMapFluentApi.TryGetValue(e.EntityType, out var tryjmfu) && tryjmfu.ContainsKey(e.Property.Name);
|
||||
if (isJsonMap)
|
||||
{
|
||||
if (_dicTypes.ContainsKey(e.Property.PropertyType) == false &&
|
||||
FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(e.Property.PropertyType))
|
||||
return; //基础类型使用 JsonMap 无效
|
||||
|
||||
e.ModifyResult.MapType = typeof(string);
|
||||
e.ModifyResult.StringLength = -2;
|
||||
if (_dicTypes.TryAdd(e.Property.PropertyType, true))
|
||||
{
|
||||
lock (_concurrentObj)
|
||||
{
|
||||
FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple[e.Property.PropertyType] = true;
|
||||
FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse.Add((LabelTarget returnTarget, Expression valueExp, Expression elseExp, Type type) =>
|
||||
{
|
||||
return Expression.IfThenElse(
|
||||
Expression.TypeIs(valueExp, e.Property.PropertyType),
|
||||
Expression.Return(returnTarget, Expression.Call(MethodJsonConvertSerializeObject, Expression.Convert(valueExp, typeof(object)), Expression.Constant(settings)), typeof(object)),
|
||||
elseExp);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
25
Extensions/FreeSql.Extensions.JsonMap/README.MD
Normal file
25
Extensions/FreeSql.Extensions.JsonMap/README.MD
Normal file
@ -0,0 +1,25 @@
|
||||
[中文](README.zh-CN.MD) | **English**
|
||||
|
||||
FreeSql extension package, map *ValueObject* to `typeof(string)`, install the extension package:
|
||||
|
||||
> dotnet add package FreeSql.Extensions.JsonMap
|
||||
|
||||
```csharp
|
||||
fsql.UseJsonMap(); //Turn on function
|
||||
|
||||
class TestConfig
|
||||
{
|
||||
public int clicks { get; set; }
|
||||
public string title { get; set; }
|
||||
}
|
||||
|
||||
[Table(Name = "sysconfig")]
|
||||
public class S_SysConfig<T>
|
||||
{
|
||||
[Column(IsPrimary = true)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonMap]
|
||||
public T Config { get; set; }
|
||||
}
|
||||
```
|
25
Extensions/FreeSql.Extensions.JsonMap/README.zh-CN.MD
Normal file
25
Extensions/FreeSql.Extensions.JsonMap/README.zh-CN.MD
Normal file
@ -0,0 +1,25 @@
|
||||
**中文** | [English](README.MD)
|
||||
|
||||
FreeSql 扩展包,将值对象映射成 `typeof(string)`,安装扩展包:
|
||||
|
||||
> dotnet add package FreeSql.Extensions.JsonMap
|
||||
|
||||
```csharp
|
||||
fsql.UseJsonMap(); //开启功能
|
||||
|
||||
class TestConfig
|
||||
{
|
||||
public int clicks { get; set; }
|
||||
public string title { get; set; }
|
||||
}
|
||||
|
||||
[Table(Name = "sysconfig")]
|
||||
public class S_SysConfig<T>
|
||||
{
|
||||
[Column(IsPrimary = true)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonMap]
|
||||
public T Config { get; set; }
|
||||
}
|
||||
```
|
BIN
Extensions/FreeSql.Extensions.JsonMap/key.snk
Normal file
BIN
Extensions/FreeSql.Extensions.JsonMap/key.snk
Normal file
Binary file not shown.
@ -0,0 +1,42 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.1;netstandard2.0;net45;net40</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||
<Description>FreeSql 扩展包,可实现【延时加载】属性.</Description>
|
||||
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageTags>FreeSql;ORM</PackageTags>
|
||||
<PackageId>$(AssemblyName)</PackageId>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
<Title>$(AssemblyName)</Title>
|
||||
<IsPackable>true</IsPackable>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<Version>3.2.833</Version>
|
||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
|
||||
<None Include="../../logo.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
|
||||
<PackageReference Include="CS-Script.Core" Version="1.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="CS-Script.Core" Version="1.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'netstandard2.0'">
|
||||
<DefineConstants>ns20;netstandard20</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FreeSql.Extensions.LazyLoading
|
||||
{
|
||||
|
||||
public class LazyLoadingComplier
|
||||
{
|
||||
|
||||
#if ns20
|
||||
//public static Assembly CompileCode(string cscode)
|
||||
//{
|
||||
// Natasha.AssemblyComplier complier = new Natasha.AssemblyComplier();
|
||||
// //complier.Domain = DomainManagment.Random;
|
||||
// complier.Add(cscode);
|
||||
// return complier.GetAssembly();
|
||||
//}
|
||||
|
||||
internal static Lazy<CSScriptLib.RoslynEvaluator> _compiler = new Lazy<CSScriptLib.RoslynEvaluator>(() =>
|
||||
{
|
||||
var compiler = new CSScriptLib.RoslynEvaluator();
|
||||
compiler.DisableReferencingFromCode = false;
|
||||
compiler
|
||||
.ReferenceAssemblyOf<IFreeSql>()
|
||||
.ReferenceDomainAssemblies();
|
||||
return compiler;
|
||||
});
|
||||
|
||||
public static Assembly CompileCode(string cscode)
|
||||
{
|
||||
return _compiler.Value.CompileCode(cscode);
|
||||
}
|
||||
#else
|
||||
|
||||
public static Assembly CompileCode(string cscode)
|
||||
{
|
||||
|
||||
var files = Directory.GetFiles(Directory.GetParent(Type.GetType("IFreeSql, FreeSql").Assembly.Location).FullName);
|
||||
using (var compiler = CodeDomProvider.CreateProvider("cs"))
|
||||
{
|
||||
var objCompilerParameters = new CompilerParameters();
|
||||
objCompilerParameters.ReferencedAssemblies.Add("System.dll");
|
||||
objCompilerParameters.ReferencedAssemblies.Add("System.Core.dll");
|
||||
objCompilerParameters.ReferencedAssemblies.Add("FreeSql.dll");
|
||||
foreach (var dll in files)
|
||||
{
|
||||
if (!dll.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) &&
|
||||
!dll.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
|
||||
//Console.WriteLine(dll);
|
||||
var dllName = string.Empty;
|
||||
var idx = dll.LastIndexOf('/');
|
||||
if (idx != -1) dllName = dll.Substring(idx + 1);
|
||||
else
|
||||
{
|
||||
idx = dll.LastIndexOf('\\');
|
||||
if (idx != -1) dllName = dll.Substring(idx + 1);
|
||||
}
|
||||
if (string.IsNullOrEmpty(dllName)) continue;
|
||||
try
|
||||
{
|
||||
var ass = Assembly.LoadFile(dll);
|
||||
objCompilerParameters.ReferencedAssemblies.Add(dllName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
objCompilerParameters.GenerateExecutable = false;
|
||||
objCompilerParameters.GenerateInMemory = true;
|
||||
|
||||
CompilerResults cr = compiler.CompileAssemblyFromSource(objCompilerParameters, cscode);
|
||||
|
||||
if (cr.Errors.Count > 0)
|
||||
throw new Exception(cr.Errors[0].ErrorText);
|
||||
|
||||
return cr.CompiledAssembly;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
BIN
Extensions/FreeSql.Extensions.LazyLoading/key.snk
Normal file
BIN
Extensions/FreeSql.Extensions.LazyLoading/key.snk
Normal file
Binary file not shown.
@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||
<Description>FreeSql 扩展包,实现 linq queryable 和 linq to sql 语法进行开发.</Description>
|
||||
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageTags>FreeSql;ORM</PackageTags>
|
||||
<PackageId>$(AssemblyName)</PackageId>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
<Title>$(AssemblyName)</Title>
|
||||
<IsPackable>true</IsPackable>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<DelaySign>false</DelaySign>
|
||||
<Version>3.2.833</Version>
|
||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
|
||||
<None Include="../../logo.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
|
||||
<DocumentationFile>FreeSql.Extensions.Linq.xml</DocumentationFile>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
|
||||
<DefineConstants>net40</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>FreeSql.Extensions.Linq</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.AsQueryable``1(FreeSql.ISelect{``0})">
|
||||
<summary>
|
||||
将 ISelect<T1> 转换为 IQueryable<T1><para></para>
|
||||
用于扩展如:abp IRepository GetAll() 接口方法需要返回 IQueryable 对象<para></para>
|
||||
提示:IQueryable 方法污染严重,查询功能的实现也不理想,应尽量避免此转换<para></para>
|
||||
IQueryable<T1> 扩展方法 RestoreToSelect() 可以还原为 ISelect<T1>
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.RestoreToSelect``1(System.Linq.IQueryable{``0})">
|
||||
<summary>
|
||||
将 IQueryable<T1> 转换为 ISelect<T1><para></para>
|
||||
前提:IQueryable 必须由 FreeSql.Extensions.Linq.QueryableProvider 实现
|
||||
</summary>
|
||||
<typeparam name="T1"></typeparam>
|
||||
<param name="that"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.Select``2(FreeSql.ISelect{``0},System.Linq.Expressions.Expression{System.Func{``0,``1}})">
|
||||
<summary>
|
||||
【linq to sql】专用扩展方法,不建议直接使用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.Join``4(FreeSql.ISelect{``0},FreeSql.ISelect{``1},System.Linq.Expressions.Expression{System.Func{``0,``2}},System.Linq.Expressions.Expression{System.Func{``1,``2}},System.Linq.Expressions.Expression{System.Func{``0,``1,``3}})">
|
||||
<summary>
|
||||
【linq to sql】专用扩展方法,不建议直接使用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.GroupJoin``4(FreeSql.ISelect{``0},FreeSql.ISelect{``1},System.Linq.Expressions.Expression{System.Func{``0,``2}},System.Linq.Expressions.Expression{System.Func{``1,``2}},System.Linq.Expressions.Expression{System.Func{``0,FreeSql.ISelect{``1},``3}})">
|
||||
<summary>
|
||||
【linq to sql】专用扩展方法,不建议直接使用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.SelectMany``3(FreeSql.ISelect{``0},System.Linq.Expressions.Expression{System.Func{``0,FreeSql.ISelect{``1}}},System.Linq.Expressions.Expression{System.Func{``0,``1,``2}})">
|
||||
<summary>
|
||||
【linq to sql】专用扩展方法,不建议直接使用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.DefaultIfEmpty``1(FreeSql.ISelect{``0})">
|
||||
<summary>
|
||||
【linq to sql】专用扩展方法,不建议直接使用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.ThenBy``2(FreeSql.ISelect{``0},System.Linq.Expressions.Expression{System.Func{``0,``1}})">
|
||||
<summary>
|
||||
【linq to sql】专用扩展方法,不建议直接使用
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSqlExtensionsLinqSql.ThenByDescending``2(FreeSql.ISelect{``0},System.Linq.Expressions.Expression{System.Func{``0,``1}})">
|
||||
<summary>
|
||||
【linq to sql】专用扩展方法,不建议直接使用
|
||||
</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
153
Extensions/FreeSql.Extensions.Linq/FreeSqlExtensionsLinq.cs
Normal file
153
Extensions/FreeSql.Extensions.Linq/FreeSqlExtensionsLinq.cs
Normal file
@ -0,0 +1,153 @@
|
||||
using FreeSql;
|
||||
using FreeSql.Extensions.Linq;
|
||||
using FreeSql.Internal;
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public static class FreeSqlExtensionsLinqSql
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 将 ISelect<T1> 转换为 IQueryable<T1><para></para>
|
||||
/// 用于扩展如:abp IRepository GetAll() 接口方法需要返回 IQueryable 对象<para></para>
|
||||
/// 提示:IQueryable 方法污染严重,查询功能的实现也不理想,应尽量避免此转换<para></para>
|
||||
/// IQueryable<T1> 扩展方法 RestoreToSelect() 可以还原为 ISelect<T1>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IQueryable<T1> AsQueryable<T1>(this ISelect<T1> that) where T1 : class
|
||||
{
|
||||
return new QueryableProvider<T1, T1>(that as Select1Provider<T1>);
|
||||
}
|
||||
/// <summary>
|
||||
/// 将 IQueryable<T1> 转换为 ISelect<T1><para></para>
|
||||
/// 前提:IQueryable 必须由 FreeSql.Extensions.Linq.QueryableProvider 实现
|
||||
/// </summary>
|
||||
/// <typeparam name="T1"></typeparam>
|
||||
/// <param name="that"></param>
|
||||
/// <returns></returns>
|
||||
public static ISelect<T1> RestoreToSelect<T1>(this IQueryable<T1> that) where T1 : class
|
||||
{
|
||||
var queryable = that as QueryableProvider<T1, T1> ?? throw new Exception(CoreStrings.S_CannotBeConverted_To_ISelect(typeof(T1).Name));
|
||||
return queryable._select;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【linq to sql】专用扩展方法,不建议直接使用
|
||||
/// </summary>
|
||||
public static ISelect<TReturn> Select<T1, TReturn>(this ISelect<T1> that, Expression<Func<T1, TReturn>> select)
|
||||
{
|
||||
var s1p = that as Select1Provider<T1>;
|
||||
if (typeof(TReturn) == typeof(T1)) return that as ISelect<TReturn>;
|
||||
s1p._tables[0].Parameter = select.Parameters[0];
|
||||
s1p._selectExpression = select.Body;
|
||||
if (s1p._orm.CodeFirst.IsAutoSyncStructure)
|
||||
(s1p._orm.CodeFirst as CodeFirstProvider)._dicSycedTryAdd(typeof(TReturn)); //._dicSyced.TryAdd(typeof(TReturn), true);
|
||||
var ret = (s1p._orm as BaseDbProvider).CreateSelectProvider<TReturn>(null) as Select1Provider<TReturn>;
|
||||
Select0Provider.CopyData(s1p, ret, null);
|
||||
return ret;
|
||||
}
|
||||
/// <summary>
|
||||
/// 【linq to sql】专用扩展方法,不建议直接使用
|
||||
/// </summary>
|
||||
public static ISelect<TResult> Join<T1, TInner, TKey, TResult>(this ISelect<T1> that, ISelect<TInner> inner, Expression<Func<T1, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<T1, TInner, TResult>> resultSelector) where T1 : class where TInner : class where TResult : class
|
||||
{
|
||||
var s1p = that as Select1Provider<T1>;
|
||||
InternalJoin2(s1p, outerKeySelector, innerKeySelector, resultSelector);
|
||||
if (typeof(TResult) == typeof(T1)) return that as ISelect<TResult>;
|
||||
s1p._selectExpression = resultSelector.Body;
|
||||
if (s1p._orm.CodeFirst.IsAutoSyncStructure)
|
||||
(s1p._orm.CodeFirst as CodeFirstProvider)._dicSycedTryAdd(typeof(TResult)); //._dicSyced.TryAdd(typeof(TResult), true);
|
||||
var ret = s1p._orm.Select<TResult>() as Select1Provider<TResult>;
|
||||
Select0Provider.CopyData(s1p, ret, null);
|
||||
return ret;
|
||||
}
|
||||
internal static void InternalJoin2<T1>(Select1Provider<T1> s1p, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) where T1 : class
|
||||
{
|
||||
s1p._tables[0].Parameter = resultSelector.Parameters[0];
|
||||
s1p._commonExpression.ExpressionLambdaToSql(outerKeySelector, new CommonExpression.ExpTSC { _tables = s1p._tables, _tableRule = s1p._tableRule });
|
||||
s1p.InternalJoin(Expression.Lambda(typeof(Func<,,>).MakeGenericType(typeof(T1), innerKeySelector.Parameters[0].Type, typeof(bool)),
|
||||
Expression.Equal(outerKeySelector.Body, innerKeySelector.Body),
|
||||
new[] { outerKeySelector.Parameters[0], innerKeySelector.Parameters[0] }
|
||||
), SelectTableInfoType.InnerJoin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【linq to sql】专用扩展方法,不建议直接使用
|
||||
/// </summary>
|
||||
public static ISelect<TResult> GroupJoin<T1, TInner, TKey, TResult>(this ISelect<T1> that, ISelect<TInner> inner, Expression<Func<T1, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<T1, ISelect<TInner>, TResult>> resultSelector) where T1 : class where TInner : class where TResult : class
|
||||
{
|
||||
var s1p = that as Select1Provider<T1>;
|
||||
InternalJoin2(s1p, outerKeySelector, innerKeySelector, resultSelector);
|
||||
if (typeof(TResult) == typeof(T1)) return that as ISelect<TResult>;
|
||||
s1p._selectExpression = resultSelector.Body;
|
||||
if (s1p._orm.CodeFirst.IsAutoSyncStructure)
|
||||
(s1p._orm.CodeFirst as CodeFirstProvider)._dicSycedTryAdd(typeof(TResult)); //._dicSyced.TryAdd(typeof(TResult), true);
|
||||
var ret = s1p._orm.Select<TResult>() as Select1Provider<TResult>;
|
||||
Select0Provider.CopyData(s1p, ret, null);
|
||||
return ret;
|
||||
}
|
||||
/// <summary>
|
||||
/// 【linq to sql】专用扩展方法,不建议直接使用
|
||||
/// </summary>
|
||||
public static ISelect<TResult> SelectMany<T1, TCollection, TResult>(this ISelect<T1> that, Expression<Func<T1, ISelect<TCollection>>> collectionSelector, Expression<Func<T1, TCollection, TResult>> resultSelector) where T1 : class where TCollection : class where TResult : class
|
||||
{
|
||||
var s1p = that as Select1Provider<T1>;
|
||||
InternalSelectMany2(s1p, collectionSelector, resultSelector);
|
||||
if (typeof(TResult) == typeof(T1)) return that as ISelect<TResult>;
|
||||
s1p._selectExpression = resultSelector.Body;
|
||||
if (s1p._orm.CodeFirst.IsAutoSyncStructure)
|
||||
(s1p._orm.CodeFirst as CodeFirstProvider)._dicSycedTryAdd(typeof(TResult)); //._dicSyced.TryAdd(typeof(TResult), true);
|
||||
var ret = s1p._orm.Select<TResult>() as Select1Provider<TResult>;
|
||||
Select0Provider.CopyData(s1p, ret, null);
|
||||
return ret;
|
||||
}
|
||||
internal static void InternalSelectMany2<T1>(Select1Provider<T1> s1p, LambdaExpression collectionSelector, LambdaExpression resultSelector) where T1 : class
|
||||
{
|
||||
SelectTableInfo find = null;
|
||||
if (collectionSelector.Body.NodeType == ExpressionType.Call)
|
||||
{
|
||||
var callExp = collectionSelector.Body as MethodCallExpression;
|
||||
if (callExp.Method.Name == "DefaultIfEmpty" && callExp.Method.GetGenericArguments().Any())
|
||||
{
|
||||
find = s1p._tables.Where((a, idx) => idx > 0 && a.Type == SelectTableInfoType.InnerJoin && a.Table.Type == callExp.Method.GetGenericArguments()[0]).LastOrDefault();
|
||||
if (find != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(find.On)) find.On = Regex.Replace(find.On, $@"\b{find.Alias}\.", $"{resultSelector.Parameters[1].Name}.");
|
||||
if (!string.IsNullOrEmpty(find.NavigateCondition)) find.NavigateCondition = Regex.Replace(find.NavigateCondition, $@"\b{find.Alias}\.", $"{resultSelector.Parameters[1].Name}.");
|
||||
find.Type = SelectTableInfoType.LeftJoin;
|
||||
find.Alias = resultSelector.Parameters[1].Name;
|
||||
find.Parameter = resultSelector.Parameters[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (find == null)
|
||||
{
|
||||
var tb = s1p._commonUtils.GetTableByEntity(resultSelector.Parameters[1].Type);
|
||||
if (tb == null) throw new Exception($"SelectMany 错误的类型:{resultSelector.Parameters[1].Type.FullName}");
|
||||
s1p._tables.Add(new SelectTableInfo { Alias = resultSelector.Parameters[1].Name, AliasInit = resultSelector.Parameters[1].Name, Parameter = resultSelector.Parameters[1], Table = tb, Type = SelectTableInfoType.From });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【linq to sql】专用扩展方法,不建议直接使用
|
||||
/// </summary>
|
||||
public static ISelect<T1> DefaultIfEmpty<T1>(this ISelect<T1> that) where T1 : class
|
||||
{
|
||||
return that;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【linq to sql】专用扩展方法,不建议直接使用
|
||||
/// </summary>
|
||||
public static ISelect<T1> ThenBy<T1, TMember>(this ISelect<T1> that, Expression<Func<T1, TMember>> column) where T1 : class => that.OrderBy(column);
|
||||
/// <summary>
|
||||
/// 【linq to sql】专用扩展方法,不建议直接使用
|
||||
/// </summary>
|
||||
public static ISelect<T1> ThenByDescending<T1, TMember>(this ISelect<T1> that, Expression<Func<T1, TMember>> column) where T1 : class => that.OrderByDescending(column);
|
||||
}
|
266
Extensions/FreeSql.Extensions.Linq/QueryableProvider.cs
Normal file
266
Extensions/FreeSql.Extensions.Linq/QueryableProvider.cs
Normal file
@ -0,0 +1,266 @@
|
||||
using FreeSql.Internal;
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace FreeSql.Extensions.Linq
|
||||
{
|
||||
class QueryableProvider<TCurrent, TSource> : IQueryable<TCurrent>, IOrderedQueryable<TCurrent> where TSource : class
|
||||
{
|
||||
Expression _expression;
|
||||
IQueryProvider _provider;
|
||||
internal Select1Provider<TSource> _select;
|
||||
|
||||
public QueryableProvider(Select1Provider<TSource> select)
|
||||
{
|
||||
_select = select;
|
||||
_expression = Expression.Constant(this);
|
||||
_provider = new QueryProvider<TCurrent, TSource>(_select, _expression);
|
||||
}
|
||||
public QueryableProvider(Expression expression, IQueryProvider provider, Select1Provider<TSource> select)
|
||||
{
|
||||
_select = select;
|
||||
_expression = expression;
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
public IEnumerator<TCurrent> GetEnumerator()
|
||||
{
|
||||
var result = _provider.Execute<List<TCurrent>>(_expression);
|
||||
if (result == null)
|
||||
yield break;
|
||||
foreach (var item in result)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return (IEnumerator)GetEnumerator();
|
||||
}
|
||||
|
||||
public Type ElementType => typeof(QueryableProvider<TCurrent, TSource>);
|
||||
public Expression Expression => _expression;
|
||||
public IQueryProvider Provider => _provider;
|
||||
}
|
||||
|
||||
class QueryProvider<TCurrent, TSource> : IQueryProvider where TSource : class
|
||||
{
|
||||
Select1Provider<TSource> _select;
|
||||
Expression _oldExpression;
|
||||
|
||||
public QueryProvider(Select1Provider<TSource> select, Expression oldExpression)
|
||||
{
|
||||
_select = select;
|
||||
_oldExpression = oldExpression;
|
||||
}
|
||||
|
||||
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
|
||||
{
|
||||
ExecuteExp(expression, null, false);
|
||||
if (typeof(TElement) != typeof(TCurrent))
|
||||
return new QueryableProvider<TElement, TSource>(expression, new QueryProvider<TElement, TSource>(_select, expression), _select);
|
||||
|
||||
_oldExpression = expression;
|
||||
return new QueryableProvider<TElement, TSource>(expression, this, _select);
|
||||
}
|
||||
public IQueryable CreateQuery(Expression expression) => throw new NotImplementedException();
|
||||
|
||||
public TResult Execute<TResult>(Expression expression)
|
||||
{
|
||||
return (TResult)ExecuteExp(expression, typeof(TResult), _oldExpression == expression);
|
||||
}
|
||||
public object Execute(Expression expression) => throw new NotImplementedException();
|
||||
|
||||
public object ExecuteExp(Expression expression, Type tresult, bool isProcessed)
|
||||
{
|
||||
var callExp = expression as MethodCallExpression;
|
||||
var isfirst = false;
|
||||
if (callExp != null && isProcessed == false)
|
||||
{
|
||||
object throwCallExp(string message) => throw new Exception($"解析失败 {callExp.Method.Name} {message},提示:可以使用扩展方法 IQueryable.RestoreToSelect() 还原为 ISelect 再查询");
|
||||
if (callExp.Method.DeclaringType != typeof(Queryable)) return throwCallExp($"必须属于 System.Linq.Queryable");
|
||||
|
||||
object tplMaxMinAvgSum(string method)
|
||||
{
|
||||
if (callExp.Arguments.Count == 2)
|
||||
{
|
||||
var avgParam = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
return Utils.GetDataReaderValue(tresult,
|
||||
_select.GetType().GetMethod(method).MakeGenericMethod(avgParam.ReturnType).Invoke(_select, new object[] { avgParam }));
|
||||
}
|
||||
return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法");
|
||||
}
|
||||
object tplOrderBy(string method, bool isDescending)
|
||||
{
|
||||
if (callExp.Arguments.Count == 2)
|
||||
{
|
||||
var arg1 = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
_select.OrderByReflection(arg1, isDescending);
|
||||
return tresult.CreateInstanceGetDefaultValue();
|
||||
}
|
||||
return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法");
|
||||
}
|
||||
switch (callExp.Method.Name)
|
||||
{
|
||||
case "Any":
|
||||
if (callExp.Arguments.Count == 2) _select.InternalWhere(callExp.Arguments[1]);
|
||||
return _select.Any();
|
||||
case "AsQueryable":
|
||||
break;
|
||||
|
||||
case "Max": return tplMaxMinAvgSum("Max");
|
||||
case "Min": return tplMaxMinAvgSum("Min");
|
||||
case "Sum": return tplMaxMinAvgSum("Sum");
|
||||
case "Average": return tplMaxMinAvgSum("Avg");
|
||||
|
||||
case "Concat":
|
||||
return throwCallExp(CoreStrings.Not_Support);
|
||||
case "Contains":
|
||||
if (callExp.Arguments.Count == 2)
|
||||
{
|
||||
var dywhere = callExp.Arguments[1].GetConstExprValue();
|
||||
if (dywhere == null) return throwCallExp($" 参数值不能为 null");
|
||||
_select.WhereDynamic(dywhere);
|
||||
return _select.Any();
|
||||
}
|
||||
return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法");
|
||||
case "Count":
|
||||
if (callExp.Arguments.Count == 2) _select.InternalWhere(callExp.Arguments[1]);
|
||||
return Utils.GetDataReaderValue(tresult, _select.Count());
|
||||
|
||||
case "Distinct":
|
||||
if (callExp.Arguments.Count == 1)
|
||||
{
|
||||
_select.Distinct();
|
||||
break;
|
||||
}
|
||||
return throwCallExp(CoreStrings.Not_Support);
|
||||
|
||||
case "ElementAt":
|
||||
case "ElementAtOrDefault":
|
||||
_select.Offset((int)callExp.Arguments[1].GetConstExprValue());
|
||||
_select.Limit(1);
|
||||
isfirst = true;
|
||||
break;
|
||||
case "First":
|
||||
case "FirstOrDefault":
|
||||
case "Single":
|
||||
case "SingleOrDefault":
|
||||
if (callExp.Arguments.Count == 2) _select.InternalWhere(callExp.Arguments[1]);
|
||||
_select.Limit(1);
|
||||
isfirst = true;
|
||||
break;
|
||||
|
||||
case "OrderBy":
|
||||
tplOrderBy("OrderByReflection", false);
|
||||
break;
|
||||
case "OrderByDescending":
|
||||
tplOrderBy("OrderByReflection", true);
|
||||
break;
|
||||
case "ThenBy":
|
||||
tplOrderBy("OrderByReflection", false);
|
||||
break;
|
||||
case "ThenByDescending":
|
||||
tplOrderBy("OrderByReflection", true);
|
||||
break;
|
||||
|
||||
case "Where":
|
||||
var whereParam = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
if (whereParam.Parameters.Count == 1)
|
||||
{
|
||||
_select.InternalWhere(whereParam);
|
||||
break;
|
||||
}
|
||||
return throwCallExp(CoreStrings.Not_Support);
|
||||
|
||||
case "Skip":
|
||||
_select.Offset((int)callExp.Arguments[1].GetConstExprValue());
|
||||
break;
|
||||
case "Take":
|
||||
_select.Limit((int)callExp.Arguments[1].GetConstExprValue());
|
||||
break;
|
||||
|
||||
case "ToList":
|
||||
if (callExp.Arguments.Count == 1)
|
||||
return _select.ToList();
|
||||
return throwCallExp(CoreStrings.Not_Support);
|
||||
|
||||
case "Select":
|
||||
var selectParam = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
if (selectParam.Parameters.Count == 1)
|
||||
{
|
||||
_select._selectExpression = selectParam;
|
||||
break;
|
||||
}
|
||||
return throwCallExp(CoreStrings.Not_Support);
|
||||
|
||||
case "Join":
|
||||
if (callExp.Arguments.Count == 5)
|
||||
{
|
||||
var arg2 = (callExp.Arguments[2] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
var arg3 = (callExp.Arguments[3] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
var arg4 = (callExp.Arguments[4] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
FreeSqlExtensionsLinqSql.InternalJoin2(_select, arg2, arg3, arg4);
|
||||
_select._selectExpression = arg4.Body;
|
||||
break;
|
||||
}
|
||||
return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法");
|
||||
|
||||
case "GroupJoin":
|
||||
if (callExp.Arguments.Count == 5)
|
||||
{
|
||||
var arg2 = (callExp.Arguments[2] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
var arg3 = (callExp.Arguments[3] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
var arg4 = (callExp.Arguments[4] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
FreeSqlExtensionsLinqSql.InternalJoin2(_select, arg2, arg3, arg4);
|
||||
_select._selectExpression = arg4.Body;
|
||||
break;
|
||||
}
|
||||
return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法");
|
||||
|
||||
case "SelectMany":
|
||||
if (callExp.Arguments.Count == 3)
|
||||
{
|
||||
var arg1 = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
var arg2 = (callExp.Arguments[2] as UnaryExpression)?.Operand as LambdaExpression;
|
||||
FreeSqlExtensionsLinqSql.InternalSelectMany2(_select, arg1, arg2);
|
||||
_select._selectExpression = arg2.Body;
|
||||
break;
|
||||
}
|
||||
return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法");
|
||||
|
||||
case "DefaultIfEmpty":
|
||||
break;
|
||||
|
||||
case "Last":
|
||||
case "LastOrDefault":
|
||||
return throwCallExp(CoreStrings.Not_Support);
|
||||
|
||||
case "GroupBy":
|
||||
return throwCallExp(CoreStrings.Not_Support);
|
||||
|
||||
default:
|
||||
return throwCallExp(CoreStrings.Not_Support);
|
||||
}
|
||||
}
|
||||
if (tresult == null) return null;
|
||||
if (isfirst)
|
||||
{
|
||||
_select.Limit(1);
|
||||
if (_select._selectExpression != null)
|
||||
return _select.InternalToList<TCurrent>(_select._selectExpression).FirstOrDefault();
|
||||
return _select.ToList().FirstOrDefault();
|
||||
}
|
||||
if (_select._selectExpression != null)
|
||||
return _select.InternalToList<TCurrent>(_select._selectExpression);
|
||||
return _select.ToList();
|
||||
}
|
||||
}
|
||||
}
|
189
Extensions/FreeSql.Extensions.Linq/SelectedQueryProvider.cs
Normal file
189
Extensions/FreeSql.Extensions.Linq/SelectedQueryProvider.cs
Normal file
@ -0,0 +1,189 @@
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FreeSql
|
||||
{
|
||||
public interface ISelectedQuery<TOut>
|
||||
{
|
||||
Select0Provider SelectOwner { get; }
|
||||
|
||||
#if net40
|
||||
#else
|
||||
Task<List<TOut>> ToListAsync(CancellationToken cancellationToken = default);
|
||||
Task<TOut> ToOneAsync(CancellationToken cancellationToken = default);
|
||||
Task<TOut> FirstAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> AnyAsync(CancellationToken cancellationToken = default);
|
||||
Task<long> CountAsync(CancellationToken cancellationToken = default);
|
||||
#endif
|
||||
|
||||
List<TOut> ToList();
|
||||
TOut ToOne();
|
||||
TOut First();
|
||||
|
||||
string ToSql();
|
||||
bool Any();
|
||||
|
||||
long Count();
|
||||
ISelectedQuery<TOut> Count(out long count);
|
||||
|
||||
ISelectedQuery<TOut> Skip(int offset);
|
||||
ISelectedQuery<TOut> Offset(int offset);
|
||||
ISelectedQuery<TOut> Limit(int limit);
|
||||
ISelectedQuery<TOut> Take(int limit);
|
||||
ISelectedQuery<TOut> Page(int pageNumber, int pageSize);
|
||||
|
||||
ISelectedQuery<TOut> Where(Expression<Func<TOut, bool>> exp);
|
||||
ISelectedQuery<TOut> WhereIf(bool condition, Expression<Func<TOut, bool>> exp);
|
||||
|
||||
ISelectedQuery<TOut> OrderBy<TMember>(Expression<Func<TOut, TMember>> column);
|
||||
ISelectedQuery<TOut> OrderByIf<TMember>(bool condition, Expression<Func<TOut, TMember>> column, bool descending = false);
|
||||
ISelectedQuery<TOut> OrderByDescending<TMember>(Expression<Func<TOut, TMember>> column);
|
||||
}
|
||||
}
|
||||
|
||||
namespace FreeSql.Internal.CommonProvider
|
||||
{
|
||||
public class SelectedQueryProvider<TOut> : BaseDiyMemberExpression, ISelectedQuery<TOut>
|
||||
{
|
||||
public Select0Provider _select;
|
||||
public CommonExpression _comonExp;
|
||||
public SelectedQueryProvider(Select0Provider select, Expression selector)
|
||||
{
|
||||
_select = select;
|
||||
_comonExp = _select._commonExpression;
|
||||
_map = new ReadAnonymousTypeInfo();
|
||||
var field = new StringBuilder();
|
||||
var index = -10000; //临时规则,不返回 as1
|
||||
|
||||
if (selector != null)
|
||||
_comonExp.ReadAnonymousField(_select._tables, _select._tableRule, field, _map, ref index, selector, null, null, _select._whereGlobalFilter, null, null, false); //不走 DTO 映射,不处理 IncludeMany
|
||||
_field = field.ToString();
|
||||
}
|
||||
|
||||
public override string ParseExp(Expression[] members)
|
||||
{
|
||||
ParseExpMapResult = null;
|
||||
if (members.Any() == false)
|
||||
{
|
||||
ParseExpMapResult = _map;
|
||||
return _map.DbField;
|
||||
}
|
||||
var read = _map;
|
||||
for (var a = 0; a < members.Length; a++)
|
||||
{
|
||||
read = read.Childs.Where(z => z.CsName == (members[a] as MemberExpression)?.Member.Name).FirstOrDefault();
|
||||
if (read == null) return null;
|
||||
}
|
||||
ParseExpMapResult = read;
|
||||
return read.DbField;
|
||||
}
|
||||
|
||||
public Select0Provider SelectOwner => _select;
|
||||
|
||||
#if net40
|
||||
#else
|
||||
public Task<List<TOut>> ToListAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var method = _select.GetType().GetMethod("ToListMapReaderAsync", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
method = method.MakeGenericMethod(typeof(TOut));
|
||||
return method.Invoke(_select, new object[] { new ReadAnonymousTypeAfInfo(_map, _field.Length > 0 ? _field.Remove(0, 2).ToString() : null), cancellationToken }) as Task<List<TOut>>;
|
||||
}
|
||||
async public Task<TOut> ToOneAsync(CancellationToken cancellationToken = default) => (await ToListAsync(cancellationToken)).FirstOrDefault();
|
||||
public Task<TOut> FirstAsync(CancellationToken cancellationToken = default) => ToOneAsync(cancellationToken);
|
||||
|
||||
public Task<bool> AnyAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var method = _select.GetType().GetMethod("AnyAsync", new Type[] { typeof(CancellationToken) });
|
||||
return method.Invoke(_select, new object[] { cancellationToken }) as Task<bool>;
|
||||
}
|
||||
public Task<long> CountAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var method = _select.GetType().GetMethod("CountAsync", new Type[] { typeof(CancellationToken) });
|
||||
return method.Invoke(_select, new object[] { cancellationToken }) as Task<long>;
|
||||
}
|
||||
#endif
|
||||
|
||||
public List<TOut> ToList()
|
||||
{
|
||||
var method = _select.GetType().GetMethod("ToListMapReader", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
method = method.MakeGenericMethod(typeof(TOut));
|
||||
return method.Invoke(_select, new object[] { new ReadAnonymousTypeAfInfo(_map, _field.Length > 0 ? _field.Remove(0, 2).ToString() : null) }) as List<TOut>;
|
||||
}
|
||||
public TOut ToOne() => ToList().FirstOrDefault();
|
||||
public TOut First() => ToOne();
|
||||
|
||||
public string ToSql()
|
||||
{
|
||||
var method = _select.GetType().GetMethod("ToSql", new[] { typeof(string) });
|
||||
return method.Invoke(_select, new object[] { _field.Length > 0 ? _field.Remove(0, 2).ToString() : null }) as string;
|
||||
}
|
||||
|
||||
public bool Any()
|
||||
{
|
||||
var method = _select.GetType().GetMethod("Any", new Type[0]);
|
||||
return (bool)method.Invoke(_select, new object[0]);
|
||||
}
|
||||
public long Count()
|
||||
{
|
||||
var method = _select.GetType().GetMethod("Count", new Type[0]);
|
||||
return (long)method.Invoke(_select, new object[0]);
|
||||
}
|
||||
public ISelectedQuery<TOut> Count(out long count)
|
||||
{
|
||||
count = this.Count();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISelectedQuery<TOut> Skip(int offset)
|
||||
{
|
||||
_select._skip = offset;
|
||||
return this;
|
||||
}
|
||||
public ISelectedQuery<TOut> Offset(int offset) => Skip(offset);
|
||||
public ISelectedQuery<TOut> Limit(int limit) => Take(limit);
|
||||
public ISelectedQuery<TOut> Take(int limit)
|
||||
{
|
||||
_select._limit = limit;
|
||||
return this;
|
||||
}
|
||||
public ISelectedQuery<TOut> Page(int pageNumber, int pageSize)
|
||||
{
|
||||
this.Skip(Math.Max(0, pageNumber - 1) * pageSize);
|
||||
return this.Limit(pageSize);
|
||||
}
|
||||
|
||||
public ISelectedQuery<TOut> OrderBy<TMember>(Expression<Func<TOut, TMember>> column) => OrderByIf(true, column);
|
||||
public ISelectedQuery<TOut> OrderByIf<TMember>(bool condition, Expression<Func<TOut, TMember>> column, bool descending = false)
|
||||
{
|
||||
if (condition == false) return this;
|
||||
_lambdaParameter = column?.Parameters[0];
|
||||
var sql = _comonExp.ExpressionWhereLambda(null, null, column, this, null, null);
|
||||
var method = _select.GetType().GetMethod("OrderBy", new[] { typeof(string), typeof(object) });
|
||||
method.Invoke(_select, new object[] { descending ? $"{sql} DESC" : sql, null });
|
||||
return this;
|
||||
}
|
||||
public ISelectedQuery<TOut> OrderByDescending<TMember>(Expression<Func<TOut, TMember>> column) => OrderByIf(true, column, true);
|
||||
|
||||
public ISelectedQuery<TOut> Where(Expression<Func<TOut, bool>> exp) => WhereIf(true, exp);
|
||||
public ISelectedQuery<TOut> WhereIf(bool condition, Expression<Func<TOut, bool>> exp)
|
||||
{
|
||||
if (condition == false) return this;
|
||||
_lambdaParameter = exp?.Parameters[0];
|
||||
var sql = _comonExp.ExpressionWhereLambda(null, null, exp, this, null, null);
|
||||
var method = _select.GetType().GetMethod("Where", new[] { typeof(string), typeof(object) });
|
||||
method.Invoke(_select, new object[] { sql, null });
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
BIN
Extensions/FreeSql.Extensions.Linq/key.snk
Normal file
BIN
Extensions/FreeSql.Extensions.Linq/key.snk
Normal file
Binary file not shown.
@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net40</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||
<Description>FreeSql 扩展包,实现 低代码、零实体、ZeroEntity,并且支持导航属性,级联保存 等功能.</Description>
|
||||
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageTags>FreeSql;ORM;低代码;</PackageTags>
|
||||
<PackageId>$(AssemblyName)</PackageId>
|
||||
<PackageIcon>logo.png</PackageIcon>
|
||||
<Title>$(AssemblyName)</Title>
|
||||
<IsPackable>true</IsPackable>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<DelaySign>false</DelaySign>
|
||||
<Version>3.2.833</Version>
|
||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../readme.md" Pack="true" PackagePath="\" />
|
||||
<None Include="../../logo.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
|
||||
<DocumentationFile>FreeSql.Extensions.ZeroEntity.xml</DocumentationFile>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
|
||||
<DefineConstants>net40</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FreeSql\FreeSql.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,88 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>FreeSql.Extensions.ZeroEntity</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.#ctor(IFreeSql,FreeSql.Extensions.ZeroEntity.TableDescriptor[],System.Boolean)">
|
||||
<summary>
|
||||
创建新的ZeroDbCotext实例
|
||||
</summary>
|
||||
<param name="orm">IfreeSql 对象</param>
|
||||
<param name="schemas">动态表结构描述</param>
|
||||
<param name="syncStructure">是否强制同步表结构</param>
|
||||
<exception cref="T:FreeSql.Extensions.ZeroEntity.Models.SchemaValidationResult"> Schema 未验证通过时抛出验证异常</exception>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.#ctor(IFreeSql)">
|
||||
<summary>
|
||||
初始化一个 ZeroDbContext 对象,暂不指定任何Schema
|
||||
</summary>
|
||||
<param name="orm"></param>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SyncTableStructure(System.String)">
|
||||
<summary>
|
||||
同步指定表结构
|
||||
</summary>
|
||||
<param name="name"></param>
|
||||
</member>
|
||||
<member name="P:FreeSql.Extensions.ZeroEntity.ZeroDbContext.Select">
|
||||
<summary>
|
||||
【有状态管理】自动 Include 查询
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectNoTracking(System.String)">
|
||||
<summary>
|
||||
【无状态管理】指定表查询
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.Extensions.ZeroEntity.ZeroDbContext.ChangeReport.ChangeInfo.BeforeObject">
|
||||
<summary>
|
||||
Type = Update 的时候,获取更新之前的对象
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.Extensions.ZeroEntity.ZeroDbContext.ChangeReport.Report">
|
||||
<summary>
|
||||
实体变化记录
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FreeSql.Extensions.ZeroEntity.ZeroDbContext.ChangeReport.OnChange">
|
||||
<summary>
|
||||
实体变化事件
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.LeftJoin(System.String,System.String[])">
|
||||
<summary>
|
||||
举例1:LeftJoin("table1", "id", "user.id") -> LEFT JOIN [table1] b ON b.[id] = a.[id]<para></para>
|
||||
举例2:LeftJoin("table1", "id", "user.id", "xid", "user.xid") -> LEFT JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.InnerJoin(System.String,System.String[])">
|
||||
<summary>
|
||||
举例1:InnerJoin("table1", "id", "user.id") -> INNER JOIN [table1] b ON b.[id] = a.[id]<para></para>
|
||||
举例2:InnerJoin("table1", "id", "user.id", "xid", "user.xid") -> INNER JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.RightJoin(System.String,System.String[])">
|
||||
<summary>
|
||||
举例1:RightJoin("table1", "id", "user.id") -> RIGTH JOIN [table1] b ON b.[id] = a.[id]<para></para>
|
||||
举例2:RightJoin("table1", "id", "user.id", "xid", "user.xid") -> RIGTH JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.Where(System.Collections.Generic.IEnumerable{System.Collections.Generic.Dictionary{System.String,System.Object}})">
|
||||
<summary>
|
||||
WHERE [Id] IN (...)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.Where(System.Object)">
|
||||
<summary>
|
||||
Where(new { Year = 2017, CategoryId = 198, IsPublished = true })<para></para>
|
||||
WHERE [Year] = 2017 AND [CategoryId] = 198 AND [IsPublished] = 1
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FreeSql.Extensions.ZeroEntity.ZeroDbContext.SelectImpl.Where(System.String,System.Object)">
|
||||
<summary>
|
||||
WHERE [field] = ..
|
||||
</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace FreeSql.Extensions.ZeroEntity.Models
|
||||
{
|
||||
public class SchemaValidationException : Exception
|
||||
{
|
||||
public SchemaValidationException(string message) : base(message) { }
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FreeSql.Extensions.ZeroEntity.Models
|
||||
{
|
||||
public class SchemaValidationResult
|
||||
{
|
||||
public readonly static SchemaValidationResult _schemaValidationResult = new SchemaValidationResult("");
|
||||
|
||||
public static SchemaValidationResult SuccessedResult => _schemaValidationResult;
|
||||
|
||||
public SchemaValidationResult(string errorMessage)
|
||||
{
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
public bool Succeeded { get; set; } = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,916 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using FreeSql.Internal;
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using T = System.Collections.Generic.Dictionary<string, object>;
|
||||
|
||||
namespace FreeSql.Extensions.ZeroEntity
|
||||
{
|
||||
partial class ZeroDbContext
|
||||
{
|
||||
public class SelectImpl
|
||||
{
|
||||
ZeroDbContext _dbcontext;
|
||||
IFreeSql _orm => _dbcontext._orm;
|
||||
List<ZeroTableInfo> _tables => _dbcontext._tables;
|
||||
int _mainTableIndex = -1;
|
||||
List<TableAliasInfo> _tableAlias;
|
||||
ISelect<TestDynamicFilterInfo> _select;
|
||||
Select0Provider _selectProvider;
|
||||
string _field;
|
||||
Dictionary<string, string> _fieldAlias;
|
||||
List<Action<DbDataReaderContext>> _fieldReader;
|
||||
string _groupBy;
|
||||
List<DbParameter> _params = new List<DbParameter>();
|
||||
CommonUtils _common => _selectProvider._commonUtils;
|
||||
bool _useStates = true;
|
||||
bool _includeAll = false;
|
||||
|
||||
SelectImpl() { }
|
||||
internal SelectImpl(ZeroDbContext dbcontext, string tableName)
|
||||
{
|
||||
_dbcontext = dbcontext;
|
||||
var tableIndex = _tables.FindIndex(a => a.CsName.ToLower() == tableName?.ToLower());
|
||||
if (tableIndex == -1) throw new Exception($"未定义表名 {tableName}");
|
||||
|
||||
_mainTableIndex = tableIndex;
|
||||
_tableAlias = new List<TableAliasInfo>();
|
||||
_select = _orm.Select<TestDynamicFilterInfo>()
|
||||
.AsTable((t, old) => _tables[tableIndex].DbName);
|
||||
_selectProvider = _select as Select0Provider;
|
||||
_fieldAlias = new Dictionary<string, string>();
|
||||
_fieldReader = new List<Action<DbDataReaderContext>>();
|
||||
FlagFetchResult(_tables[_mainTableIndex], "a", "");
|
||||
}
|
||||
|
||||
public SelectImpl NoTracking()
|
||||
{
|
||||
_useStates = false;
|
||||
return this;
|
||||
}
|
||||
public SelectImpl IncludeAll()
|
||||
{
|
||||
var ignores = new Dictionary<string, bool>();
|
||||
_includeAll = true;
|
||||
LocalAutoInclude(_tables[_mainTableIndex], "a");
|
||||
return this;
|
||||
|
||||
void LocalAutoInclude(ZeroTableInfo table, string alias, string navPath = "")
|
||||
{
|
||||
if (ignores.ContainsKey(table.CsName)) return;
|
||||
ignores.Add(table.CsName, true);
|
||||
TableAliasInfo tableAlias = null;
|
||||
if (table != _tables[_mainTableIndex])
|
||||
tableAlias = FlagFetchResult(table, alias.ToString(), navPath);
|
||||
|
||||
var leftJoins = table.Navigates.Where(a => a.Value.RefType == TableRefType.ManyToOne || a.Value.RefType == TableRefType.OneToOne).ToArray();
|
||||
foreach (var join in leftJoins)
|
||||
{
|
||||
var joinTable = join.Value.RefTable;
|
||||
if (ignores.ContainsKey(joinTable.CsName)) return;
|
||||
|
||||
var joinAlias = GetMaxAlias();
|
||||
var joinOn = string.Join(" AND ", join.Value.RefColumns.Select((bname, idx) =>
|
||||
$"{joinAlias}.{_common.QuoteSqlName(join.Value.RefTable.ColumnsByCs[bname].Attribute.Name)} = {alias}.{_common.QuoteSqlName(join.Value.Table.ColumnsByCs[join.Value.Columns[idx]].Attribute.Name)}"));
|
||||
_select.LeftJoin($"{_common.QuoteSqlName(join.Value.RefTable.DbName)} {joinAlias} ON {joinOn}");
|
||||
|
||||
LocalAutoInclude(joinTable, joinAlias, $"{navPath}.{join.Key}");
|
||||
}
|
||||
if (tableAlias == null) tableAlias = _tableAlias.Where(a => a.Alias == alias).First();
|
||||
var includeManys = table.Navigates.Where(a => a.Value.RefType == TableRefType.OneToMany || a.Value.RefType == TableRefType.ManyToMany).ToArray();
|
||||
foreach (var includeMany in includeManys)
|
||||
tableAlias.IncludeMany.Add(NativeTuple.Create(includeMany.Key, (Action<SelectImpl>)null));
|
||||
}
|
||||
}
|
||||
public SelectImpl Include(string navigate, Action<SelectImpl> then = null)
|
||||
{
|
||||
var alias = _tableAlias[0];
|
||||
var navPath = navigate.Split('.');
|
||||
var navPath2 = "";
|
||||
for (var navIdx = 0; navIdx < navPath.Length; navIdx++)
|
||||
{
|
||||
if (alias.Table.Navigates.TryGetValue(navPath[navIdx], out var nav) == false) throw new Exception($"{alias.Table.CsName} 未定义导航属性 {navPath[navIdx]}");
|
||||
if (nav.RefType == TableRefType.OneToMany || nav.RefType == TableRefType.ManyToMany)
|
||||
{
|
||||
if (navIdx < navPath.Length - 1) throw new Exception($"导航属性 OneToMany/ManyToMany {navPath[navIdx]} 不能处于中间位置");
|
||||
if (alias.IncludeMany.Where(a => a.Item1 == nav.NavigateKey).Any() == false)
|
||||
alias.IncludeMany.Add(NativeTuple.Create(nav.NavigateKey, then));
|
||||
break;
|
||||
}
|
||||
navPath2 = navIdx == 0 ? nav.NavigateKey : $"{navPath2}.{nav.NavigateKey}";
|
||||
var navAlias = _tableAlias.Where(a => string.Join(".", a.NavPath) == navPath2).FirstOrDefault();
|
||||
if (navAlias == null)
|
||||
{
|
||||
var joinAlias = GetMaxAlias();
|
||||
var joinOn = string.Join(" AND ", nav.RefColumns.Select((bname, idx) =>
|
||||
$"{joinAlias}.{_common.QuoteSqlName(nav.RefTable.ColumnsByCs[bname].Attribute.Name)} = {alias.Alias}.{_common.QuoteSqlName(nav.Table.ColumnsByCs[nav.Columns[idx]].Attribute.Name)}"));
|
||||
_select.LeftJoin($"{_common.QuoteSqlName(nav.RefTable.DbName)} {joinAlias} ON {joinOn}");
|
||||
navAlias = FlagFetchResult(nav.RefTable, joinAlias, navPath2);
|
||||
}
|
||||
alias = navAlias;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
//public SelectImpl IncludeSubQuery(string resultKey, string tableName, Action<SelectImpl> then)
|
||||
//{
|
||||
// var query = _dbcontext.SelectNoTracking(tableName);
|
||||
// query._tableAlias.AddRange(_tableAlias);
|
||||
// return this;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 举例1:LeftJoin("table1", "id", "user.id") -> LEFT JOIN [table1] b ON b.[id] = a.[id]<para></para>
|
||||
/// 举例2:LeftJoin("table1", "id", "user.id", "xid", "user.xid") -> LEFT JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
|
||||
/// </summary>
|
||||
public SelectImpl LeftJoin(string tableName, params string[] onFields) => Join("LEFT JOIN", tableName, onFields);
|
||||
/// <summary>
|
||||
/// 举例1:InnerJoin("table1", "id", "user.id") -> INNER JOIN [table1] b ON b.[id] = a.[id]<para></para>
|
||||
/// 举例2:InnerJoin("table1", "id", "user.id", "xid", "user.xid") -> INNER JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
|
||||
/// </summary>
|
||||
public SelectImpl InnerJoin(string tableName, params string[] onFields) => Join("INNER JOIN", tableName, onFields);
|
||||
/// <summary>
|
||||
/// 举例1:RightJoin("table1", "id", "user.id") -> RIGTH JOIN [table1] b ON b.[id] = a.[id]<para></para>
|
||||
/// 举例2:RightJoin("table1", "id", "user.id", "xid", "user.xid") -> RIGTH JOIN [table1] b ON b.[id] = [a].id] AND b.[xid] = a.[xid]
|
||||
/// </summary>
|
||||
public SelectImpl RightJoin(string tableName, params string[] onFields) => Join("RIGTH JOIN", tableName, onFields);
|
||||
SelectImpl Join(string joinType, string tableName, params string[] onFields)
|
||||
{
|
||||
if (onFields == null || onFields.Length == 0) throw new Exception($"{joinType} 参数 {nameof(onFields)} 不能为空");
|
||||
if (onFields.Length % 2 != 0) throw new Exception($"{joinType} 参数 {nameof(onFields)} 数组长度必须为偶数,正确:.LeftJoin(\"table1\", \"id\", \"user.id\")");
|
||||
|
||||
var table = _tables.Where(a => a.CsName.ToLower() == tableName?.ToLower()).FirstOrDefault();
|
||||
if (table == null) throw new Exception($"未定义表名 {tableName}");
|
||||
var alias = GetMaxAlias();
|
||||
var navKey = tableName;
|
||||
for (var a = 2; true; a++)
|
||||
{
|
||||
if (_tables[_mainTableIndex].ColumnsByCs.ContainsKey(navKey))
|
||||
{
|
||||
navKey = $"{tableName}{a}";
|
||||
continue;
|
||||
}
|
||||
if (_tableAlias.Any(b => b.NavPath.Length == 1 && b.NavPath.Last() == navKey))
|
||||
{
|
||||
navKey = $"{tableName}{a}";
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
FlagFetchResult(table, alias, navKey);
|
||||
var joinOn = new string[onFields.Length / 2];
|
||||
for (var a = 0; a < onFields.Length; a += 2)
|
||||
{
|
||||
var field1 = ParseField(table, alias, onFields[a]);
|
||||
if (field1 == null) throw new Exception($"未匹配字段名 {onFields[a]}");
|
||||
var field2 = ParseField(table, alias, onFields[a + 1]);
|
||||
if (field2 == null) throw new Exception($"未匹配字段名 {onFields[a + 1]}");
|
||||
joinOn[a / 2] = $"{field1.Item1} = {field2.Item1}";
|
||||
}
|
||||
_select.RawJoin($"{joinType} {_common.QuoteSqlName(table.DbName)} {alias} ON {string.Join(" AND ", joinOn)}");
|
||||
return this;
|
||||
}
|
||||
|
||||
class TableAliasInfo
|
||||
{
|
||||
public string Alias { get; set; }
|
||||
public ZeroTableInfo Table { get; set; }
|
||||
public string[] NavPath { get; set; }
|
||||
public List<NativeTuple<string, Action<SelectImpl>>> IncludeMany { get; set; } = new List<NativeTuple<string, Action<SelectImpl>>>();
|
||||
}
|
||||
class DbDataReaderContext
|
||||
{
|
||||
public DbDataReader Reader { get; set; }
|
||||
public int Index { get; set; }
|
||||
public T Result { get; set; }
|
||||
}
|
||||
string GetMaxAlias()
|
||||
{
|
||||
var max = (int)_tableAlias.Where(a => a.Alias.Length == 1).Max(a => a.Alias[0]);
|
||||
if (max < 'a') max = 'a';
|
||||
for (var a = 1; true; a++)
|
||||
{
|
||||
var alias = ((char)(max + a)).ToString();
|
||||
if (_tableAlias.Where(b => b.Alias == alias).Any()) continue;
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
TableAliasInfo FlagFetchResult(ZeroTableInfo table, string alias, string navPath)
|
||||
{
|
||||
var tableAlias = _tableAlias.Where(a => a.Alias == alias).FirstOrDefault();
|
||||
if (tableAlias == null)
|
||||
{
|
||||
var navPathArray = navPath.Split('.').Where(a => string.IsNullOrWhiteSpace(a) == false).ToArray();
|
||||
_tableAlias.Add(tableAlias = new TableAliasInfo
|
||||
{
|
||||
Alias = alias,
|
||||
Table = table,
|
||||
NavPath = navPathArray
|
||||
});
|
||||
}
|
||||
var sbfield = new StringBuilder();
|
||||
if (string.IsNullOrEmpty(_field) == false) sbfield.Append(", ").Append(_field);
|
||||
foreach (var col in table.Columns.Values)
|
||||
{
|
||||
var colName = col.Attribute.Name;
|
||||
for (var a = 2; true; a++)
|
||||
{
|
||||
if (_fieldAlias.ContainsKey(colName)) colName = $"{col.Attribute.Name}{a}";
|
||||
else break;
|
||||
}
|
||||
_fieldAlias.Add(colName, col.Attribute.Name);
|
||||
sbfield.Append(", ").Append(alias).Append(".").Append(_common.QuoteSqlName(col.Attribute.Name));
|
||||
if (colName != col.Attribute.Name) sbfield.Append(_common.FieldAsAlias(colName));
|
||||
}
|
||||
_field = sbfield.Remove(0, 2).ToString();
|
||||
sbfield.Clear();
|
||||
|
||||
_fieldReader.Add(dr =>
|
||||
{
|
||||
var pkIsNull = false;
|
||||
foreach (var col in table.Columns.Values)
|
||||
{
|
||||
if (pkIsNull == false && col.Attribute.IsPrimary)
|
||||
{
|
||||
pkIsNull = dr.Reader.IsDBNull(dr.Index);
|
||||
if (pkIsNull) dr.Result.Clear();
|
||||
}
|
||||
if (pkIsNull == false) dr.Result[col.CsName] = Utils.GetDataReaderValue(col.CsType, dr.Reader.GetValue(dr.Index));
|
||||
dr.Index++;
|
||||
}
|
||||
});
|
||||
return tableAlias;
|
||||
}
|
||||
T FetchResult(DbDataReader reader)
|
||||
{
|
||||
var fieldIndex = 0;
|
||||
var result = new T();
|
||||
for (var aliasIndex = 0; aliasIndex < _tableAlias.Count; aliasIndex++)
|
||||
{
|
||||
var navValue = result;
|
||||
var drctx = new DbDataReaderContext { Index = fieldIndex, Reader = reader };
|
||||
if (aliasIndex == 0)
|
||||
{
|
||||
drctx.Result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var isNull = false;
|
||||
for (var navidx = 0; navidx < _tableAlias[aliasIndex].NavPath.Length - 1; navidx++)
|
||||
{
|
||||
var navKey = _tableAlias[aliasIndex].NavPath[navidx];
|
||||
if (navValue.ContainsKey(navKey) == false)
|
||||
{
|
||||
isNull = true;
|
||||
break;
|
||||
}
|
||||
navValue = navValue[navKey] as T;
|
||||
if (navValue == null)
|
||||
{
|
||||
isNull = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isNull)
|
||||
{
|
||||
fieldIndex += _tableAlias[aliasIndex].Table.Columns.Count;
|
||||
continue;
|
||||
}
|
||||
drctx.Result = new T();
|
||||
navValue[_tableAlias[aliasIndex].NavPath.LastOrDefault()] = drctx.Result;
|
||||
}
|
||||
_fieldReader[aliasIndex](drctx);
|
||||
fieldIndex = drctx.Index;
|
||||
if (aliasIndex > 0 && drctx.Result.Any() == false) navValue.Remove(_tableAlias[aliasIndex].NavPath.LastOrDefault());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void IncludeMany(TableAliasInfo alias, NativeTuple<string, Action<SelectImpl>> navMany, List<T> list, List<string> flagIndexs = null)
|
||||
{
|
||||
if (list?.Any() != true) return;
|
||||
if (flagIndexs == null) flagIndexs = new List<string>();
|
||||
flagIndexs.Add(alias.Table.CsName);
|
||||
|
||||
var nav = alias.Table.Navigates[navMany.Item1];
|
||||
if (_includeAll && flagIndexs.Contains(nav.RefTable.CsName)) return;
|
||||
|
||||
if (nav.RefType == TableRefType.OneToMany)
|
||||
{
|
||||
var subTable = nav.RefTable;
|
||||
var subSelect = new SelectImpl(_dbcontext, subTable.CsName);
|
||||
if (_includeAll) subSelect.IncludeAll();
|
||||
|
||||
Func<Dictionary<string, bool>> getWhereDic = () =>
|
||||
{
|
||||
var sbDic = new Dictionary<string, bool>();
|
||||
for (var y = 0; y < list.Count; y++)
|
||||
{
|
||||
var sbWhereOne = new StringBuilder();
|
||||
sbWhereOne.Append("(");
|
||||
for (var z = 0; z < nav.Columns.Count; z++)
|
||||
{
|
||||
if (z > 0) sbWhereOne.Append(" AND ");
|
||||
var refcol = nav.RefTable.ColumnsByCs[nav.RefColumns[z]];
|
||||
var val = Utils.GetDataReaderValue(refcol.Attribute.MapType, list[y][nav.Columns[z]]);
|
||||
sbWhereOne.Append(_common.FormatSql($"a.{_common.QuoteSqlName(refcol.Attribute.Name)}={{0}}", val));
|
||||
}
|
||||
sbWhereOne.Append(")");
|
||||
var whereOne = sbWhereOne.ToString();
|
||||
sbWhereOne.Clear();
|
||||
if (sbDic.ContainsKey(whereOne) == false) sbDic.Add(whereOne, true);
|
||||
}
|
||||
return sbDic;
|
||||
};
|
||||
if (nav.Columns.Count == 1)
|
||||
{
|
||||
var refcol = nav.RefTable.ColumnsByCs[nav.RefColumns[0]];
|
||||
var args1 = $"a.{_common.QuoteSqlName(refcol.Attribute.Name)}";
|
||||
var left = _common.FormatSql("{0}", new object[] { list.Select(a => a[nav.Columns[0]]).Distinct().ToArray() });
|
||||
subSelect._select.Where($"({args1} in {left.Replace(", \r\n \r\n", $") \r\n OR {args1} in (")})");
|
||||
}
|
||||
else
|
||||
{
|
||||
var sbDic = getWhereDic();
|
||||
var sbWhere = new StringBuilder();
|
||||
foreach (var sbd in sbDic)
|
||||
sbWhere.Append(" OR ").Append(sbd.Key);
|
||||
subSelect._select.Where(sbWhere.Remove(0, 4).ToString());
|
||||
sbWhere.Clear();
|
||||
sbDic.Clear();
|
||||
}
|
||||
navMany.Item2?.Invoke(subSelect);
|
||||
var subList = subSelect.ToListPrivate(null, null, flagIndexs);
|
||||
foreach (var item in list)
|
||||
{
|
||||
item[nav.NavigateKey] = subList.Where(a =>
|
||||
{
|
||||
for (var z = 0; z < nav.Columns.Count; z++)
|
||||
if (CompareEntityPropertyValue(nav.Table.ColumnsByCs[nav.Columns[z]].Attribute.MapType, item[nav.Columns[z]], a[nav.RefColumns[z]]) == false)
|
||||
return false;
|
||||
return true;
|
||||
}).ToList();
|
||||
}
|
||||
subList.Clear();
|
||||
}
|
||||
else if (nav.RefType == TableRefType.ManyToMany)
|
||||
{
|
||||
var subTable = nav.RefTable;
|
||||
var subSelect = new SelectImpl(_dbcontext, subTable.CsName);
|
||||
if (_includeAll) subSelect.IncludeAll();
|
||||
|
||||
var middleJoinOn = string.Join(" AND ", nav.RefColumns.Select((bname, idx) =>
|
||||
$"midtb.{_common.QuoteSqlName(nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[nav.Columns.Count + idx]].Attribute.Name)} = a.{_common.QuoteSqlName(nav.RefTable.ColumnsByCs[bname].Attribute.Name)}"));
|
||||
subSelect._select.InnerJoin($"{_common.QuoteSqlName(nav.RefMiddleTable.DbName)} midtb ON {middleJoinOn}");
|
||||
|
||||
Func<Dictionary<string, bool>> getWhereDic = () =>
|
||||
{
|
||||
var sbDic = new Dictionary<string, bool>();
|
||||
for (var y = 0; y < list.Count; y++)
|
||||
{
|
||||
var sbWhereOne = new StringBuilder();
|
||||
sbWhereOne.Append("(");
|
||||
for (var z = 0; z < nav.Columns.Count; z++)
|
||||
{
|
||||
if (z > 0) sbWhereOne.Append(" AND ");
|
||||
var midcol = nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[z]];
|
||||
var val = Utils.GetDataReaderValue(midcol.Attribute.MapType, list[y][nav.Columns[z]]);
|
||||
sbWhereOne.Append(_common.FormatSql($"midtb.{_common.QuoteSqlName(midcol.Attribute.Name)}={{0}}", val));
|
||||
}
|
||||
sbWhereOne.Append(")");
|
||||
var whereOne = sbWhereOne.ToString();
|
||||
sbWhereOne.Clear();
|
||||
if (sbDic.ContainsKey(whereOne) == false) sbDic.Add(whereOne, true);
|
||||
}
|
||||
return sbDic;
|
||||
};
|
||||
if (nav.Columns.Count == 1)
|
||||
{
|
||||
var midcol = nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[0]];
|
||||
var args1 = $"midtb.{_common.QuoteSqlName(midcol.Attribute.Name)}";
|
||||
var left = _common.FormatSql("{0}", new object[] { list.Select(a => a[nav.Columns[0]]).Distinct().ToArray() });
|
||||
subSelect._select.Where($"({args1} in {left.Replace(", \r\n \r\n", $") \r\n OR {args1} in (")})");
|
||||
}
|
||||
else
|
||||
{
|
||||
var sbDic = getWhereDic();
|
||||
var sbWhere = new StringBuilder();
|
||||
foreach (var sbd in sbDic)
|
||||
sbWhere.Append(" OR ").Append(sbd.Key);
|
||||
subSelect._select.Where(sbWhere.Remove(0, 4).ToString());
|
||||
sbWhere.Clear();
|
||||
sbDic.Clear();
|
||||
}
|
||||
navMany.Item2?.Invoke(subSelect);
|
||||
var subList = subSelect.ToListPrivate(
|
||||
string.Join(", ", nav.MiddleColumns.Select((a, idx) => $"midtb.{_common.QuoteSqlName(nav.RefMiddleTable.ColumnsByCs[a].Attribute.Name)}{_common.FieldAsAlias($"midtb_field__{idx}")}")),
|
||||
(dict, dr) =>
|
||||
{
|
||||
var fieldCount = dr.FieldCount - nav.MiddleColumns.Count;
|
||||
for (var z = 0; z < nav.MiddleColumns.Count; z++)
|
||||
dict[$"midtb_field__{z}"] = Utils.GetDataReaderValue(nav.RefMiddleTable.ColumnsByCs[nav.MiddleColumns[z]].CsType, dr.GetValue(fieldCount + z));
|
||||
}, flagIndexs);
|
||||
foreach (var item in list)
|
||||
{
|
||||
item[nav.NavigateKey] = subList.Where(a =>
|
||||
{
|
||||
for (var z = 0; z < nav.Columns.Count; z++)
|
||||
if (CompareEntityPropertyValue(nav.Table.ColumnsByCs[nav.Columns[z]].Attribute.MapType, item[nav.Columns[z]], a[$"midtb_field__{z}"]) == false)
|
||||
return false;
|
||||
return true;
|
||||
}).ToList();
|
||||
}
|
||||
foreach (var subItem in subList)
|
||||
for (var z = 0; z < nav.MiddleColumns.Count; z++)
|
||||
subItem.Remove($"midtb_field__{z}");
|
||||
subList.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public string ToSql(string field = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(field)) return _select.ToSql(_field);
|
||||
return _select.ToSql(field);
|
||||
}
|
||||
List<T> ToListPrivate(string otherField, Action<T, DbDataReader> otherReader, List<string> flagIndexs = null)
|
||||
{
|
||||
var sql = string.IsNullOrWhiteSpace(otherField) ? this.ToSql() : this.ToSql($"{_field},{otherField}");
|
||||
var ret = new List<T>();
|
||||
var dbParms = _params.ToArray();
|
||||
var before = new Aop.CurdBeforeEventArgs(_tables[_mainTableIndex].Type, _tables[_mainTableIndex], Aop.CurdType.Select, sql, dbParms);
|
||||
_orm.Aop.CurdBeforeHandler?.Invoke(this, before);
|
||||
Exception exception = null;
|
||||
try
|
||||
{
|
||||
_orm.Ado.ExecuteReader(_dbcontext._transaction?.Connection, _dbcontext._transaction, fetch =>
|
||||
{
|
||||
var item = FetchResult(fetch.Object);
|
||||
otherReader?.Invoke(item, fetch.Object);
|
||||
ret.Add(item);
|
||||
}, CommandType.Text, sql, _dbcontext._commandTimeout, dbParms);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
var after = new Aop.CurdAfterEventArgs(before, exception, ret);
|
||||
_orm.Aop.CurdAfterHandler?.Invoke(this, after);
|
||||
}
|
||||
foreach (var join in _tableAlias)
|
||||
{
|
||||
if (join.IncludeMany.Any() == false) continue;
|
||||
var list = new List<T>();
|
||||
if (join.Alias == "a") list = ret;
|
||||
else
|
||||
{
|
||||
foreach (var obj in ret)
|
||||
{
|
||||
T item = obj;
|
||||
foreach (var navKey in join.NavPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(navKey)) continue;
|
||||
item.TryGetValue(navKey, out var obj2);
|
||||
item = obj2 as T;
|
||||
if (item == null) break;
|
||||
}
|
||||
if (item != null) list.Add(item);
|
||||
}
|
||||
}
|
||||
foreach(var navMany in join.IncludeMany)
|
||||
IncludeMany(join, navMany, list, flagIndexs);
|
||||
}
|
||||
if (_useStates && flagIndexs == null)
|
||||
foreach (var item in ret)
|
||||
_dbcontext.AttachCascade(_tables[_mainTableIndex], item, true);
|
||||
return ret;
|
||||
}
|
||||
public List<T> ToList() => ToListPrivate(null, null);
|
||||
public T ToOne() => ToListPrivate(null, null).FirstOrDefault();
|
||||
public T First() => ToOne();
|
||||
public bool Any() => _select.Any();
|
||||
public long Count() => _select.Count();
|
||||
public SelectImpl Count(out long count)
|
||||
{
|
||||
_select.Count(out count);
|
||||
return this;
|
||||
}
|
||||
public SelectImpl WithTransaction(DbTransaction transaction)
|
||||
{
|
||||
_select.WithTransaction(transaction);
|
||||
return this;
|
||||
}
|
||||
public SelectImpl WithConnection(DbConnection connection)
|
||||
{
|
||||
_select.WithConnection(connection);
|
||||
return this;
|
||||
}
|
||||
public SelectImpl CommandTimeout(int timeout)
|
||||
{
|
||||
_select.CommandTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
public SelectImpl Distinct()
|
||||
{
|
||||
_select.Distinct();
|
||||
return this;
|
||||
}
|
||||
public SelectImpl Master()
|
||||
{
|
||||
_select.Master();
|
||||
return this;
|
||||
}
|
||||
public SelectImpl ForUpdate(bool nowait = false)
|
||||
{
|
||||
_select.ForUpdate(nowait);
|
||||
return this;
|
||||
}
|
||||
|
||||
NativeTuple<string, ColumnInfo> ParseField(ZeroTableInfo firstTable, string firstTableAlias, string property)
|
||||
{
|
||||
if (string.IsNullOrEmpty(property)) return null;
|
||||
var field = property.Split('.').Select(a => a.Trim()).ToArray();
|
||||
|
||||
if (field.Length == 1)
|
||||
{
|
||||
if (firstTable != null && firstTable.ColumnsByCs.TryGetValue(field[0], out var col2) == true)
|
||||
return NativeTuple.Create($"{firstTableAlias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
|
||||
|
||||
foreach (var ta2 in _tableAlias)
|
||||
{
|
||||
if (ta2.Table.ColumnsByCs.TryGetValue(field[0], out col2))
|
||||
return NativeTuple.Create($"{ta2.Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
|
||||
}
|
||||
}
|
||||
else if (field.Length == 2)
|
||||
{
|
||||
if (firstTable != null && firstTable.CsName.ToLower() == field[0].ToLower() && firstTable.ColumnsByCs.TryGetValue(field[1], out var col2) == true)
|
||||
return NativeTuple.Create($"{firstTableAlias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
|
||||
|
||||
var ta2s = _tableAlias.Where(a => a.Table.CsName.ToLower() == field[0].ToLower()).ToArray();
|
||||
if (ta2s.Length == 1 && ta2s[0].Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
|
||||
return NativeTuple.Create($"{ta2s[0].Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
|
||||
if (ta2s.Length > 1)
|
||||
{
|
||||
ta2s = _tableAlias.Where(a => a.Table.CsName == field[0]).ToArray();
|
||||
if (ta2s.Length == 1 && ta2s[0].Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
|
||||
return NativeTuple.Create($"{ta2s[0].Alias}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
|
||||
}
|
||||
if (_tableAlias.Where(a => a.Alias == field[0]).FirstOrDefault()?.Table.ColumnsByCs.TryGetValue(field[1], out col2) == true)
|
||||
return NativeTuple.Create($"{field[0]}.{_common.QuoteSqlName(col2.Attribute.Name)}", col2);
|
||||
}
|
||||
|
||||
var navPath = string.Join(".", field.Skip(1).Take(field.Length - 1));
|
||||
var ta = _tableAlias.Where(a => string.Join(".", a.NavPath) == navPath).FirstOrDefault();
|
||||
if (ta?.Table.ColumnsByCs.TryGetValue(field.Last(), out var col) == true)
|
||||
return NativeTuple.Create($"{ta.Alias}.{_common.QuoteSqlName(col.Attribute.Name)}", col);
|
||||
throw new Exception(CoreStrings.Cannot_Match_Property(property));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WHERE [Id] IN (...)
|
||||
/// </summary>
|
||||
public SelectImpl Where(IEnumerable<T> items)
|
||||
{
|
||||
var alias = _tableAlias.Where(a => a.Table == _tables[_mainTableIndex]).FirstOrDefault()?.Alias;
|
||||
if (!string.IsNullOrWhiteSpace(alias)) alias = $"{alias}.";
|
||||
var where = _common.WhereItems(_tables[_mainTableIndex].Primarys, alias, items);
|
||||
_select.Where(where);
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Where(new { Year = 2017, CategoryId = 198, IsPublished = true })<para></para>
|
||||
/// WHERE [Year] = 2017 AND [CategoryId] = 198 AND [IsPublished] = 1
|
||||
/// </summary>
|
||||
public SelectImpl Where(object multipleFields)
|
||||
{
|
||||
if (multipleFields == null) return this;
|
||||
foreach (var prop in multipleFields.GetType().GetProperties())
|
||||
WhereDynamicFilter(new DynamicFilterInfo { Field = prop.Name, Operator = DynamicFilterOperator.Eq, Value = prop.GetValue(multipleFields, null) });
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// WHERE [field] = ..
|
||||
/// </summary>
|
||||
public SelectImpl Where(string field, object value) => WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Eq, Value = value });
|
||||
public SelectImpl Where(string field, string @operator, object value)
|
||||
{
|
||||
switch (@operator?.ToLower().Trim())
|
||||
{
|
||||
case "=":
|
||||
case "==":
|
||||
case "eq":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Eq, Value = value });
|
||||
case "!=":
|
||||
case "<>":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.NotEqual, Value = value });
|
||||
case ">":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.GreaterThan, Value = value });
|
||||
case ">=":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.GreaterThanOrEqual, Value = value });
|
||||
case "<":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.LessThan, Value = value });
|
||||
case "<=":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.LessThanOrEqual, Value = value });
|
||||
case "like":
|
||||
case "contains":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Contains, Value = value });
|
||||
case "!like":
|
||||
case "notlike":
|
||||
case "not like":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.NotContains, Value = value });
|
||||
case "in":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Any, Value = value });
|
||||
case "!in":
|
||||
case "notin":
|
||||
case "not in":
|
||||
return WhereDynamicFilter(new DynamicFilterInfo { Field = field, Operator = DynamicFilterOperator.Any, Value = value });
|
||||
}
|
||||
throw new Exception($"未实现 {@operator}");
|
||||
}
|
||||
public SelectImpl WhereColumns(string field1, string @operator, string field2)
|
||||
{
|
||||
var field1Result = ParseField(null, null, field1);
|
||||
if (field1Result == null) throw new Exception($"未匹配字段名 {field1}");
|
||||
var field2Result = ParseField(null, null, field2);
|
||||
if (field2Result == null) throw new Exception($"未匹配字段名 {field2}");
|
||||
switch (@operator?.ToLower().Trim())
|
||||
{
|
||||
case "=":
|
||||
case "==":
|
||||
case "eq":
|
||||
_select.Where($"{field1Result.Item1} = {field2Result.Item1}");
|
||||
return this;
|
||||
case "!=":
|
||||
case "<>":
|
||||
_select.Where($"{field1Result.Item1} <> {field2Result.Item1}");
|
||||
return this;
|
||||
case ">":
|
||||
_select.Where($"{field1Result.Item1} > {field2Result.Item1}");
|
||||
return this;
|
||||
case ">=":
|
||||
_select.Where($"{field1Result.Item1} >= {field2Result.Item1}");
|
||||
return this;
|
||||
case "<":
|
||||
_select.Where($"{field1Result.Item1} < {field2Result.Item1}");
|
||||
return this;
|
||||
case "<=":
|
||||
_select.Where($"{field1Result.Item1} <= {field2Result.Item1}");
|
||||
return this;
|
||||
}
|
||||
throw new Exception($"未实现 {@operator}");
|
||||
}
|
||||
public SelectImpl WhereDynamicFilter(DynamicFilterInfo filter)
|
||||
{
|
||||
var sql = ParseDynamicFilter(filter);
|
||||
_selectProvider._where.Append(sql);
|
||||
return this;
|
||||
}
|
||||
string ParseDynamicFilter(DynamicFilterInfo filter)
|
||||
{
|
||||
var replacedFilter = new DynamicFilterInfo();
|
||||
var replacedMap = new List<NativeTuple<string, string>>();
|
||||
LocalCloneFilter(filter, replacedFilter);
|
||||
var oldWhere = _selectProvider._where.ToString();
|
||||
var newWhere = "";
|
||||
try
|
||||
{
|
||||
_selectProvider._where.Clear();
|
||||
_select.WhereDynamicFilter(replacedFilter);
|
||||
newWhere = _selectProvider._where.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_selectProvider._where.Clear().Append(oldWhere);
|
||||
}
|
||||
foreach (var rm in replacedMap)
|
||||
{
|
||||
var find = $"a.{_common.QuoteSqlName(rm.Item1)}";
|
||||
var idx = newWhere.IndexOf(find);
|
||||
if (idx != -1) newWhere = $"{newWhere.Substring(0, idx)}{rm.Item2}{newWhere.Substring(idx + find.Length)}";
|
||||
}
|
||||
return newWhere;
|
||||
|
||||
void LocalCloneFilter(DynamicFilterInfo source, DynamicFilterInfo target)
|
||||
{
|
||||
target.Field = source.Field;
|
||||
target.Operator = source.Operator;
|
||||
target.Value = source.Value;
|
||||
target.Logic = source.Logic;
|
||||
if (string.IsNullOrWhiteSpace(source.Field) == false)
|
||||
{
|
||||
var parseResult = ParseField(null, null, source.Field);
|
||||
if (parseResult != null)
|
||||
{
|
||||
if (TestDynamicFilterInfo._dictTypeToPropertyname.TryGetValue(parseResult.Item2.Attribute.MapType, out var pname))
|
||||
target.Field = pname;
|
||||
else
|
||||
target.Field = TestDynamicFilterInfo._dictTypeToPropertyname[typeof(string)];
|
||||
replacedMap.Add(NativeTuple.Create(target.Field, parseResult.Item1));
|
||||
}
|
||||
}
|
||||
if (source.Filters?.Any() == true)
|
||||
{
|
||||
target.Filters = new List<DynamicFilterInfo>();
|
||||
foreach (var sourceChild in source.Filters)
|
||||
{
|
||||
var targetChild = new DynamicFilterInfo();
|
||||
target.Filters.Add(targetChild);
|
||||
LocalCloneFilter(sourceChild, targetChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public class SubQuery
|
||||
{
|
||||
internal SelectImpl _parentQuery;
|
||||
internal SubQuery() { }
|
||||
public SelectImpl From(string tableName)
|
||||
{
|
||||
var query = _parentQuery._dbcontext.SelectNoTracking(tableName);
|
||||
query._selectProvider._tables[0].Alias =
|
||||
query._tableAlias[0].Alias = $"sub_{_parentQuery._tableAlias[0].Alias}";
|
||||
query._tableAlias.AddRange(_parentQuery._tableAlias);
|
||||
return query;
|
||||
}
|
||||
}
|
||||
public SelectImpl WhereExists(Func<SubQuery, SelectImpl> q)
|
||||
{
|
||||
var query = q?.Invoke(new SubQuery { _parentQuery = this });
|
||||
switch (_orm.Ado.DataType)
|
||||
{
|
||||
case DataType.Oracle:
|
||||
case DataType.OdbcOracle:
|
||||
case DataType.CustomOracle:
|
||||
case DataType.Dameng:
|
||||
case DataType.OdbcDameng:
|
||||
case DataType.GBase:
|
||||
query.Limit(-1);
|
||||
break;
|
||||
default:
|
||||
query.Limit(1); //#462 ORACLE rownum <= 2 会影响索引变慢
|
||||
break;
|
||||
}
|
||||
_selectProvider._where.Append($" AND EXISTS({query.ToSql("1").Replace(" \r\n", " \r\n ")})");
|
||||
return this;
|
||||
}
|
||||
public SelectImpl GroupByRaw(string sql)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql)) return this;
|
||||
_useStates = false;
|
||||
_groupBy = $"{_groupBy}, {sql}";
|
||||
_useStates = false;
|
||||
_select.GroupBy(_groupBy.Substring(2));
|
||||
return this;
|
||||
}
|
||||
public SelectImpl GroupBy(string[] fields)
|
||||
{
|
||||
var count = 0;
|
||||
for (var a = 0; a < fields.Length; a++)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fields[a])) continue;
|
||||
var field1 = ParseField(null, null, fields[a]);
|
||||
if (field1 == null) throw new Exception($"未匹配字段名 {fields[a]}");
|
||||
_groupBy = $"{_groupBy}, {field1.Item1}";
|
||||
count++;
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
_useStates = false;
|
||||
_select.GroupBy(_groupBy.Substring(2));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public SelectImpl HavingRaw(string sql)
|
||||
{
|
||||
_select.Having(sql);
|
||||
return this;
|
||||
}
|
||||
public SelectImpl OrderByRaw(string sql)
|
||||
{
|
||||
_select.OrderBy(sql);
|
||||
return this;
|
||||
}
|
||||
SelectImpl OrderBy(bool isdesc, string[] fields)
|
||||
{
|
||||
for (var a = 0; a < fields.Length; a ++)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fields[a])) continue;
|
||||
var field1 = ParseField(null, null, fields[a]);
|
||||
if (field1 == null) throw new Exception($"未匹配字段名 {fields[a]}");
|
||||
if (isdesc) _select.OrderBy($"{field1.Item1} DESC");
|
||||
else _select.OrderBy(field1.Item1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public SelectImpl OrderBy(params string[] fields) => OrderBy(false, fields);
|
||||
public SelectImpl OrderByDescending(params string[] fields) => OrderBy(true, fields);
|
||||
public SelectImpl Offset(int offset)
|
||||
{
|
||||
_select.Offset(offset);
|
||||
return this;
|
||||
}
|
||||
public SelectImpl Limit(int limit)
|
||||
{
|
||||
_select.Limit(limit);
|
||||
return this;
|
||||
}
|
||||
public SelectImpl Skip(int offset) => Offset(offset);
|
||||
public SelectImpl Take(int limit) => Limit(limit);
|
||||
public SelectImpl Page(int pageNumber, int pageSize)
|
||||
{
|
||||
_select.Page(pageNumber, pageSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
TResult InternalQuerySingle<TResult>(string field) => _orm.Ado.CommandFluent(this.ToSql(field))
|
||||
.WithConnection(_selectProvider._connection)
|
||||
.WithTransaction(_selectProvider._transaction).QuerySingle<TResult>();
|
||||
public decimal Sum(string field)
|
||||
{
|
||||
var field1 = ParseField(null, null, field);
|
||||
if (field1 == null) throw new Exception($"未匹配字段名 {field}");
|
||||
return InternalQuerySingle<decimal>($"sum({field1.Item1})");
|
||||
}
|
||||
public TMember Min<TMember>(string field)
|
||||
{
|
||||
var field1 = ParseField(null, null, field);
|
||||
if (field1 == null) throw new Exception($"未匹配字段名 {field}");
|
||||
return InternalQuerySingle<TMember>($"min({field1.Item1})");
|
||||
}
|
||||
public TMember Max<TMember>(string field)
|
||||
{
|
||||
var field1 = ParseField(null, null, field);
|
||||
if (field1 == null) throw new Exception($"未匹配字段名 {field}");
|
||||
return InternalQuerySingle<TMember>($"max({field1.Item1})");
|
||||
}
|
||||
public double Avg(string field)
|
||||
{
|
||||
var field1 = ParseField(null, null, field);
|
||||
if (field1 == null) throw new Exception($"未匹配字段名 {field}");
|
||||
return InternalQuerySingle<double>($"avg({field1.Item1})");
|
||||
}
|
||||
|
||||
|
||||
[Table(DisableSyncStructure = true)]
|
||||
class TestDynamicFilterInfo
|
||||
{
|
||||
public Guid DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf00 { get; set; }
|
||||
public bool DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf01 { get; set; }
|
||||
public string DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf02 { get; set; }
|
||||
public char DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf03 { get; set; }
|
||||
public DateTime DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf04 { get; set; }
|
||||
public DateTimeOffset DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf05 { get; set; }
|
||||
public TimeSpan DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf06 { get; set; }
|
||||
|
||||
public int DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf07 { get; set; }
|
||||
public long DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf08 { get; set; }
|
||||
public short DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf09 { get; set; }
|
||||
public sbyte DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf10 { get; set; }
|
||||
|
||||
public uint DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf11 { get; set; }
|
||||
public ulong DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf12 { get; set; }
|
||||
public ushort DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf13 { get; set; }
|
||||
public byte DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf14 { get; set; }
|
||||
public byte[] DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf15 { get; set; }
|
||||
|
||||
public double DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf16 { get; set; }
|
||||
public float DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf17 { get; set; }
|
||||
public decimal DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf18 { get; set; }
|
||||
|
||||
internal static Dictionary<Type, string> _dictTypeToPropertyname = new Dictionary<Type, string>
|
||||
{
|
||||
[typeof(Guid)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf00),
|
||||
[typeof(bool)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf01),
|
||||
[typeof(string)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf02),
|
||||
[typeof(char)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf03),
|
||||
[typeof(DateTime)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf04),
|
||||
[typeof(DateTimeOffset)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf05),
|
||||
[typeof(TimeSpan)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf06),
|
||||
|
||||
[typeof(int)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf07),
|
||||
[typeof(long)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf08),
|
||||
[typeof(short)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf09),
|
||||
[typeof(sbyte)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf10),
|
||||
|
||||
[typeof(uint)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf11),
|
||||
[typeof(ulong)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf12),
|
||||
[typeof(ushort)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf13),
|
||||
[typeof(byte)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf14),
|
||||
[typeof(byte[])] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf15),
|
||||
|
||||
[typeof(double)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf16),
|
||||
[typeof(float)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf17),
|
||||
[typeof(decimal)] = nameof(DynamicField_4bf98fbe2b4d4d14bbb3fc66fa08bf18),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1537
Extensions/FreeSql.Extensions.ZeroEntity/ZeroDbContext.cs
Normal file
1537
Extensions/FreeSql.Extensions.ZeroEntity/ZeroDbContext.cs
Normal file
File diff suppressed because it is too large
Load Diff
97
Extensions/FreeSql.Extensions.ZeroEntity/ZeroDescriptor.cs
Normal file
97
Extensions/FreeSql.Extensions.ZeroEntity/ZeroDescriptor.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using FreeSql.Internal.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FreeSql.Extensions.ZeroEntity
|
||||
{
|
||||
public class TableDescriptor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string DbName { get; set; }
|
||||
public string AsTable { get; set; }
|
||||
public bool DisableSyncStructure { get; set; }
|
||||
public string Comment { get; set; }
|
||||
public List<ColumnDescriptor> Columns { get; } = new List<ColumnDescriptor>();
|
||||
public List<NavigateDescriptor> Navigates { get; } = new List<NavigateDescriptor>();
|
||||
public List<IndexDescriptor> Indexes { get; } = new List<IndexDescriptor>();
|
||||
|
||||
public class ColumnDescriptor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string DbType { get; set; }
|
||||
bool? _IsPrimary, _IsIdentity, _IsNullable, _IsVersion;
|
||||
public bool IsPrimary { get => _IsPrimary ?? false; set => _IsPrimary = value; }
|
||||
public bool IsIdentity { get => _IsIdentity ?? false; set => _IsIdentity = value; }
|
||||
public bool IsNullable { get => _IsNullable ?? false; set => _IsNullable = value; }
|
||||
public bool IsVersion { get => _IsVersion ?? false; set => _IsVersion = value; }
|
||||
public Type MapType { get; set; }
|
||||
public DateTimeKind ServerTime { get; set; }
|
||||
public string InsertValueSql { get; set; }
|
||||
int? _StringLength;
|
||||
public int StringLength { get => _StringLength ?? 0; set => _StringLength = value; }
|
||||
int? _Precision;
|
||||
public int Precision { get => _Precision ?? 0; set => _Precision = value; }
|
||||
int? _Scale;
|
||||
public int Scale { get => _Scale ?? 0; set => _Scale = value; }
|
||||
public string Comment { get; set; }
|
||||
|
||||
public ColumnAttribute ToAttribute()
|
||||
{
|
||||
var attr = new ColumnAttribute
|
||||
{
|
||||
Name = Name,
|
||||
DbType = DbType,
|
||||
MapType = MapType,
|
||||
ServerTime = ServerTime,
|
||||
InsertValueSql = InsertValueSql,
|
||||
};
|
||||
if (_IsPrimary != null) attr.IsPrimary = IsPrimary;
|
||||
if (_IsIdentity != null) attr.IsIdentity = IsIdentity;
|
||||
if (_IsNullable != null) attr.IsNullable = IsNullable;
|
||||
if (_IsVersion != null) attr.IsVersion = IsVersion;
|
||||
if (_StringLength != null) attr.StringLength = StringLength;
|
||||
if (_Precision != null) attr.Precision = Precision;
|
||||
if (_Scale != null) attr.Scale = Scale;
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
public class IndexDescriptor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Fields { get; set; }
|
||||
public bool IsUnique { get; set; }
|
||||
public IndexMethod IndexMethod { get; set; }
|
||||
}
|
||||
public class NavigateDescriptor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public NavigateType Type { get; set; }
|
||||
public string RelTable { get; set; }
|
||||
public string Bind { get; set; }
|
||||
public string ManyToMany { get; set; }
|
||||
}
|
||||
public enum NavigateType
|
||||
{
|
||||
OneToOne, ManyToOne, OneToMany, ManyToMany
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ZeroTableRef
|
||||
{
|
||||
internal string NavigateKey { get; set; }
|
||||
public TableRefType RefType { get; set; }
|
||||
internal ZeroTableInfo Table { get; set; }
|
||||
internal ZeroTableInfo RefTable { get; set; }
|
||||
internal ZeroTableInfo RefMiddleTable { get; set; }
|
||||
|
||||
public List<string> Columns { get; set; } = new List<string>();
|
||||
public List<string> MiddleColumns { get; set; } = new List<string>();
|
||||
public List<string> RefColumns { get; set; } = new List<string>();
|
||||
}
|
||||
class ZeroTableInfo : TableInfo
|
||||
{
|
||||
public Dictionary<string, ZeroTableRef> Navigates { get; set; } = new Dictionary<string, ZeroTableRef>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
BIN
Extensions/FreeSql.Extensions.ZeroEntity/key.snk
Normal file
BIN
Extensions/FreeSql.Extensions.ZeroEntity/key.snk
Normal file
Binary file not shown.
354
Extensions/FreeSql.Generator/ConsoleApp.cs
Normal file
354
Extensions/FreeSql.Generator/ConsoleApp.cs
Normal file
@ -0,0 +1,354 @@
|
||||
using FreeSql.DatabaseModel;
|
||||
using RazorEngine.Templating;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Console = Colorful.Console;
|
||||
|
||||
namespace FreeSql.Generator
|
||||
{
|
||||
class ConsoleApp
|
||||
{
|
||||
string ArgsRazorRaw { get; }
|
||||
string ArgsRazor { get; }
|
||||
bool[] ArgsNameOptions { get; }
|
||||
string ArgsNameSpace { get; }
|
||||
DataType ArgsDbType { get; }
|
||||
string ArgsConnectionString { get; }
|
||||
string ArgsFilter { get; }
|
||||
string ArgsMatch { get; }
|
||||
string ArgsJson { get; }
|
||||
string ArgsFileName { get; }
|
||||
bool ArgsReadKey { get; }
|
||||
internal string ArgsOutput { get; private set; }
|
||||
|
||||
public ConsoleApp(string[] args, ManualResetEvent wait)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
//var gb2312 = Encoding.GetEncoding("GB2312");
|
||||
//if (gb2312 != null)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// Console.OutputEncoding = gb2312;
|
||||
// Console.InputEncoding = gb2312;
|
||||
// }
|
||||
// catch { }
|
||||
//}
|
||||
|
||||
//var ntjson = Assembly.LoadFile(@"C:\Users\28810\Desktop\testfreesql\bin\Debug\netcoreapp2.2\publish\testfreesql.dll");
|
||||
|
||||
//using (var gen = new Generator(new GeneratorOptions()))
|
||||
//{
|
||||
// gen.TraceLog = log => Console.WriteFormatted(log + "\r\n", Color.DarkGray);
|
||||
// gen.Build(ArgsOutput, new[] { typeof(ojbk.Entities.AuthRole) }, false);
|
||||
//}
|
||||
|
||||
var version = "v" + string.Join(".", typeof(ConsoleApp).Assembly.GetName().Version.ToString().Split('.').Where((a, b) => b <= 2));
|
||||
Console.WriteAscii(" FreeSql", Color.Violet);
|
||||
Console.WriteFormatted(@"
|
||||
# Github # {0} {1}
|
||||
", Color.SlateGray,
|
||||
new Colorful.Formatter("https://github.com/2881099/FreeSql", Color.DeepSkyBlue),
|
||||
new Colorful.Formatter("v" + string.Join(".", typeof(ConsoleApp).Assembly.GetName().Version.ToString().Split('.').Where((a, b) => b <= 2)), Color.SlateGray));
|
||||
|
||||
ArgsRazorRaw = "1";
|
||||
ArgsRazor = RazorContentManager.实体类_特性_cshtml;
|
||||
ArgsNameOptions = new[] { false, false, false, false };
|
||||
ArgsNameSpace = "MyProject";
|
||||
ArgsFilter = "";
|
||||
ArgsMatch = "";
|
||||
ArgsJson = "Newtonsoft.Json";
|
||||
ArgsFileName = "{name}.cs";
|
||||
ArgsReadKey = true;
|
||||
Action<string> setArgsOutput = value =>
|
||||
{
|
||||
ArgsOutput = value;
|
||||
ArgsOutput = ArgsOutput.Trim().TrimEnd('/', '\\');
|
||||
ArgsOutput += ArgsOutput.Contains("\\") ? "\\" : "/";
|
||||
if (!Directory.Exists(ArgsOutput))
|
||||
Directory.CreateDirectory(ArgsOutput);
|
||||
};
|
||||
setArgsOutput(Directory.GetCurrentDirectory());
|
||||
|
||||
string args0 = args[0].Trim().ToLower();
|
||||
if (args[0] == "?" || args0 == "--help" || args0 == "-help")
|
||||
{
|
||||
|
||||
Console.WriteFormatted(@"
|
||||
{0}
|
||||
|
||||
更新工具:dotnet tool update -g FreeSql.Generator
|
||||
|
||||
|
||||
# 快速开始 #
|
||||
|
||||
> {1} {2} 1 {3} 0,0,0,0 {4} MyProject {5} ""MySql,Data Source=127.0.0.1;...""
|
||||
|
||||
-Razor 1 * 选择模板:实体类+特性
|
||||
-Razor 2 * 选择模板:实体类+特性+导航属性
|
||||
-Razor ""d:\diy.cshtml"" * 自定义模板文件,如乱码请修改为UTF8(不带BOM)编码格式
|
||||
|
||||
-NameOptions * 4个布尔值对应:
|
||||
首字母大写
|
||||
首字母大写,其他小写
|
||||
全部小写
|
||||
下划线转驼峰
|
||||
|
||||
-NameSpace * 命名空间
|
||||
|
||||
-DB ""{6},data source=127.0.0.1;port=3306;user id=root;password=root;initial catalog=数据库;charset=utf8;sslmode=none;max pool size=2""
|
||||
-DB ""{7},data source=.;integrated security=True;initial catalog=数据库;pooling=true;max pool size=2""
|
||||
-DB ""{8},host=127.0.0.1;port=5432;username=postgres;password=123456;database=数据库;pooling=true;maximum pool size=2""
|
||||
-DB ""{9},user id=user1;password=123456;data source=//127.0.0.1:1521/XE;pooling=true;max pool size=2""
|
||||
-DB ""{10},data source=document.db""
|
||||
-DB ""{14},database=localhost:D:\fbdata\EXAMPLES.fdb;user=sysdba;password=123456;max pool size=2""
|
||||
-DB ""{11},server=127.0.0.1;port=5236;user id=2user;password=123456789;database=2user;poolsize=2""
|
||||
-DB ""{12},server=127.0.0.1;port=54321;uid=USER2;pwd=123456789;database=数据库""
|
||||
-DB ""{13},host=127.0.0.1;port=2003;database=数据库;username=SYSDBA;password=szoscar55;maxpoolsize=2""
|
||||
* {11}(达梦数据库)、{12}(人大金仓数据库)、{13}(神舟通用数据库)
|
||||
|
||||
-Filter Table+View+StoreProcedure
|
||||
默认生成:表+视图+存储过程
|
||||
如果不想生成视图和存储过程 -Filter View+StoreProcedure
|
||||
|
||||
-Match 表名或正则表达式,只生成匹配的表,如:dbo\.TB_.+
|
||||
-Json NTJ、STJ、NONE
|
||||
Newtonsoft.Json、System.Text.Json、不生成
|
||||
|
||||
-FileName 文件名,默认:{name}.cs
|
||||
-Output 保存路径,默认为当前 shell 所在目录
|
||||
{15}
|
||||
|
||||
", Color.SlateGray,
|
||||
new Colorful.Formatter("FreeSql 快速生成数据库的实体类", Color.SlateGray),
|
||||
new Colorful.Formatter("FreeSql.Generator", Color.White),
|
||||
new Colorful.Formatter("-Razor", Color.ForestGreen),
|
||||
new Colorful.Formatter("-NameOptions", Color.ForestGreen),
|
||||
new Colorful.Formatter("-NameSpace", Color.ForestGreen),
|
||||
new Colorful.Formatter("-DB", Color.ForestGreen),
|
||||
new Colorful.Formatter("MySql", Color.Yellow),
|
||||
new Colorful.Formatter("SqlServer", Color.Yellow),
|
||||
new Colorful.Formatter("PostgreSQL", Color.Yellow),
|
||||
new Colorful.Formatter("Oracle", Color.Yellow),
|
||||
new Colorful.Formatter("Sqlite", Color.Yellow),
|
||||
new Colorful.Formatter("Dameng", Color.Yellow),
|
||||
new Colorful.Formatter("KingbaseES", Color.Yellow),
|
||||
new Colorful.Formatter("ShenTong", Color.Yellow),
|
||||
new Colorful.Formatter("Firebird", Color.Yellow),
|
||||
new Colorful.Formatter("推荐在实体类目录创建 gen.bat,双击它重新所有实体类", Color.ForestGreen)
|
||||
);
|
||||
wait.Set();
|
||||
return;
|
||||
}
|
||||
for (int a = 0; a < args.Length; a++)
|
||||
{
|
||||
switch (args[a].Trim().ToLower())
|
||||
{
|
||||
case "-razor":
|
||||
ArgsRazorRaw = args[a + 1].Trim();
|
||||
switch (ArgsRazorRaw)
|
||||
{
|
||||
case "1": ArgsRazor = RazorContentManager.实体类_特性_cshtml; break;
|
||||
case "2": ArgsRazor = RazorContentManager.实体类_特性_导航属性_cshtml; break;
|
||||
default: ArgsRazor = File.ReadAllText(args[a + 1], Encoding.UTF8); break;
|
||||
}
|
||||
a++;
|
||||
break;
|
||||
|
||||
case "-nameoptions":
|
||||
ArgsNameOptions = args[a + 1].Split(',').Select(opt => opt == "1").ToArray();
|
||||
if (ArgsNameOptions.Length != 4) throw new ArgumentException(CoreStrings.S_NameOptions_Incorrect);
|
||||
a++;
|
||||
break;
|
||||
case "-namespace":
|
||||
ArgsNameSpace = args[a + 1];
|
||||
a++;
|
||||
break;
|
||||
case "-db":
|
||||
var dbargs = args[a + 1].Split(',', 2);
|
||||
if (dbargs.Length != 2) throw new ArgumentException(CoreStrings.S_DB_ParameterError);
|
||||
|
||||
switch (dbargs[0].Trim().ToLower())
|
||||
{
|
||||
case "mysql": ArgsDbType = DataType.MySql; break;
|
||||
case "sqlserver": ArgsDbType = DataType.SqlServer; break;
|
||||
case "postgresql": ArgsDbType = DataType.PostgreSQL; break;
|
||||
case "oracle": ArgsDbType = DataType.Oracle; break;
|
||||
case "sqlite": ArgsDbType = DataType.Sqlite; break;
|
||||
case "firebird": ArgsDbType = DataType.Firebird; break;
|
||||
case "dameng": ArgsDbType = DataType.Dameng; break;
|
||||
case "kingbasees": ArgsDbType = DataType.KingbaseES; break;
|
||||
case "shentong": ArgsDbType = DataType.ShenTong; break;
|
||||
case "clickhouse": ArgsDbType = DataType.ClickHouse; break;
|
||||
default: throw new ArgumentException(CoreStrings.S_DB_ParameterError_UnsupportedType(dbargs[0]));
|
||||
}
|
||||
ArgsConnectionString = dbargs[1].Trim();
|
||||
a++;
|
||||
break;
|
||||
case "-filter":
|
||||
ArgsFilter = args[a + 1];
|
||||
a++;
|
||||
break;
|
||||
case "-match":
|
||||
ArgsMatch = args[a + 1];
|
||||
if (Regex.IsMatch("", ArgsMatch)) { } //throw
|
||||
a++;
|
||||
break;
|
||||
case "-json":
|
||||
switch(args[a + 1].Trim().ToLower())
|
||||
{
|
||||
case "none":
|
||||
ArgsJson = "";
|
||||
break;
|
||||
case "stj":
|
||||
ArgsJson = "System.Text.Json";
|
||||
break;
|
||||
}
|
||||
a++;
|
||||
break;
|
||||
case "-filename":
|
||||
ArgsFileName = args[a + 1];
|
||||
a++;
|
||||
break;
|
||||
case "-readkey":
|
||||
ArgsReadKey = args[a + 1].Trim() == "1";
|
||||
a++;
|
||||
break;
|
||||
case "-output":
|
||||
setArgsOutput(args[a + 1]);
|
||||
a++;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(CoreStrings.S_WrongParameter(args[a]));
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(ArgsConnectionString)) throw new ArgumentException(CoreStrings.S_DB_Parameter_Error_NoConnectionString);
|
||||
|
||||
RazorEngine.Engine.Razor = RazorEngineService.Create(new RazorEngine.Configuration.TemplateServiceConfiguration
|
||||
{
|
||||
EncodedStringFactory = new RazorEngine.Text.RawStringFactory() // Raw string encoding.
|
||||
});
|
||||
var razorId = Guid.NewGuid().ToString("N");
|
||||
RazorEngine.Engine.Razor.Compile(ArgsRazor, razorId);
|
||||
|
||||
var outputCounter = 0;
|
||||
using (IFreeSql fsql = new FreeSql.FreeSqlBuilder()
|
||||
.UseConnectionString(ArgsDbType, ArgsConnectionString)
|
||||
.UseAutoSyncStructure(false)
|
||||
.UseMonitorCommand(cmd => Console.WriteFormatted(cmd.CommandText + "\r\n", Color.SlateGray))
|
||||
.Build())
|
||||
{
|
||||
List<DbTableInfo> tables = new List<DbTableInfo>();
|
||||
if (string.IsNullOrEmpty(ArgsMatch) == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var matchTable = fsql.DbFirst.GetTableByName(ArgsMatch);
|
||||
if (matchTable != null) tables.Add(matchTable);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
if (tables.Any() == false)
|
||||
tables = fsql.DbFirst.GetTablesByDatabase();
|
||||
var outputTables = tables;
|
||||
|
||||
//开始生成操作
|
||||
foreach (var table in outputTables)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ArgsMatch) == false)
|
||||
{
|
||||
if (Regex.IsMatch($"{table.Schema}.{table.Name}".TrimStart('.'), ArgsMatch) == false) continue;
|
||||
}
|
||||
switch (table.Type)
|
||||
{
|
||||
case DatabaseModel.DbTableType.TABLE:
|
||||
if (ArgsFilter.Contains("Table", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteFormatted(" Ignore Table -> " + table.Name + "\r\n", Color.DarkSlateGray);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case DatabaseModel.DbTableType.VIEW:
|
||||
if (ArgsFilter.Contains("View", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteFormatted(" Ignore View -> " + table.Name + "\r\n", Color.DarkSlateGray);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case DatabaseModel.DbTableType.StoreProcedure:
|
||||
if (ArgsFilter.Contains("StoreProcedure", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteFormatted(" Ignore StoreProcedure -> " + table.Name + "\r\n", Color.DarkSlateGray);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
var sw = new StringWriter();
|
||||
var model = new RazorModel(fsql, ArgsNameSpace, ArgsNameOptions, tables, table);
|
||||
RazorEngine.Engine.Razor.Run(razorId, sw, null, model);
|
||||
|
||||
StringBuilder plus = new StringBuilder();
|
||||
//plus.AppendLine("//------------------------------------------------------------------------------");
|
||||
//plus.AppendLine("// <auto-generated>");
|
||||
//plus.AppendLine("// 此代码由工具 FreeSql.Generator 生成。");
|
||||
//plus.AppendLine("// 运行时版本:" + Environment.Version.ToString());
|
||||
//plus.AppendLine("// Website: https://github.com/2881099/FreeSql");
|
||||
//plus.AppendLine("// 对此文件的更改可能会导致不正确的行为,并且如果");
|
||||
//plus.AppendLine("// 重新生成代码,这些更改将会丢失。");
|
||||
//plus.AppendLine("// </auto-generated>");
|
||||
//plus.AppendLine("//------------------------------------------------------------------------------");
|
||||
plus.Append(sw.ToString());
|
||||
plus.AppendLine();
|
||||
|
||||
var outputFile = $"{ArgsOutput}{ArgsFileName.Replace("{name}", model.GetCsName(table.Name))}";
|
||||
File.WriteAllText(outputFile, plus.ToString(), Encoding.UTF8);
|
||||
switch (table.Type)
|
||||
{
|
||||
case DatabaseModel.DbTableType.TABLE:
|
||||
Console.WriteFormatted(" OUT Table -> " + outputFile + "\r\n", Color.DeepSkyBlue);
|
||||
break;
|
||||
case DatabaseModel.DbTableType.VIEW:
|
||||
Console.WriteFormatted(" OUT View -> " + outputFile + "\r\n", Color.DeepSkyBlue);
|
||||
break;
|
||||
case DatabaseModel.DbTableType.StoreProcedure:
|
||||
Console.WriteFormatted(" OUT StoreProcedure -> " + outputFile + "\r\n", Color.DeepSkyBlue);
|
||||
break;
|
||||
}
|
||||
++outputCounter;
|
||||
}
|
||||
}
|
||||
|
||||
var rebuildBat = ArgsOutput + "__重新生成.bat";
|
||||
if (File.Exists(rebuildBat) == false)
|
||||
{
|
||||
var razorCshtml = ArgsOutput + "__razor.cshtml.txt";
|
||||
if (File.Exists(razorCshtml) == false)
|
||||
{
|
||||
File.WriteAllText(razorCshtml, ArgsRazor, Encoding.UTF8);
|
||||
Console.WriteFormatted(" OUT -> " + razorCshtml + " (以后) 编辑它自定义模板生成\r\n", Color.Magenta);
|
||||
++outputCounter;
|
||||
}
|
||||
|
||||
File.WriteAllText(rebuildBat, $@"
|
||||
FreeSql.Generator -Razor ""__razor.cshtml.txt"" -NameOptions {string.Join(",", ArgsNameOptions.Select(a => a ? 1 : 0))} -NameSpace {ArgsNameSpace} -DB ""{ArgsDbType},{ArgsConnectionString}""{(string.IsNullOrEmpty(ArgsFilter) ? "" : $" -Filter \"{ArgsFilter}\"")}{(string.IsNullOrEmpty(ArgsMatch) ? "" : $" -Match \"{ArgsMatch}\"")} -FileName ""{ArgsFileName}""
|
||||
", Encoding.UTF8);
|
||||
Console.WriteFormatted(" OUT -> " + rebuildBat + " (以后) 双击它重新生成实体\r\n", Color.Magenta);
|
||||
++outputCounter;
|
||||
}
|
||||
|
||||
Console.WriteFormatted($"\r\n[{DateTime.Now.ToString("MM-dd HH:mm:ss")}] 生成完毕,总共生成了 {outputCounter} 个文件,目录:\"{ArgsOutput}\"\r\n", Color.DarkGreen);
|
||||
|
||||
if (ArgsReadKey)
|
||||
Console.ReadKey();
|
||||
wait.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
Extensions/FreeSql.Generator/FreeSql.Generator.csproj
Normal file
60
Extensions/FreeSql.Generator/FreeSql.Generator.csproj
Normal file
@ -0,0 +1,60 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net60</TargetFrameworks>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<Authors>FreeSql;ncc;YeXiangQin</Authors>
|
||||
<Company>2881099</Company>
|
||||
<Product>FreeSql</Product>
|
||||
<Description>使用 FreeSql 快速生成数据库的实体类,安装:dotnet tool install -g FreeSql.Generator</Description>
|
||||
<PackageProjectUrl>https://github.com/2881099/FreeSql</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/2881099/FreeSql</RepositoryUrl>
|
||||
<PackageTags>FreeSql DbFirst 实体生成器</PackageTags>
|
||||
<Version>3.2.833</Version>
|
||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../readme.md" Pack="true" PackagePath="\"/>
|
||||
<None Include="../../logo.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Colorful.Console" Version="1.2.9" />
|
||||
<PackageReference Include="RazorEngine.NetCore" Version="2.2.6" />
|
||||
<PackageReference Include="System.CodeDom" Version="6.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="DmProvider">
|
||||
<HintPath>..\..\Providers\FreeSql.Provider.Dameng\lib\DmProvider\netstandard2.0\DmProvider.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.OscarClient">
|
||||
<HintPath>..\..\Providers\FreeSql.Provider.ShenTong\lib\System.Data.OscarClient.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Kdbndp">
|
||||
<HintPath>..\..\Providers\FreeSql.Provider.KingbaseES\lib\Kdbndp.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Dameng\FreeSql.Provider.Dameng.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Firebird\FreeSql.Provider.Firebird.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.KingbaseES\FreeSql.Provider.KingbaseES.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.MySqlConnector\FreeSql.Provider.MySqlConnector.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Oracle\FreeSql.Provider.Oracle.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.PostgreSQL\FreeSql.Provider.PostgreSQL.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.ShenTong\FreeSql.Provider.ShenTong.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.Sqlite\FreeSql.Provider.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\Providers\FreeSql.Provider.SqlServer\FreeSql.Provider.SqlServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
22
Extensions/FreeSql.Generator/Program.cs
Normal file
22
Extensions/FreeSql.Generator/Program.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace FreeSql.Generator
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args != null && args.Length == 0) args = new[] { "?" };
|
||||
ManualResetEvent wait = new ManualResetEvent(false);
|
||||
new Thread(() => {
|
||||
Thread.CurrentThread.Join(TimeSpan.FromSeconds(1));
|
||||
ConsoleApp app = new ConsoleApp(args, wait);
|
||||
}).Start();
|
||||
wait.WaitOne();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
266
Extensions/FreeSql.Generator/RazorContentManager.cs
Normal file
266
Extensions/FreeSql.Generator/RazorContentManager.cs
Normal file
@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FreeSql.Generator
|
||||
{
|
||||
class RazorContentManager
|
||||
{
|
||||
public static string 实体类_特性_cshtml =
|
||||
#region 长内容
|
||||
@"using FreeSql.DatabaseModel;@{
|
||||
var gen = Model as RazorModel;
|
||||
|
||||
Func<string, string> GetAttributeString = attr => {
|
||||
if (string.IsNullOrEmpty(attr)) return """";
|
||||
return string.Concat("", "", attr.Trim('[', ']'));
|
||||
};
|
||||
Func<string, string> GetDefaultValue = defval => {
|
||||
if (string.IsNullOrEmpty(defval)) return """";
|
||||
return "" = "" + defval + "";"";
|
||||
};
|
||||
}@{
|
||||
switch (gen.fsql.Ado.DataType) {
|
||||
case FreeSql.DataType.PostgreSQL:
|
||||
@:using System;
|
||||
@:using System.Collections;
|
||||
@:using System.Collections.Generic;
|
||||
@:using System.Linq;
|
||||
@:using System.Reflection;
|
||||
@:using System.Threading.Tasks;
|
||||
@:using Newtonsoft.Json;
|
||||
@:using FreeSql.DataAnnotations;
|
||||
@:using System.Net;
|
||||
@:using Newtonsoft.Json.Linq;
|
||||
@:using System.Net.NetworkInformation;
|
||||
@:using NpgsqlTypes;
|
||||
@:using Npgsql.LegacyPostgis;
|
||||
break;
|
||||
case FreeSql.DataType.SqlServer:
|
||||
case FreeSql.DataType.MySql:
|
||||
default:
|
||||
@:using System;
|
||||
@:using System.Collections;
|
||||
@:using System.Collections.Generic;
|
||||
@:using System.Linq;
|
||||
@:using System.Reflection;
|
||||
@:using System.Threading.Tasks;
|
||||
@:using Newtonsoft.Json;
|
||||
@:using FreeSql.DataAnnotations;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
namespace @gen.NameSpace {
|
||||
|
||||
@if (string.IsNullOrEmpty(gen.table.Comment) == false) {
|
||||
@:/// <summary>
|
||||
@:/// @gen.table.Comment.Replace(""\r\n"", ""\n"").Replace(""\n"", ""\r\n /// "")
|
||||
@:/// </summary>
|
||||
}
|
||||
[JsonObject(MemberSerialization.OptIn)@GetAttributeString(gen.GetTableAttribute())]
|
||||
public partial class @gen.GetCsName(gen.FullTableName) {
|
||||
|
||||
@foreach (var col in gen.columns) {
|
||||
|
||||
if (string.IsNullOrEmpty(col.Comment) == false) {
|
||||
@:/// <summary>
|
||||
@:/// @col.Comment.Replace(""\r\n"", ""\n"").Replace(""\n"", ""\r\n /// "")
|
||||
@:/// </summary>
|
||||
}
|
||||
@:@(""[JsonProperty"" + GetAttributeString(gen.GetColumnAttribute(col, true)) + ""]"")
|
||||
@:public @gen.GetCsType(col) @gen.GetCsName(col.Name) { get; set; }@GetDefaultValue(gen.GetColumnDefaultValue(col, false))
|
||||
@:
|
||||
}
|
||||
}
|
||||
@gen.GetMySqlEnumSetDefine()
|
||||
}";
|
||||
#endregion
|
||||
|
||||
public static string 实体类_特性_导航属性_cshtml =
|
||||
#region 长内容
|
||||
@"@using FreeSql.DatabaseModel;@{
|
||||
|
||||
var isLazying = true; //延时加载
|
||||
var isOneToMany = true; //一对多,集合属性
|
||||
var isManyToMany = true; //多对多,集合属性
|
||||
|
||||
var gen = Model as RazorModel;
|
||||
var fks = gen.table.Foreigns;
|
||||
|
||||
Func<string, string> GetAttributeString = attr => {
|
||||
if (string.IsNullOrEmpty(attr)) return """";
|
||||
return string.Concat("", "", attr.Trim('[', ']'));
|
||||
};
|
||||
Func<string, string> GetDefaultValue = defval => {
|
||||
if (string.IsNullOrEmpty(defval)) return """";
|
||||
return "" = "" + defval + "";"";
|
||||
};
|
||||
|
||||
Func<DbForeignInfo, string> GetFkObjectName = fkx => {
|
||||
var eqfks = fks.Where(fk22a => fk22a.ReferencedTable.Name == fkx.ReferencedTable.Name);
|
||||
if (eqfks.Count() == 1) return fkx.ReferencedTable.Name;
|
||||
var fkretname = fkx.Columns[0].Name;
|
||||
if (fkretname.EndsWith(fkx.ReferencedColumns[0].Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedColumns[0].Name.Length).TrimEnd('_');
|
||||
if (fkretname.EndsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedTable.Name.Length).TrimEnd('_');
|
||||
if (fkretname.StartsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(fkx.ReferencedTable.Name.Length).TrimStart('_');
|
||||
return fkx.ReferencedTable.Name + (string.IsNullOrEmpty(fkretname) ? """" : (""_"" + fkretname));
|
||||
};
|
||||
Func<DbForeignInfo, string> GetFkObjectNameOutside = fkx => {
|
||||
var eqfks = fkx.Table.Foreigns.Where(fk22a => fk22a.ReferencedTable.Name == fkx.ReferencedTable.Name);
|
||||
if (eqfks.Count() == 1) return fkx.Table.Name;
|
||||
var fkretname = fkx.Columns[0].Name;
|
||||
if (fkretname.EndsWith(fkx.ReferencedColumns[0].Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedColumns[0].Name.Length).TrimEnd('_');
|
||||
if (fkretname.EndsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(0, fkretname.Length - fkx.ReferencedTable.Name.Length).TrimEnd('_');
|
||||
if (fkretname.StartsWith(fkx.ReferencedTable.Name, StringComparison.CurrentCultureIgnoreCase)) fkretname = fkretname.Substring(fkx.ReferencedTable.Name.Length).TrimStart('_');
|
||||
return fkx.Table.Name + (string.IsNullOrEmpty(fkretname) ? """" : (""_"" + fkretname));
|
||||
};
|
||||
}@{
|
||||
switch (gen.fsql.Ado.DataType) {
|
||||
case FreeSql.DataType.PostgreSQL:
|
||||
@:using System;
|
||||
@:using System.Collections.Generic;
|
||||
@:using Newtonsoft.Json;
|
||||
@:using FreeSql.DataAnnotations;
|
||||
@:using System.Net;
|
||||
@:using Newtonsoft.Json.Linq;
|
||||
@:using System.Net.NetworkInformation;
|
||||
@:using NpgsqlTypes;
|
||||
@:using Npgsql.LegacyPostgis;
|
||||
break;
|
||||
case FreeSql.DataType.SqlServer:
|
||||
case FreeSql.DataType.MySql:
|
||||
default:
|
||||
@:using System;
|
||||
@:using System.Collections.Generic;
|
||||
@:using Newtonsoft.Json;
|
||||
@:using FreeSql.DataAnnotations;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
namespace @gen.NameSpace {
|
||||
|
||||
@if (string.IsNullOrEmpty(gen.table.Comment) == false) {
|
||||
@:/// <summary>
|
||||
@:/// @gen.table.Comment.Replace(""\r\n"", ""\n"").Replace(""\n"", ""\r\n /// "")
|
||||
@:/// </summary>
|
||||
}
|
||||
[JsonObject(MemberSerialization.OptIn)@GetAttributeString(gen.GetTableAttribute())]
|
||||
public partial class @gen.GetCsName(gen.FullTableName) {
|
||||
|
||||
@foreach (var col in gen.columns) {
|
||||
|
||||
var findfks = fks.Where(fkaa => fkaa.Columns.Where(fkaac1 => fkaac1.Name == col.Name).Any());
|
||||
var csname = gen.GetCsName(col.Name);
|
||||
|
||||
if (string.IsNullOrEmpty(col.Comment) == false) {
|
||||
@:/// <summary>
|
||||
@:/// @col.Comment.Replace(""\r\n"", ""\n"").Replace(""\n"", ""\r\n /// "")
|
||||
@:/// </summary>
|
||||
}
|
||||
@:@(""[JsonProperty"" + GetAttributeString(gen.GetColumnAttribute(col, true)) + ""]"")
|
||||
if (findfks.Any() == false) {
|
||||
@:public @gen.GetCsType(col) @csname { get; set; }@GetDefaultValue(gen.GetColumnDefaultValue(col, false))
|
||||
} else {
|
||||
@:public @gen.GetCsType(col) @csname { get => _@csname; set {
|
||||
@:if (_@csname == value) return;
|
||||
@:_@csname = value;
|
||||
foreach (var fkcok2 in findfks) {
|
||||
@:@gen.GetCsName(GetFkObjectName(fkcok2)) = null;
|
||||
}
|
||||
@:} }
|
||||
@:private @gen.GetCsType(col) _@csname@GetDefaultValue(gen.GetColumnDefaultValue(col, false)).TrimEnd(';');
|
||||
}
|
||||
@:
|
||||
}
|
||||
@if (fks.Any()) {
|
||||
@:
|
||||
@:#region 外键 => 导航属性,ManyToOne/OneToOne
|
||||
foreach (var fk in fks) {
|
||||
var fkTableName = (fk.ReferencedTable.Schema + ""."" + fk.ReferencedTable.Name).Trim('.');
|
||||
if (fk.ReferencedTable.Schema == ""public"" || fk.ReferencedTable.Schema == ""dbo"")
|
||||
{
|
||||
fkTableName = fkTableName.Replace(fk.ReferencedTable.Schema + ""."", """");
|
||||
}
|
||||
@:
|
||||
@:[Navigate(""@string.Join("", "", fk.Columns.Select(a => gen.GetCsName(a.Name)))"")]
|
||||
@:public@(isLazying ? "" virtual"" : """") @gen.GetCsName(fkTableName) @gen.GetCsName(GetFkObjectName(fk)) { get; set; }
|
||||
}
|
||||
@:
|
||||
@:#endregion
|
||||
}
|
||||
@if (isOneToMany && gen.tables.Where(tmpft => tmpft.Foreigns.Where(tmpftfk => tmpftfk.ReferencedTable.Schema == gen.table.Schema && tmpftfk.ReferencedTable.Name == gen.table.Name && tmpftfk.Columns.Where(tmpcol => tmpcol.IsPrimary).Count() != tmpftfk.Columns.Count).Any()).Any()) {
|
||||
@:
|
||||
@:#region 外键 => 导航属性,OneToMany
|
||||
foreach (var ft in gen.tables) {
|
||||
var ftfks = ft.Foreigns.Where(ftfk => ftfk.ReferencedTable.Schema == gen.table.Schema && ftfk.ReferencedTable.Name == gen.table.Name && ftfk.Columns.Where(tmpcol => tmpcol.IsPrimary).Count() != ftfk.Columns.Count).ToArray();
|
||||
foreach (var fk in ftfks) {
|
||||
var fkTableName = (ft.Schema + ""."" + ft.Name).Trim('.');
|
||||
if (ft.Schema == ""public"" || ft.Schema == ""dbo"")
|
||||
{
|
||||
fkTableName = fkTableName.Replace(ft.Schema + ""."", """");
|
||||
}
|
||||
@:
|
||||
@:[Navigate(""@string.Join("", "", fk.Columns.Select(a => gen.GetCsName(a.Name)))"")]
|
||||
@:public@(isLazying ? "" virtual"" : """") List<@gen.GetCsName(fkTableName)> @gen.GetCsName(GetFkObjectNameOutside(fk))s { get; set; }
|
||||
}
|
||||
}
|
||||
@:
|
||||
@:#endregion
|
||||
}
|
||||
@if (isManyToMany) {
|
||||
var manyAny = false;
|
||||
foreach (var ft in gen.tables) {
|
||||
if (ft != gen.table) {
|
||||
var ftfks = ft.Foreigns.Where(ftfk => ftfk.Columns.Where(ftfkcol => ftfkcol.IsPrimary == false).Any() == false).ToArray();
|
||||
if (ftfks.Length == 2) {
|
||||
var fk1 = ftfks.Where(ftfk => (ftfk.ReferencedTable.Schema + ""."" + ftfk.ReferencedTable.Name).Trim('.') == gen.FullTableName).ToArray();
|
||||
if (fk1.Length == 1) {
|
||||
var fk2 = ftfks.Where(ftfk => fk1.Contains(ftfk) == false).ToArray();
|
||||
|
||||
var midft = ft;
|
||||
var leftft = gen.table;
|
||||
DbTableInfo rightft = null;
|
||||
if (fk2.Any()) {
|
||||
rightft = fk2[0].ReferencedTable;
|
||||
} else {
|
||||
rightft = fk1[1].ReferencedTable;
|
||||
}
|
||||
|
||||
var fkTableName = (rightft.Schema + ""."" + rightft.Name).Trim('.');
|
||||
if (rightft.Schema == ""public"" || rightft.Schema == ""dbo"")
|
||||
{
|
||||
fkTableName = fkTableName.Replace(rightft.Schema + ""."", """");
|
||||
}
|
||||
var middleTableName = (midft.Schema + ""."" + midft.Name).Trim('.');
|
||||
if (midft.Schema == ""public"" || midft.Schema == ""dbo"")
|
||||
{
|
||||
middleTableName = middleTableName.Replace(midft.Schema + ""."", """");
|
||||
}
|
||||
var csname = rightft.Name;
|
||||
if (manyAny == false)
|
||||
{
|
||||
manyAny = true;
|
||||
@:
|
||||
@:#region 外键 => 导航属性,ManyToMany
|
||||
}
|
||||
@:
|
||||
@:[Navigate(ManyToMany = typeof(@gen.GetCsName(middleTableName)))]
|
||||
@:public@(isLazying ? "" virtual"" : """") List<@gen.GetCsName(fkTableName)> @gen.GetCsName(csname)s { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (manyAny)
|
||||
{
|
||||
@:
|
||||
@:#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@gen.GetMySqlEnumSetDefine()
|
||||
}";
|
||||
#endregion
|
||||
}
|
||||
}
|
344
Extensions/FreeSql.Generator/RazorModel.cs
Normal file
344
Extensions/FreeSql.Generator/RazorModel.cs
Normal file
@ -0,0 +1,344 @@
|
||||
using FreeSql;
|
||||
using FreeSql.DataAnnotations;
|
||||
using FreeSql.DatabaseModel;
|
||||
using FreeSql.Internal.CommonProvider;
|
||||
using MySqlConnector;
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public class RazorModel
|
||||
{
|
||||
public RazorModel(IFreeSql fsql, string nameSpace, bool[] NameOptions, List<DbTableInfo> tables, DbTableInfo table)
|
||||
{
|
||||
this.fsql = fsql;
|
||||
this.NameSpace = nameSpace;
|
||||
this.NameOptions = NameOptions;
|
||||
this.tables = tables;
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public IFreeSql fsql { get; set; }
|
||||
public bool[] NameOptions { get; set; }
|
||||
public List<DbTableInfo> tables { get; set; }
|
||||
public DbTableInfo table { get; set; }
|
||||
public List<DbColumnInfo> columns => this.table.Columns;
|
||||
public string NameSpace { get; set; }
|
||||
public string FullTableName => $"{(new[] { "public", "dbo" }.Contains(table.Schema) ? "" : table.Schema)}.{table.Name}".TrimStart('.');
|
||||
|
||||
public string GetCsName(string name)
|
||||
{
|
||||
name = Regex.Replace(name.TrimStart('@', '.'), @"[^\w]", "_");
|
||||
name = char.IsLetter(name, 0) ? name : string.Concat("_", name);
|
||||
if (NameOptions[0]) name = UFString(name);
|
||||
if (NameOptions[1]) name = UFString(name.ToLower());
|
||||
if (NameOptions[2]) name = name.ToLower();
|
||||
if (NameOptions[3]) name = string.Join("", name.Split('_').Select(a => UFString(a)));
|
||||
return name;
|
||||
}
|
||||
public string UFString(string text)
|
||||
{
|
||||
text = Regex.Replace(text, @"[^\w]", "_");
|
||||
if (text.Length <= 1) return text.ToUpper();
|
||||
else return text.Substring(0, 1).ToUpper() + text.Substring(1, text.Length - 1);
|
||||
}
|
||||
public string LFString(string text)
|
||||
{
|
||||
text = Regex.Replace(text, @"[^\w]", "_");
|
||||
if (text.Length <= 1) return text.ToLower();
|
||||
else return text.Substring(0, 1).ToLower() + text.Substring(1, text.Length - 1);
|
||||
}
|
||||
private string LiteralString(string text)
|
||||
{
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
using (var provider = CodeDomProvider.CreateProvider("CSharp"))
|
||||
{
|
||||
provider.GenerateCodeFromExpression(new CodePrimitiveExpression(text), writer, null);
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
public string GetCsType(DbColumnInfo col)
|
||||
{
|
||||
if (fsql.Ado.DataType == FreeSql.DataType.MySql)
|
||||
if (col.DbType == (int)MySqlDbType.Enum || col.DbType == (int)MySqlDbType.Set)
|
||||
return $"{this.GetCsName(this.FullTableName)}{this.GetCsName(col.Name).ToUpper()}{(col.IsNullable ? "?" : "")}";
|
||||
return fsql.DbFirst.GetCsType(col);
|
||||
}
|
||||
|
||||
#region 特性
|
||||
public string GetTableAttribute()
|
||||
{
|
||||
var sb = new List<string>();
|
||||
|
||||
if (GetCsName(this.FullTableName) != this.FullTableName)
|
||||
{
|
||||
if (this.FullTableName.IndexOf('.') == -1)
|
||||
sb.Add("Name = \"" + this.FullTableName + "\"");
|
||||
else
|
||||
sb.Add("Name = \"" + this.FullTableName + "\""); //Todo: QuoteSqlName
|
||||
}
|
||||
|
||||
sb.Add("DisableSyncStructure = true");
|
||||
if (sb.Any() == false) return null;
|
||||
return "[Table(" + string.Join(", ", sb) + ")]";
|
||||
}
|
||||
public string GetColumnAttribute(DbColumnInfo col, bool isInsertValueSql = false)
|
||||
{
|
||||
var sb = new List<string>();
|
||||
|
||||
if (GetCsName(col.Name) != col.Name)
|
||||
sb.Add("Name = \"" + col.Name + "\"");
|
||||
|
||||
if (col.CsType != null)
|
||||
{
|
||||
var dbinfo = fsql.CodeFirst.GetDbInfo(col.CsType);
|
||||
if (dbinfo != null && string.Compare(dbinfo.dbtypeFull.Replace("NOT NULL", "").Trim(), col.DbTypeTextFull, true) != 0)
|
||||
{
|
||||
#region StringLength 反向
|
||||
switch (fsql.Ado.DataType)
|
||||
{
|
||||
case DataType.MySql:
|
||||
case DataType.OdbcMySql:
|
||||
switch (col.DbTypeTextFull.ToLower())
|
||||
{
|
||||
case "longtext": sb.Add("StringLength = -2"); break;
|
||||
case "text": sb.Add("StringLength = -1"); break;
|
||||
default:
|
||||
var m_stringLength = Regex.Match(col.DbTypeTextFull, @"^varchar\s*\((\w+)\)$", RegexOptions.IgnoreCase);
|
||||
if (m_stringLength.Success) sb.Add($"StringLength = {m_stringLength.Groups[1].Value}");
|
||||
else sb.Add("DbType = \"" + col.DbTypeTextFull + "\"");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DataType.SqlServer:
|
||||
case DataType.OdbcSqlServer:
|
||||
switch (col.DbTypeTextFull.ToLower())
|
||||
{
|
||||
case "nvarchar(max)": sb.Add("StringLength = -2"); break;
|
||||
default:
|
||||
var m_stringLength = Regex.Match(col.DbTypeTextFull, @"^nvarchar\s*\((\w+)\)$", RegexOptions.IgnoreCase);
|
||||
if (m_stringLength.Success) sb.Add($"StringLength = {m_stringLength.Groups[1].Value}");
|
||||
else sb.Add("DbType = \"" + col.DbTypeTextFull + "\"");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DataType.PostgreSQL:
|
||||
case DataType.OdbcPostgreSQL:
|
||||
case DataType.KingbaseES:
|
||||
case DataType.OdbcKingbaseES:
|
||||
case DataType.ShenTong:
|
||||
switch (col.DbTypeTextFull.ToLower())
|
||||
{
|
||||
case "text": sb.Add("StringLength = -2"); break;
|
||||
default:
|
||||
var m_stringLength = Regex.Match(col.DbTypeTextFull, @"^varchar\s*\((\w+)\)$", RegexOptions.IgnoreCase);
|
||||
if (m_stringLength.Success) sb.Add($"StringLength = {m_stringLength.Groups[1].Value}");
|
||||
else sb.Add("DbType = \"" + col.DbTypeTextFull + "\"");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DataType.Oracle:
|
||||
case DataType.OdbcOracle:
|
||||
switch (col.DbTypeTextFull.ToLower())
|
||||
{
|
||||
case "nclob": sb.Add("StringLength = -2"); break;
|
||||
default:
|
||||
var m_stringLength = Regex.Match(col.DbTypeTextFull, @"^nvarchar2\s*\((\w+)\)$", RegexOptions.IgnoreCase);
|
||||
if (m_stringLength.Success) sb.Add($"StringLength = {m_stringLength.Groups[1].Value}");
|
||||
else sb.Add("DbType = \"" + col.DbTypeTextFull + "\"");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DataType.Dameng:
|
||||
case DataType.OdbcDameng:
|
||||
switch (col.DbTypeTextFull.ToLower())
|
||||
{
|
||||
case "text": sb.Add("StringLength = -2"); break;
|
||||
default:
|
||||
var m_stringLength = Regex.Match(col.DbTypeTextFull, @"^nvarchar2\s*\((\w+)\)$", RegexOptions.IgnoreCase);
|
||||
if (m_stringLength.Success) sb.Add($"StringLength = {m_stringLength.Groups[1].Value}");
|
||||
else sb.Add("DbType = \"" + col.DbTypeTextFull + "\"");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DataType.Sqlite:
|
||||
switch (col.DbTypeTextFull.ToLower())
|
||||
{
|
||||
case "text": sb.Add("StringLength = -2"); break;
|
||||
default:
|
||||
var m_stringLength = Regex.Match(col.DbTypeTextFull, @"^nvarchar\s*\((\w+)\)$", RegexOptions.IgnoreCase);
|
||||
if (m_stringLength.Success) sb.Add($"StringLength = {m_stringLength.Groups[1].Value}");
|
||||
else sb.Add("DbType = \"" + col.DbTypeTextFull + "\"");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DataType.MsAccess:
|
||||
switch (col.DbTypeTextFull.ToLower())
|
||||
{
|
||||
case "longtext": sb.Add("StringLength = -2"); break;
|
||||
default:
|
||||
var m_stringLength = Regex.Match(col.DbTypeTextFull, @"^varchar\s*\((\w+)\)$", RegexOptions.IgnoreCase);
|
||||
if (m_stringLength.Success) sb.Add($"StringLength = {m_stringLength.Groups[1].Value}");
|
||||
else sb.Add("DbType = \"" + col.DbTypeTextFull + "\"");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
if (col.IsPrimary)
|
||||
sb.Add("IsPrimary = true");
|
||||
if (col.IsIdentity)
|
||||
sb.Add("IsIdentity = true");
|
||||
|
||||
if (dbinfo != null && dbinfo.isnullable != col.IsNullable)
|
||||
{
|
||||
var cstype = fsql.DbFirst.GetCsType(col);
|
||||
if (col.IsNullable && cstype.Contains("?") == false && col.CsType.IsValueType)
|
||||
sb.Add("IsNullable = true");
|
||||
if (col.IsNullable == false && (cstype.Contains("?") == true || cstype == "string"))
|
||||
sb.Add("IsNullable = false");
|
||||
}
|
||||
|
||||
if (isInsertValueSql)
|
||||
{
|
||||
var defval = GetColumnDefaultValue(col, false);
|
||||
if (defval == null) //c#默认属性值,就不需要设置 InsertValueSql 了
|
||||
{
|
||||
defval = GetColumnDefaultValue(col, true);
|
||||
if (defval != null)
|
||||
{
|
||||
sb.Add("InsertValueSql = \"" + defval.Replace("\"", "\\\"") + "\"");
|
||||
//sb.Add("CanInsert = false");
|
||||
}
|
||||
}
|
||||
//else
|
||||
//sb.Add("CanInsert = false");
|
||||
}
|
||||
}
|
||||
if (sb.Any() == false) return null;
|
||||
return "[Column(" + string.Join(", ", sb) + ")]";
|
||||
}
|
||||
public string GetColumnDefaultValue(DbColumnInfo col, bool isInsertValueSql)
|
||||
{
|
||||
var defval = col.DefaultValue?.Trim();
|
||||
if (string.IsNullOrEmpty(defval)) return null;
|
||||
var cstype = col.CsType.NullableTypeOrThis();
|
||||
if (fsql.Ado.DataType == DataType.SqlServer || fsql.Ado.DataType == DataType.OdbcSqlServer)
|
||||
{
|
||||
if (defval.StartsWith("((") && defval.EndsWith("))")) defval = defval.Substring(2, defval.Length - 4);
|
||||
else if (defval.StartsWith("('") && defval.EndsWith("')")) defval = defval.Substring(2, defval.Length - 4).Replace("''", "'");
|
||||
else if (defval.StartsWith("(") && defval.EndsWith(")")) defval = defval.Substring(1, defval.Length - 2);
|
||||
else return null;
|
||||
if (defval.StartsWith("N'") && defval.EndsWith("'")) defval = defval.Substring(1);
|
||||
if (cstype == typeof(Guid) && string.Compare(defval, "newid()", true) == 0) return $"Guid.NewGuid()";
|
||||
if (cstype == typeof(string) && string.Compare(defval, "newid()", true) == 0) return $"Guid.NewGuid().ToString().ToUpper()";
|
||||
if (defval == "NULL") return null;
|
||||
}
|
||||
if ((cstype == typeof(string) && defval.StartsWith("'") && defval.EndsWith("'::character varying") ||
|
||||
cstype == typeof(Guid) && defval.StartsWith("'") && defval.EndsWith("'::uuid")
|
||||
) && (fsql.Ado.DataType == DataType.PostgreSQL || fsql.Ado.DataType == DataType.OdbcPostgreSQL ||
|
||||
fsql.Ado.DataType == DataType.KingbaseES || fsql.Ado.DataType == DataType.OdbcKingbaseES ||
|
||||
fsql.Ado.DataType == DataType.ShenTong))
|
||||
{
|
||||
defval = defval.Substring(1, defval.LastIndexOf("'::") - 1).Replace("''", "'");
|
||||
}
|
||||
else if (defval.StartsWith("'") && defval.EndsWith("'"))
|
||||
{
|
||||
defval = defval.Substring(1, defval.Length - 2).Replace("''", "'");
|
||||
if (fsql.Ado.DataType == DataType.MySql || fsql.Ado.DataType == DataType.OdbcMySql) defval = defval.Replace("\\\\", "\\");
|
||||
}
|
||||
if (cstype.IsNumberType() && decimal.TryParse(defval, out var trydec))
|
||||
{
|
||||
if (isInsertValueSql) return defval;
|
||||
if (cstype == typeof(float)) return defval + "f";
|
||||
if (cstype == typeof(double)) return defval + "d";
|
||||
if (cstype == typeof(decimal)) return defval + "M";
|
||||
return defval;
|
||||
}
|
||||
if (cstype == typeof(Guid) && Guid.TryParse(defval, out var tryguid)) return isInsertValueSql ? (fsql.Select<TestTb>() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"Guid.Parse({LiteralString(defval)})";
|
||||
if (cstype == typeof(DateTime) && DateTime.TryParse(defval, out var trydt)) return isInsertValueSql ? (fsql.Select<TestTb>() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"DateTime.Parse({LiteralString(defval)})";
|
||||
if (cstype == typeof(TimeSpan) && TimeSpan.TryParse(defval, out var tryts)) return isInsertValueSql ? (fsql.Select<TestTb>() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"TimeSpan.Parse({LiteralString(defval)})";
|
||||
if (cstype == typeof(string)) return isInsertValueSql ? (fsql.Select<TestTb>() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : LiteralString(defval);
|
||||
if (cstype == typeof(bool)) return isInsertValueSql ? defval : (defval == "1" || defval == "t" ? "true" : "false");
|
||||
if (fsql.Ado.DataType == DataType.MySql || fsql.Ado.DataType == DataType.OdbcMySql)
|
||||
if (col.DbType == (int)MySqlDbType.Enum || col.DbType == (int)MySqlDbType.Set)
|
||||
if (isInsertValueSql) return (fsql.Select<TestTb>() as Select0Provider)._commonUtils.FormatSql("{0}", defval);
|
||||
return isInsertValueSql ? defval : null; //sql function or exp
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region mysql enum/set
|
||||
public string GetMySqlEnumSetDefine()
|
||||
{
|
||||
if (fsql.Ado.DataType != FreeSql.DataType.MySql && fsql.Ado.DataType != FreeSql.DataType.OdbcMySql) return null;
|
||||
var sb = new StringBuilder();
|
||||
foreach (var col in table.Columns)
|
||||
{
|
||||
if (col.DbType == (int)MySqlDbType.Enum || col.DbType == (int)MySqlDbType.Set)
|
||||
{
|
||||
if (col.DbType == (int)MySqlDbType.Set) sb.Append("\r\n\t[Flags]");
|
||||
sb.Append($"\r\n\tpublic enum {this.GetCsName(this.FullTableName)}{this.GetCsName(col.Name).ToUpper()}");
|
||||
if (col.DbType == (int)MySqlDbType.Set) sb.Append(" : long");
|
||||
sb.Append(" {\r\n\t\t");
|
||||
|
||||
string slkdgjlksdjg = "";
|
||||
int field_idx = 0;
|
||||
int unknow_idx = 0;
|
||||
string exp2 = string.Concat(col.DbTypeTextFull);
|
||||
int quote_pos = -1;
|
||||
while (true)
|
||||
{
|
||||
int first_pos = quote_pos = exp2.IndexOf('\'', quote_pos + 1);
|
||||
if (quote_pos == -1) break;
|
||||
while (true)
|
||||
{
|
||||
quote_pos = exp2.IndexOf('\'', quote_pos + 1);
|
||||
if (quote_pos == -1) break;
|
||||
int r_cout = 0;
|
||||
//for (int p = 1; true; p++) {
|
||||
// if (exp2[quote_pos - p] == '\\') r_cout++;
|
||||
// else break;
|
||||
//}
|
||||
while (exp2[++quote_pos] == '\'') r_cout++;
|
||||
if (r_cout % 2 == 0/* && quote_pos - first_pos > 2*/)
|
||||
{
|
||||
string str2 = exp2.Substring(first_pos + 1, quote_pos - first_pos - 2).Replace("''", "'");
|
||||
if (Regex.IsMatch(str2, @"^[\u0391-\uFFE5a-zA-Z_\$][\u0391-\uFFE5a-zA-Z_\$\d]*$"))
|
||||
slkdgjlksdjg += ", " + str2;
|
||||
else
|
||||
slkdgjlksdjg += string.Format(@",
|
||||
/// <summary>
|
||||
/// {0}
|
||||
/// </summary>
|
||||
[Description(""{0}"")]
|
||||
Unknow{1}", str2.Replace("\"", "\\\""), ++unknow_idx);
|
||||
if (col.DbType == (int)MySqlDbType.Set)
|
||||
slkdgjlksdjg += " = " + Math.Pow(2, field_idx++);
|
||||
if (col.DbType == (int)MySqlDbType.Enum && field_idx++ == 0)
|
||||
slkdgjlksdjg += " = 1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (quote_pos == -1) break;
|
||||
}
|
||||
sb.Append(slkdgjlksdjg.Substring(2).TrimStart('\r', '\n', '\t'));
|
||||
sb.Append("\r\n\t}");
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Table(DisableSyncStructure = true)]
|
||||
class TestTb { public Guid id { get; set; } }
|
||||
|
||||
|
Reference in New Issue
Block a user