diff --git a/Extensions/FreeSql.Extensions.BaseEntity/BaseEntity.cs b/Extensions/FreeSql.Extensions.BaseEntity/BaseEntity.cs index 40382f84..8986e5b5 100644 --- a/Extensions/FreeSql.Extensions.BaseEntity/BaseEntity.cs +++ b/Extensions/FreeSql.Extensions.BaseEntity/BaseEntity.cs @@ -1,17 +1,24 @@ -using FreeSql; +#if NET40 +using FreeSql.DataAnnotations; +using System; + +#else using FreeSql.DataAnnotations; using System; -using System.Data; -using System.Diagnostics; -using System.Linq.Expressions; -using System.Threading; using System.Threading.Tasks; +#endif + +// ReSharper disable once CheckNamespace namespace FreeSql { /// + /// Entity base class, including CreateTime/UpdateTime/IsDeleted, the CRUD methods, and ID primary key definition. + /// /// 包括 CreateTime/UpdateTime/IsDeleted、CRUD 方法、以及 ID 主键定义 的实体基类 /// + /// When TKey is int/long, the Id is set to be an auto-incremented primary key + /// /// 当 TKey 为 int/long 时,Id 主键被设为自增值主键 /// /// @@ -21,25 +28,26 @@ namespace FreeSql { static BaseEntity() { - var tkeyType = typeof(TKey)?.NullableTypeOrThis(); - if (tkeyType == typeof(int) || tkeyType == typeof(long)) - BaseEntity.ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true)); + var keyType = typeof(TKey).NullableTypeOrThis(); + if (keyType == typeof(int) || keyType == typeof(long)) + ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true)); } /// + /// Primary key
/// 主键 ///
[Column(Position = 1)] public virtual TKey Id { get; set; } -#if net40 -#else +#if !NET40 /// + /// Get data based on the value of the primary key
/// 根据主键值获取数据 ///
/// /// - async public static Task FindAsync(TKey id) + public static async Task FindAsync(TKey id) { var item = await Select.WhereDynamic(id).FirstAsync(); (item as BaseEntity)?.Attach(); @@ -48,6 +56,7 @@ namespace FreeSql #endif /// + /// Get data based on the value of the primary key
/// 根据主键值获取数据 ///
/// @@ -61,6 +70,8 @@ namespace FreeSql } /// + /// Entity base class, including CreateTime/UpdateTime/IsDeleted, and sync/async CRUD methods. + /// /// 包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步和同步方法的实体基类 /// /// @@ -69,88 +80,97 @@ namespace FreeSql { bool UpdateIsDeleted(bool value) { - if (this.Repository == null) + if (Repository is null) + { return Orm.Update(this as TEntity) - .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction()) - .Set(a => (a as BaseEntity).IsDeleted, this.IsDeleted = value).ExecuteAffrows() == 1; + .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction()) + .Set(a => (a as BaseEntity).IsDeleted, IsDeleted = value).ExecuteAffrows() == 1; + } - this.IsDeleted = value; - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return this.Repository.Update(this as TEntity) == 1; + IsDeleted = value; + Repository.UnitOfWork = _resolveUow?.Invoke(); + return Repository.Update(this as TEntity) == 1; } + /// + /// To delete data
/// 删除数据 ///
- /// 是否物理删除 + /// To flag whether to delete the physical level of the data /// public virtual bool Delete(bool physicalDelete = false) { - if (physicalDelete == false) return this.UpdateIsDeleted(true); - if (this.Repository == null) + if (physicalDelete == false) + return UpdateIsDeleted(true); + + if (Repository is null) return Orm.Delete(this as TEntity).ExecuteAffrows() == 1; - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return this.Repository.Delete(this as TEntity) == 1; + Repository.UnitOfWork = _resolveUow?.Invoke(); + return Repository.Delete(this as TEntity) == 1; } + /// + /// To recover deleted data
/// 恢复删除的数据 ///
/// - public virtual bool Restore() => this.UpdateIsDeleted(false); + public virtual bool Restore() => UpdateIsDeleted(false); /// + /// To update data
/// 更新数据 ///
/// public virtual bool Update() { - this.UpdateTime = DateTime.Now; - if (this.Repository == null) + UpdateTime = DateTime.Now; + if (Repository is null) + { return Orm.Update() - .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction()) - .SetSource(this as TEntity).ExecuteAffrows() == 1; + .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction()) + .SetSource(this as TEntity).ExecuteAffrows() == 1; + } - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return this.Repository.Update(this as TEntity) == 1; + Repository.UnitOfWork = _resolveUow?.Invoke(); + return Repository.Update(this as TEntity) == 1; } + /// + /// To insert data
/// 插入数据 ///
public virtual TEntity Insert() { - this.CreateTime = DateTime.Now; - if (this.Repository == null) - this.Repository = Orm.GetRepository(); - - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return this.Repository.Insert(this as TEntity); + CreateTime = DateTime.Now; + Repository ??= Orm.GetRepository(); + Repository.UnitOfWork = _resolveUow?.Invoke(); + return Repository.Insert(this as TEntity); } /// + /// To insert or update data
/// 更新或插入 ///
/// public virtual TEntity Save() { - this.UpdateTime = DateTime.Now; - if (this.Repository == null) - this.Repository = Orm.GetRepository(); - - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return this.Repository.InsertOrUpdate(this as TEntity); + UpdateTime = DateTime.Now; + Repository ??= Orm.GetRepository(); + Repository.UnitOfWork = _resolveUow?.Invoke(); + return Repository.InsertOrUpdate(this as TEntity); } /// + /// To completely save the navigation properties of the entity in the form of sub-tables.
/// 【完整】保存导航属性,子表 ///
- /// 导航属性名 + /// Navigation property name public virtual void SaveMany(string navigatePropertyName) { - if (this.Repository == null) - this.Repository = Orm.GetRepository(); - - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - this.Repository.SaveMany(this as TEntity, navigatePropertyName); + Repository ??= Orm.GetRepository(); + Repository.UnitOfWork = _resolveUow?.Invoke(); + Repository.SaveMany(this as TEntity, navigatePropertyName); } } } \ No newline at end of file diff --git a/Extensions/FreeSql.Extensions.BaseEntity/BaseEntityAsync.cs b/Extensions/FreeSql.Extensions.BaseEntity/BaseEntityAsync.cs index ca589470..58f1300e 100644 --- a/Extensions/FreeSql.Extensions.BaseEntity/BaseEntityAsync.cs +++ b/Extensions/FreeSql.Extensions.BaseEntity/BaseEntityAsync.cs @@ -1,14 +1,23 @@ - -using FreeSql; +#if NET40 +using FreeSql.DataAnnotations; + +#else using FreeSql.DataAnnotations; using System; using System.Threading.Tasks; +#endif + +// ReSharper disable once CheckNamespace namespace FreeSql { /// + /// Entity base class, including CreateTime/UpdateTime/IsDeleted, the async CRUD methods, and ID primary key definition. + /// /// 包括 CreateTime/UpdateTime/IsDeleted、CRUD 异步方法、以及 ID 主键定义 的实体基类 /// + /// When TKey is int/long, the Id is set to be an auto-incremented primary key + /// /// 当 TKey 为 int/long 时,Id 主键被设为自增值主键 /// /// @@ -18,128 +27,138 @@ namespace FreeSql { static BaseEntityAsync() { - var tkeyType = typeof(TKey)?.NullableTypeOrThis(); - if (tkeyType == typeof(int) || tkeyType == typeof(long)) - BaseEntity.ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true)); + var keyType = typeof(TKey).NullableTypeOrThis(); + if (keyType == typeof(int) || keyType == typeof(long)) + ConfigEntity(typeof(TEntity), t => t.Property("Id").IsIdentity(true)); } /// + /// Primary key
/// 主键 ///
- [Column(Position = 1)] + [Column(Position = 1)] public virtual TKey Id { get; set; } -#if net40 -#else +#if !NET40 /// + /// Get data based on the value of the primary key
/// 根据主键值获取数据 ///
/// /// - async public static Task FindAsync(TKey id) + public static async Task FindAsync(TKey id) { var item = await Select.WhereDynamic(id).FirstAsync(); (item as BaseEntity)?.Attach(); return item; } #endif - } /// + /// Entity base class, including CreateTime/UpdateTime/IsDeleted, and async CRUD methods. + /// /// 包括 CreateTime/UpdateTime/IsDeleted、以及 CRUD 异步方法的实体基类 /// /// [Table(DisableSyncStructure = true)] public abstract class BaseEntityAsync : BaseEntityReadOnly where TEntity : class { -#if net40 -#else +#if !NET40 async Task UpdateIsDeletedAsync(bool value) { - if (this.Repository == null) + if (Repository is null) + { return await Orm.Update(this as TEntity) - .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction()) - .Set(a => (a as BaseEntity).IsDeleted, this.IsDeleted = value).ExecuteAffrowsAsync() == 1; + .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction()) + .Set(a => (a as BaseEntity).IsDeleted, IsDeleted = value).ExecuteAffrowsAsync() == 1; + } - this.IsDeleted = value; - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return await this.Repository.UpdateAsync(this as TEntity) == 1; + IsDeleted = value; + Repository.UnitOfWork = _resolveUow?.Invoke(); + return await Repository.UpdateAsync(this as TEntity) == 1; } + /// + /// To delete data
/// 删除数据 ///
- /// 是否物理删除 + /// To flag whether to delete the physical level of the data /// - async public virtual Task DeleteAsync(bool physicalDelete = false) + public virtual async Task DeleteAsync(bool physicalDelete = false) { - if (physicalDelete == false) return await this.UpdateIsDeletedAsync(true); - if (this.Repository == null) + if (physicalDelete == false) + return await UpdateIsDeletedAsync(true); + + if (Repository is null) return await Orm.Delete(this as TEntity).ExecuteAffrowsAsync() == 1; - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return await this.Repository.DeleteAsync(this as TEntity) == 1; + Repository.UnitOfWork = _resolveUow?.Invoke(); + return await Repository.DeleteAsync(this as TEntity) == 1; } + /// + /// To recover deleted data
/// 恢复删除的数据 ///
/// - public virtual Task RestoreAsync() => this.UpdateIsDeletedAsync(false); + public virtual Task RestoreAsync() => UpdateIsDeletedAsync(false); /// + /// To update data
/// 更新数据 ///
/// - async public virtual Task UpdateAsync() + public virtual async Task UpdateAsync() { - this.UpdateTime = DateTime.Now; - if (this.Repository == null) + UpdateTime = DateTime.Now; + if (Repository is null) + { return await Orm.Update() - .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction()) - .SetSource(this as TEntity).ExecuteAffrowsAsync() == 1; + .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction()) + .SetSource(this as TEntity).ExecuteAffrowsAsync() == 1; + } - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return await this.Repository.UpdateAsync(this as TEntity) == 1; + Repository.UnitOfWork = _resolveUow?.Invoke(); + return await Repository.UpdateAsync(this as TEntity) == 1; } + /// + /// To insert data
/// 插入数据 ///
public virtual Task InsertAsync() { - this.CreateTime = DateTime.Now; - if (this.Repository == null) - this.Repository = Orm.GetRepository(); - - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return this.Repository.InsertAsync(this as TEntity); + CreateTime = DateTime.Now; + Repository ??= Orm.GetRepository(); + Repository.UnitOfWork = _resolveUow?.Invoke(); + return Repository.InsertAsync(this as TEntity); } /// + /// To insert or update data
/// 更新或插入 ///
/// public virtual Task SaveAsync() { - this.UpdateTime = DateTime.Now; - if (this.Repository == null) - this.Repository = Orm.GetRepository(); - - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return this.Repository.InsertOrUpdateAsync(this as TEntity); + UpdateTime = DateTime.Now; + Repository ??= Orm.GetRepository(); + Repository.UnitOfWork = _resolveUow?.Invoke(); + return Repository.InsertOrUpdateAsync(this as TEntity); } /// + /// To completely save the navigation properties of the entity in the form of sub-tables.
/// 【完整】保存导航属性,子表 ///
- /// 导航属性名 + /// Navigation property name public virtual Task SaveManyAsync(string navigatePropertyName) { - if (this.Repository == null) - this.Repository = Orm.GetRepository(); - - this.Repository.UnitOfWork = _resolveUow?.Invoke(); - return this.Repository.SaveManyAsync(this as TEntity, navigatePropertyName); + Repository ??= Orm.GetRepository(); + Repository.UnitOfWork = _resolveUow?.Invoke(); + return Repository.SaveManyAsync(this as TEntity, navigatePropertyName); } #endif } -} +} \ No newline at end of file diff --git a/Extensions/FreeSql.Extensions.BaseEntity/BaseEntityReadOnly.cs b/Extensions/FreeSql.Extensions.BaseEntity/BaseEntityReadOnly.cs index b3dd1c8a..8b10be63 100644 --- a/Extensions/FreeSql.Extensions.BaseEntity/BaseEntityReadOnly.cs +++ b/Extensions/FreeSql.Extensions.BaseEntity/BaseEntityReadOnly.cs @@ -1,44 +1,53 @@ - -using FreeSql.DataAnnotations; +using FreeSql.DataAnnotations; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Data.Common; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Threading; +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentlySynchronizedField namespace FreeSql { /// + /// Entity base class, including CreateTime/UpdateTime/IsDeleted. + /// /// 包括 CreateTime/UpdateTime/IsDeleted 的实体基类 /// [Table(DisableSyncStructure = true)] public abstract class BaseEntity { internal static IFreeSql _ormPriv; + + private const string ErrorMessageTemplate = @"使用前请初始化: +BaseEntity.Initialization(new FreeSqlBuilder() + .UseAutoSyncStructure(true) + .UseConnectionString(DataType.Sqlite, ""data source=test.db;max pool size=5"") + .Build());"; + /// - /// 全局 IFreeSql orm 对象 + /// Global IFreeSql ORM Object
+ /// 全局 IFreeSql ORM 对象 ///
- public static IFreeSql Orm => _ormPriv ?? throw new Exception(@"使用前请初始化 BaseEntity.Initialization(new FreeSqlBuilder() -.UseAutoSyncStructure(true) -.UseConnectionString(DataType.Sqlite, ""data source=test.db;max pool size=5"") -.Build());"); + public static IFreeSql Orm => _ormPriv ?? throw new Exception(ErrorMessageTemplate); + internal static Func _resolveUow; /// - /// 初始化BaseEntity - /// BaseEntity.Initialization(new FreeSqlBuilder() + /// To initialize the BaseEntity
+ /// 初始化 BaseEntity /// - /// .UseAutoSyncStructure(true) - /// - /// .UseConnectionString(DataType.Sqlite, "data source=test.db;max pool size=5") - /// - /// .Build()); + /// BaseEntity.Initialization(
+ /// new FreeSqlBuilder()
+ /// .UseAutoSyncStructure(true)
+ /// .UseConnectionString(DataType.Sqlite, "data source=test.db;max pool size=5")
+ /// .Build()); ///
- /// IFreeSql orm 对象 + /// IFreeSql ORM Object /// 工作单元(事务)委托,如果不使用事务请传 null解释:由于AsyncLocal平台兼容不好,所以交给外部管理 public static void Initialization(IFreeSql fsql, Func resolveUow) { @@ -52,21 +61,24 @@ namespace FreeSql _ormPriv.CodeFirst.ConfigEntity(cei.EntityType, cei.Fluent); } } + _resolveUow = resolveUow; } - + class ConfigEntityInfo { public Type EntityType; public Action Fluent; } - static ConcurrentQueue _configEntityQueues = new ConcurrentQueue(); - static object _configEntityLock = new object(); + + static readonly ConcurrentQueue _configEntityQueues = new(); + static readonly object _configEntityLock = new(); + internal static void ConfigEntity(Type entityType, Action fluent) { lock (_configEntityLock) { - if (_ormPriv == null) + if (_ormPriv is null) _configEntityQueues.Enqueue(new ConfigEntityInfo { EntityType = entityType, Fluent = fluent }); else _ormPriv.CodeFirst.ConfigEntity(entityType, fluent); @@ -74,31 +86,45 @@ namespace FreeSql } /// + /// Created time
/// 创建时间 ///
[Column(Position = -4)] public virtual DateTime CreateTime { get; set; } = DateTime.Now; + /// + /// Updated time
/// 更新时间 ///
[Column(Position = -3)] public virtual DateTime UpdateTime { get; set; } + /// + /// Logical Delete
/// 逻辑删除 ///
[Column(Position = -2)] public virtual bool IsDeleted { get; set; } + /// + /// Sort
/// 排序 ///
[Column(Position = -1)] public virtual int Sort { get; set; } } + /// + /// A readonly entity base class, including CreateTime/UpdateTime/IsDeleted. + /// + /// 包括 CreateTime/UpdateTime/IsDeleted 的只读实体基类 + /// + /// [Table(DisableSyncStructure = true)] public abstract class BaseEntityReadOnly : BaseEntity where TEntity : class { /// + /// To query data
/// 查询数据 ///
/// @@ -107,68 +133,92 @@ namespace FreeSql get { var select = Orm.Select() - .TrackToList(TrackToList) //自动为每个元素 Attach - .WithTransaction(_resolveUow?.Invoke()?.GetOrBeginTransaction(false)); + .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; - if (ls == null) + if (list is null) + return; + + if (list is not IList ls) { - var ie = list as IEnumerable; - if (ie == null) return; + if (list is not IEnumerable ie) + return; + var isFirst = true; - IBaseRepository berepo = null; + IBaseRepository baseRepo = null; + foreach (var item in ie) { - if (item == null) return; + if (item is null) + { + return; + } + if (isFirst) { isFirst = false; var itemType = item.GetType(); if (itemType == typeof(object)) return; - if (itemType.FullName.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType; + if (itemType.FullName!.Contains("FreeSqlLazyEntity__")) itemType = itemType.BaseType; if (Orm.CodeFirst.GetTableByEntity(itemType)?.Primarys.Any() != true) return; - if (item is BaseEntity == false) return; + if (item is not BaseEntity) return; } - var beitem = item as BaseEntity; - if (beitem != null) + + if (item is BaseEntity entity) { - if (berepo == null) berepo = Orm.GetRepository(); - beitem.Repository = berepo; - beitem.Attach(); + baseRepo ??= Orm.GetRepository(); + entity.Repository = baseRepo; + entity.Attach(); } } + return; } - if (ls.Any() == false) return; - if (ls.FirstOrDefault() is BaseEntity == false) return; - if (Orm.CodeFirst.GetTableByEntity(typeof(TEntity))?.Primarys.Any() != true) return; + + if (ls.Any() == false) + return; + + if (ls.FirstOrDefault() is not BaseEntity) + return; + + if (Orm.CodeFirst.GetTableByEntity(typeof(TEntity))?.Primarys.Any() != true) + return; + IBaseRepository repo = null; + foreach (var item in ls) { - var beitem = item as BaseEntity; - if (beitem != null) + if (item is BaseEntity entity) { - if (repo == null) repo = Orm.GetRepository(); - beitem.Repository = repo; - beitem.Attach(); + repo ??= Orm.GetRepository(); + entity.Repository = repo; + entity.Attach(); } } } /// - /// 查询条件,Where(a => a.Id > 10),支持导航对象查询,Where(a => a.Author.Email == "2881099@qq.com") + /// Query conditions
+ /// 查询条件,Where(a => a.Id> 10) + /// + /// Support navigation object query
+ /// 支持导航对象查询,Where(a => a.Author.Email == "2881099@qq.com") ///
/// lambda表达式 /// public static ISelect Where(Expression> exp) => Select.Where(exp); + /// - /// 查询条件,Where(true, a => a.Id > 10),支导航对象查询,Where(true, a => a.Author.Email == "2881099@qq.com") + /// Query conditions
+ /// 查询条件,Where(true, a => a.Id > 10) + /// + /// Support navigation object query
+ /// 支导航对象查询,Where(true, a => a.Author.Email == "2881099@qq.com") ///
/// true 时生效 /// lambda表达式 @@ -176,21 +226,21 @@ namespace FreeSql public static ISelect WhereIf(bool condition, Expression> exp) => Select.WhereIf(condition, exp); /// + /// Repository object.
/// 仓储对象 ///
protected IBaseRepository Repository { get; set; } /// - /// 附加实体,在更新数据时,只更新变化的部分 + /// To Attach entities. When updating data, only the changed part is updated.
+ /// 附加实体。在更新数据时,只更新变化的部分 ///
public TEntity Attach() { - if (this.Repository == null) - this.Repository = Orm.GetRepository(); - + Repository ??= Orm.GetRepository(); var item = this as TEntity; - this.Repository.Attach(item); + Repository.Attach(item); return item; } } -} +} \ No newline at end of file diff --git a/Extensions/FreeSql.Extensions.BaseEntity/FreeSql.Extensions.BaseEntity.csproj b/Extensions/FreeSql.Extensions.BaseEntity/FreeSql.Extensions.BaseEntity.csproj index bfd13274..8a9e2002 100644 --- a/Extensions/FreeSql.Extensions.BaseEntity/FreeSql.Extensions.BaseEntity.csproj +++ b/Extensions/FreeSql.Extensions.BaseEntity/FreeSql.Extensions.BaseEntity.csproj @@ -6,8 +6,8 @@ true FreeSql;ncc;YeXiangQin BaseEntity 是一种极简单的 CodeFirst 开发方式,特别对单表或多表CRUD,利用继承节省了每个实体类的重复属性(创建时间、ID等字段),软件删除等功能,进行 crud 操作时不必时常考虑仓储的使用. - https://github.com/2881099/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity - https://github.com/2881099/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity + https://github.com/dotnetcore/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity + https://github.com/dotnetcore/FreeSql/tree/master/Extensions/FreeSql.Extensions.BaseEntity git MIT FreeSql;ORM;BaseEntity @@ -19,6 +19,7 @@ true key.snk false + latest diff --git a/Extensions/FreeSql.Extensions.BaseEntity/README.MD b/Extensions/FreeSql.Extensions.BaseEntity/README.MD new file mode 100644 index 00000000..9f850945 --- /dev/null +++ b/Extensions/FreeSql.Extensions.BaseEntity/README.MD @@ -0,0 +1,129 @@ +[中文](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 + +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 +{ + 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 +{ + [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 +{ + 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 _asyncUow = new AsyncLocal(); + +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(); +} +``` diff --git a/Extensions/FreeSql.Extensions.BaseEntity/readme.md b/Extensions/FreeSql.Extensions.BaseEntity/README.zh-CN.MD similarity index 96% rename from Extensions/FreeSql.Extensions.BaseEntity/readme.md rename to Extensions/FreeSql.Extensions.BaseEntity/README.zh-CN.MD index bd1a0749..4314961b 100644 --- a/Extensions/FreeSql.Extensions.BaseEntity/readme.md +++ b/Extensions/FreeSql.Extensions.BaseEntity/README.zh-CN.MD @@ -1,4 +1,6 @@ -# 前言 +**中文** | [English](README.MD) + +# 前言 尝试过 ado.net、dapper、ef,以及Repository仓储,甚至自己还写过生成器工具,以便做常规CRUD操作。 @@ -47,7 +49,7 @@ public class UserGroup : BaseEntity public string GroupName { get; set; } } ``` -> 有关更多实体的特性配置,请参考资料:https://github.com/2881099/FreeSql/wiki/%e5%ae%9e%e4%bd%93%e7%89%b9%e6%80%a7 +> 有关更多实体的特性配置,请参考资料:https://github.com/dotnetcore/FreeSql/wiki/%e5%ae%9e%e4%bd%93%e7%89%b9%e6%80%a7 2、定义一个主键 Guid 的实体类型,保存数据时会自动产生有序不重复的 Guid 值(不用自己指定 Guid.NewGuid()); diff --git a/Extensions/FreeSql.Extensions.JsonMap/DataAnnotations/JsonMapAttribute.cs b/Extensions/FreeSql.Extensions.JsonMap/DataAnnotations/JsonMapAttribute.cs index 44f5d8a1..77878720 100644 --- a/Extensions/FreeSql.Extensions.JsonMap/DataAnnotations/JsonMapAttribute.cs +++ b/Extensions/FreeSql.Extensions.JsonMap/DataAnnotations/JsonMapAttribute.cs @@ -1,13 +1,11 @@ using System; -using System.Linq; +// ReSharper disable once CheckNamespace namespace FreeSql.DataAnnotations { - /// - /// 当实体类属性为【对象】时,以JSON形式映射存储 + /// When the entity class property is , map storage in JSON format.
+ /// 当实体类属性为【对象】时,以 JSON 形式映射存储 ///
- public class JsonMapAttribute : Attribute - { - } -} + public class JsonMapAttribute : Attribute { } +} \ No newline at end of file diff --git a/Extensions/FreeSql.Extensions.JsonMap/JsonMapCore.cs b/Extensions/FreeSql.Extensions.JsonMap/JsonMapCore.cs index 42c61462..60b0e0b1 100644 --- a/Extensions/FreeSql.Extensions.JsonMap/JsonMapCore.cs +++ b/Extensions/FreeSql.Extensions.JsonMap/JsonMapCore.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; using System.Threading; public static class FreeSqlJsonMapCoreExtensions @@ -19,11 +18,12 @@ public static class FreeSqlJsonMapCoreExtensions public static ColumnFluent JsonMap(this ColumnFluent col) { _dicJsonMapFluentApi.GetOrAdd(col._entityType, et => new ConcurrentDictionary()) - .GetOrAdd(col._property.Name, pn => true); + .GetOrAdd(col._property.Name, pn => true); return col; } /// + /// When the entity class property is and the attribute is marked as , map storage in JSON format.
/// 当实体类属性为【对象】时,并且标记特性 [JsonMap] 时,该属性将以JSON形式映射存储 ///
/// @@ -43,7 +43,7 @@ public static class FreeSqlJsonMapCoreExtensions }); } - that.Aop.ConfigEntityProperty += new EventHandler((s, e) => + 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) @@ -61,7 +61,6 @@ public static class FreeSqlJsonMapCoreExtensions }); } } - }); + }; } -} - +} \ No newline at end of file diff --git a/Extensions/FreeSql.Extensions.JsonMap/README.MD b/Extensions/FreeSql.Extensions.JsonMap/README.MD new file mode 100644 index 00000000..92e9f6fc --- /dev/null +++ b/Extensions/FreeSql.Extensions.JsonMap/README.MD @@ -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 +{ + [Column(IsPrimary = true)] + public string Name { get; set; } + + [JsonMap] + public T Config { get; set; } +} +``` diff --git a/Extensions/FreeSql.Extensions.JsonMap/readme.md b/Extensions/FreeSql.Extensions.JsonMap/README.zh-CN.MD similarity index 75% rename from Extensions/FreeSql.Extensions.JsonMap/readme.md rename to Extensions/FreeSql.Extensions.JsonMap/README.zh-CN.MD index 96906cd8..fa3f7c82 100644 --- a/Extensions/FreeSql.Extensions.JsonMap/readme.md +++ b/Extensions/FreeSql.Extensions.JsonMap/README.zh-CN.MD @@ -1,4 +1,6 @@ -FreeSql 扩展包,将值对象映射成 typeof(string),安装扩展包: +**中文** | [English](README.MD) + +FreeSql 扩展包,将值对象映射成 `typeof(string)`,安装扩展包: > dotnet add package FreeSql.Extensions.JsonMap @@ -20,4 +22,4 @@ public class S_SysConfig [JsonMap] public T Config { get; set; } } -``` \ No newline at end of file +``` diff --git a/Extensions/FreeSql.Generator/FreeSql.Generator.csproj b/Extensions/FreeSql.Generator/FreeSql.Generator.csproj index d63429eb..02579dbf 100644 --- a/Extensions/FreeSql.Generator/FreeSql.Generator.csproj +++ b/Extensions/FreeSql.Generator/FreeSql.Generator.csproj @@ -19,6 +19,7 @@ + diff --git a/Extensions/FreeSql.Generator/RazorModel.cs b/Extensions/FreeSql.Generator/RazorModel.cs index e21d2deb..7f181fd7 100644 --- a/Extensions/FreeSql.Generator/RazorModel.cs +++ b/Extensions/FreeSql.Generator/RazorModel.cs @@ -4,7 +4,10 @@ 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; @@ -50,7 +53,17 @@ public class RazorModel 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) @@ -250,10 +263,10 @@ public class RazorModel if (cstype == typeof(decimal)) return defval + "M"; return defval; } - if (cstype == typeof(Guid) && Guid.TryParse(defval, out var tryguid)) return isInsertValueSql ? (fsql.Select() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"Guid.Parse(\"{defval.Replace("\r\n", "\\r\\n").Replace("\"", "\\\"")}\")"; - if (cstype == typeof(DateTime) && DateTime.TryParse(defval, out var trydt)) return isInsertValueSql ? (fsql.Select() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"DateTime.Parse(\"{defval.Replace("\r\n", "\\r\\n").Replace("\"", "\\\"")}\")"; - if (cstype == typeof(TimeSpan) && TimeSpan.TryParse(defval, out var tryts)) return isInsertValueSql ? (fsql.Select() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"TimeSpan.Parse(\"{defval.Replace("\r\n", "\\r\\n").Replace("\"", "\\\"")}\")"; - if (cstype == typeof(string)) return isInsertValueSql ? (fsql.Select() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"\"{defval.Replace("\r\n", "\\r\\n").Replace("\"", "\\\"")}\""; + if (cstype == typeof(Guid) && Guid.TryParse(defval, out var tryguid)) return isInsertValueSql ? (fsql.Select() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"Guid.Parse({LiteralString(defval)})"; + if (cstype == typeof(DateTime) && DateTime.TryParse(defval, out var trydt)) return isInsertValueSql ? (fsql.Select() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"DateTime.Parse({LiteralString(defval)})"; + if (cstype == typeof(TimeSpan) && TimeSpan.TryParse(defval, out var tryts)) return isInsertValueSql ? (fsql.Select() as Select0Provider)._commonUtils.FormatSql("{0}", defval) : $"TimeSpan.Parse({LiteralString(defval)})"; + if (cstype == typeof(string)) return isInsertValueSql ? (fsql.Select() 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) diff --git a/FreeSql/FreeSqlBuilder.cs b/FreeSql/FreeSqlBuilder.cs index 2a15d347..344e2a2c 100644 --- a/FreeSql/FreeSqlBuilder.cs +++ b/FreeSql/FreeSqlBuilder.cs @@ -171,8 +171,12 @@ namespace FreeSql if (string.IsNullOrEmpty(_masterConnectionString) && _connectionFactory == null) throw new Exception("参数 masterConnectionString 不可为空,请检查 UseConnectionString"); IFreeSql ret = null; var type = _providerType; - if (type?.IsGenericType == true) type = type.MakeGenericType(typeof(TMark)); - if (type == null) + if (type != null) + { + if (type.IsGenericTypeDefinition) + type = type.MakeGenericType(typeof(TMark)); + } + else { Action throwNotFind = (dll, providerType) => throw new Exception($"缺少 FreeSql 数据库实现包:{dll},可前往 nuget 下载;如果存在 {dll} 依然报错(原因是环境问题导致反射不到类型),请在 UseConnectionString/UseConnectionFactory 第三个参数手工传入 typeof({providerType})"); switch (_dataType) diff --git a/Providers/FreeSql.Provider.MsAccess/MsAccessProvider.cs b/Providers/FreeSql.Provider.MsAccess/MsAccessProvider.cs index a7dd41e5..0ef9e5d8 100644 --- a/Providers/FreeSql.Provider.MsAccess/MsAccessProvider.cs +++ b/Providers/FreeSql.Provider.MsAccess/MsAccessProvider.cs @@ -15,7 +15,7 @@ namespace FreeSql.MsAccess public override IDelete CreateDeleteProvider(object dywhere) => new MsAccessDelete(this, this.InternalCommonUtils, this.InternalCommonExpression, dywhere); public override IInsertOrUpdate CreateInsertOrUpdateProvider() => throw new NotImplementedException(); - public override IDbFirst DbFirst => throw new NotImplementedException("FreeSql.Provider.Sqlite 未实现该功能"); + public override IDbFirst DbFirst => throw new NotImplementedException("FreeSql.Provider.MsAccess 未实现该功能"); public MsAccessProvider(string masterConnectionString, string[] slaveConnectionString, Func connectionFactory = null) { this.InternalCommonUtils = new MsAccessUtils(this); diff --git a/README.md b/README.md index 23acdc3a..b46082c1 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ FreeSql is a powerful O/RM component, supports .NET Core 2.1+, .NET Framework 4. [![Member project of .NET Core Community](https://img.shields.io/badge/member%20project%20of-NCC-9e20c9.svg)](https://github.com/dotnetcore) [![nuget](https://img.shields.io/nuget/v/FreeSql.svg?style=flat-square)](https://www.nuget.org/packages/FreeSql) [![stats](https://img.shields.io/nuget/dt/FreeSql.svg?style=flat-square)](https://www.nuget.org/stats/packages/FreeSql?groupby=Version) -[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/2881099/FreeSql/master/LICENSE.txt) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/dotnetcore/FreeSql/master/LICENSE.txt)

English | @@ -15,30 +15,30 @@ FreeSql is a powerful O/RM component, supports .NET Core 2.1+, .NET Framework 4. - 🛠 Support CodeFirst data migration. -- 💻 Support DbFirst import entity class from database, or use [Generation Tool](https://github.com/2881099/FreeSql/wiki/DbFirst). +- 💻 Support DbFirst import entity class from database, or use [Generation Tool](https://github.com/dotnetcore/FreeSql/wiki/DbFirst). - ⛳ Support advanced type mapping, such as PostgreSQL array type, etc. - 🌲 Support expression functions, and customizable analysis. - 🏁 Support one-to-many and many-to-many navigation properties, include and lazy loading. - 📃 Support Read/Write separation, Splitting Table/Database, Global filters, Optimistic and pessimistic locker. -- 🌳 Support MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/人大金仓/神舟通用/翰高/华为GaussDB/Access, etc. +- 🌳 Support MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/Dameng/人大金仓/Shentong Database/翰高/HUAWEI GaussDB/Access, etc. -QQ Groups:4336577(full)、**8578575(full)**、**52508226(available)** +QQ Groups:4336577(full)、8578575(full)、**52508226(available)** ## 📚 Documentation | | | - | -| [Get started](https://www.cnblogs.com/FreeSql/p/11531300.html)  \|  [Select](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2)  \|  [Update](https://github.com/2881099/FreeSql/wiki/%e4%bf%ae%e6%94%b9)  \|  [Insert](https://github.com/2881099/FreeSql/wiki/%e6%b7%bb%e5%8a%a0)  \|  [Delete](https://github.com/2881099/FreeSql/wiki/%e5%88%a0%e9%99%a4)  \|  [FAQ](https://github.com/dotnetcore/FreeSql/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)  | -| [Expression](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0)  \|  [CodeFirst](https://github.com/2881099/FreeSql/wiki/CodeFirst)  \|  [DbFirst](https://github.com/2881099/FreeSql/wiki/DbFirst)  \|  [Filters](https://github.com/2881099/FreeSql/wiki/%e8%bf%87%e6%bb%a4%e5%99%a8)  \|  [AOP](https://github.com/2881099/FreeSql/wiki/AOP)  | -| [Repository](https://github.com/2881099/FreeSql/wiki/Repository)  \|  [UnitOfWork](https://github.com/2881099/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83)  \|  [DbContext](https://github.com/2881099/FreeSql/wiki/DbContext)  \|  [ADO](https://github.com/2881099/FreeSql/wiki/ADO)  | -| [Read/Write](https://github.com/2881099/FreeSql/wiki/%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb)  \|  [Splitting Table](https://github.com/2881099/FreeSql/wiki/%e5%88%86%e8%a1%a8%e5%88%86%e5%ba%93)  \|  [Hide tech](https://github.com/2881099/FreeSql/wiki/%E9%AA%9A%E6%93%8D%E4%BD%9C)  \|  [*Update Notes*](https://github.com/2881099/FreeSql/wiki/%e6%9b%b4%e6%96%b0%e6%97%a5%e5%bf%97)  | +| [Get started](https://www.cnblogs.com/FreeSql/p/11531300.html)  \|  [Select](https://github.com/dotnetcore/FreeSql/wiki/Query-Data)  \|  [Update](https://github.com/dotnetcore/FreeSql/wiki/Update-Data)  \|  [Insert](https://github.com/dotnetcore/FreeSql/wiki/Insert-Data)  \|  [Delete](https://github.com/dotnetcore/FreeSql/wiki/Delete-Data)  \|  [FAQ](https://github.com/dotnetcore/FreeSql/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)  | +| [Expression](https://github.com/dotnetcore/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0)  \|  [CodeFirst](https://github.com/dotnetcore/FreeSql/wiki/CodeFirst)  \|  [DbFirst](https://github.com/2881099/FreeSql/wiki/DbFirst)  \|  [Filters](https://github.com/dotnetcore/FreeSql/wiki/%e8%bf%87%e6%bb%a4%e5%99%a8)  \|  [AOP](https://github.com/2881099/FreeSql/wiki/AOP)  | +| [Repository](https://github.com/dotnetcore/FreeSql/wiki/Repository)  \|  [UnitOfWork](https://github.com/dotnetcore/FreeSql/wiki/%e5%b7%a5%e4%bd%9c%e5%8d%95%e5%85%83)  \|  [DbContext](https://github.com/dotnetcore/FreeSql/wiki/DbContext)  \|  [ADO](https://github.com/2881099/FreeSql/wiki/ADO)  | +| [Read/Write](https://github.com/dotnetcore/FreeSql/wiki/%e8%af%bb%e5%86%99%e5%88%86%e7%a6%bb)  \|  [Splitting Table](https://github.com/dotnetcore/FreeSql/wiki/%e5%88%86%e8%a1%a8%e5%88%86%e5%ba%93)  \|  [Hide tech](https://github.com/dotnetcore/FreeSql/wiki/%E9%AA%9A%E6%93%8D%E4%BD%9C)  \|  [*Update Notes*](https://github.com/dotnetcore/FreeSql/wiki/%e6%9b%b4%e6%96%b0%e6%97%a5%e5%bf%97)  | > Please select a development mode: - Use FreeSql, keep the original usage. -- Use [FreeSql.Repository](https://github.com/2881099/FreeSql/wiki/Repository), Repository + UnitOfWork. -- Use [FreeSql.DbContext](https://github.com/2881099/FreeSql/wiki/DbContext), Like efcore. -- Use [FreeSql.BaseEntity](https://github.com/2881099/FreeSql/tree/master/Examples/base_entity), Simple mode. +- Use [FreeSql.Repository](https://github.com/dotnetcore/FreeSql/wiki/Repository), Repository + UnitOfWork. +- Use [FreeSql.DbContext](https://github.com/dotnetcore/FreeSql/wiki/DbContext), Like efcore. +- Use [FreeSql.BaseEntity](https://github.com/dotnetcore/FreeSql/tree/master/Examples/base_entity), Simple mode. > Some open source projects that use FreeSql: @@ -113,7 +113,7 @@ fsql.Select() .OrderByDescending(a => a.Id) .ToList() ``` -[More..](https://github.com/2881099/FreeSql/wiki/%e6%9f%a5%e8%af%a2) +[More..](https://github.com/dotnetcore/FreeSql/wiki/%e6%9f%a5%e8%af%a2) ```csharp fsql.Select().Where(a => new[] { 1, 2, 3 }.Contains(a.Id)).ToList(); @@ -122,7 +122,7 @@ fsql.Select().Where(a => a.CreateTime.Date == DateTime.Today).ToList(); fsql.Select().OrderBy(a => Guid.NewGuid()).Limit(10).ToList(); ``` -[More..](https://github.com/2881099/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0) +[More..](https://github.com/dotnetcore/FreeSql/wiki/%e8%a1%a8%e8%be%be%e5%bc%8f%e5%87%bd%e6%95%b0) ### 🚁 Repository @@ -175,7 +175,7 @@ Elapsed: 00:00:00.6707125; ToList Entity Counts: 131072; ORM: FreeSql* Elapsed: 00:00:00.6495301; Query Entity Counts: 131072; ORM: Dapper ``` -[More..](https://github.com/2881099/FreeSql/wiki/%e6%80%a7%e8%83%bd) +[More..](https://github.com/dotnetcore/FreeSql/wiki/%e6%80%a7%e8%83%bd) ## 👯 Contributors