兔子压倒窝边草 2019-07-01
FreeSql 目前版本号 0.5.5,预计明年元旦发布 1.0.0,切莫小看了版本号,目前单元测试方法1350+,并且每个方法内的涵盖面又比较广(不信的话见下图),每一次版本发布都作了较多的测试工作。
最近的一段时间,关注我们的人时不时会看见扩展包发布,今天振奋人心的功能主要是核心部分实现(扩展包今天当配角)。就不多啰嗦了,咱们直接入主题。
使用 codefirst 时序列化 json 或 jsonb 数据类型报错使用 postgresql + ef 配置数据映射关系,使用 FreeSql 的映射扩展时,对于json类型的数据映射出错。
这是来自 github 上的某个 issue,原因是用户的实体定义了 string,数据库类型为 json。FreeSql 本身支持了丰富的类型,不限于 json,但是限定了类型的映射,比如 JToken/JObject/JArray 的实体类型才可以映射至 PostgreSQL 数据库的 json 类型。另外虽然有 DbType 特性可以设置,但使用范围有限,不可跨越类型(如使用 string 可使用 DbType="char(100)")。
还有一些类似要求,如:将 enum 映射到数据库 varchar 的请求。。。。
到现在,我们已经彻底突破了这个障碍,基本可以做到随意映射类型。作为新项目开发,我们提供本身的默认类型映射已经非常人性化,提这些需求的人主要还是历史原因,咱们做程序维护工作的人员还是占比很高,千怪万怪只能怪 FreeSql 来得太迟。。。。
目前为 MapType 功能增加了大约 400 个单元测试方法。贴一段 demo 配置方法:
class EnumTestMap { public Guid id { get; set; } [Column(MapType = typeof(string))] public ToStringMapEnum enum_to_string { get; set; } [Column(MapType = typeof(string))] public ToStringMapEnum? enumnullable_to_string { get; set; } [Column(MapType = typeof(int))] public ToStringMapEnum enum_to_int { get; set; } [Column(MapType = typeof(int?))] public ToStringMapEnum? enumnullable_to_int { get; set; } [Column(MapType = typeof(string))] public BigInteger biginteger_to_string { get; set; } [Column(MapType = typeof(string))] public BigInteger? bigintegernullable_to_string { get; set; } } public enum ToStringMapEnum { 中国人, abc, 香港 }
应该不需要解释了吧?
细看一下,实体内有 BigInteger 类型,这可是数据库无法表示的类型,现在就是可以使用(没辙)。但请注意:BigInteger 仅仅是 CURD 方便, Equals == 判断可以使用,无法使用 + - * / < > 等操作;
csharp | MySql | SqlServer | PostgreSQL | Oracle | Sqlite |
---|---|---|---|---|---|
bool , bool? | bit(1) | bit | bool | number(1) | boolean |
sbyte , sbyte? | tinyint(3) | smallint | int2 | number(4) | smallint |
short , short? | smallint(6) | smallint | int2 | number(6) | smallint |
int , int? | int(11) | int | int4 | number(11) | integer |
long , long? | bigint(20) | bigint | int8 | number(21) | integer |
byte , byte? | tinyint(3) unsigned | tinyint | int2 | number(3) | int2 |
ushort , ushort? | smallint(5) unsigned | int | int4 | number(5) | unsigned |
uint , uint? | int(10) unsigned | bigint | int8 | number(10) | decimal(10,0) |
ulong , ulong? | bigint(20) unsigned | decimal(20,0) | numeric(20,0) | number(20) | decimal(21,0) |
double , double? | double | float | float8 | float(126) | double |
float , float? | float | real | float4 | float(63) | float |
decimal , decimal? | decimal(10,2) | decimal(10,2) | numeric(10,2) | number(10,2) | decimal(10,2) |
Guid , Guid? | char(36) | uniqueidentifier | uuid | char(36 CHAR) | character(36) |
TimeSpan , TimeSpan? | time | time | time | interval day(2) to second(6) | bigint |
DateTime , DateTime? | datetime | datetime | timestamp | timestamp(6) | datetime |
DateTimeOffset , DateTimeOffset? | - | - | datetimeoffset | timestamp(6) with local time zone | - |
Enum , Enum? | enum | int | int4 | number(16) | mediumint |
FlagsEnum , FlagsEnum? | set | bigint | int8 | number(32) | bigint |
byte[] | varbinary(255) | varbinary(255) | bytea | blob | blob |
string | varchar(255) | nvarchar(255) | varchar(255) | nvarchar2(255) | nvarchar(255) |
MygisPoint | point | - | - | - | - |
MygisLineString | linestring | - | - | - | - |
MygisPolygon | polygon | - | - | - | - |
MygisMultiPoint | multipoint | - | - | - | - |
MygisMultiLineString | multilinestring | - | - | - | - |
MygisMultiPolygon | multipolygon | - | - | - | - |
BitArray | - | - | varbit(64) | - | - |
NpgsqlPoint , NpgsqlPoint? | - | - | point | - | - |
NpgsqlLine , NpgsqlLine? | - | - | line | - | - |
NpgsqlLSeg , NpgsqlLSeg? | - | - | lseg | - | - |
NpgsqlBox , NpgsqlBox? | - | - | box | - | - |
NpgsqlPath , NpgsqlPath? | - | - | path | - | - |
NpgsqlPolygon , NpgsqlPolygon? | - | - | polygon | - | - |
NpgsqlCircle , NpgsqlCircle? | - | - | circle | - | - |
(IPAddress Address, int Subnet) , (IPAddress Address, int Subnet)? | - | - | cidr | - | - |
IPAddress | - | - | inet | - | - |
PhysicalAddress | - | - | macaddr | - | - |
NpgsqlRange<int> , NpgsqlRange<int>? | - | - | int4range | - | - |
NpgsqlRange<long> , NpgsqlRange<long>? | - | - | int8range | - | - |
NpgsqlRange<decimal> , NpgsqlRange<decimal>? | - | - | numrange | - | - |
NpgsqlRange<DateTime> , NpgsqlRange<DateTime>? | - | - | tsrange | - | - |
PostgisPoint | - | - | geometry | - | - |
PostgisLineString | - | - | geometry | - | - |
PostgisPolygon | - | - | geometry | - | - |
PostgisMultiPoint | - | - | geometry | - | - |
PostgisMultiLineString | - | - | geometry | - | - |
PostgisMultiPolygon | - | - | geometry | - | - |
PostgisGeometry | - | - | geometry | - | - |
PostgisGeometryCollection | - | - | geometry | - | - |
Dictionary<string, string> | - | - | hstore | - | - |
JToken | - | - | jsonb | - | - |
JObject | - | - | jsonb | - | - |
JArray | - | - | jsonb | - | - |
数组 | - | - | 以上所有类型都支持 | - | - |
以上类型和长度是默认值,可手工设置,如 string 属性可指定 [Column(DbType = "varchar(max)")]
class AddUniquesInfo { public Guid id { get; set; } [Column(Unique = "uk_phone")] public string phone { get; set; } [Column(Unique = "uk_group_index")] public string group { get; set; } [Column(Unique = "uk_group_index")] public int index { get; set; } [Column(Unique = "uk_group_index222")] public string index22 { get; set; } }
Unique 指定相同的标识,代表联合唯一键,现已支持迁移。
之前在操作实体时,必须传统泛型参数,现在可以实现弱类型实体的操作。以 Repository 为例:
var repos = fsql.GetGuidRepository<object>(); repos.AsType(typeof(AddUpdateInfo)); var item = new AddUpdateInfo(); repos.Insert(item); item.Clicks += 1; repos.InsertOrUpdate(item); var item2 = repos.Find(item.Id) as AddUpdateInfo; Assert.Equal(item.Clicks, item2.Clicks); repos.DataFilter.Apply("xxx", a => (a as AddUpdateInfo).Clicks == 11); Assert.Null(repos.Find(item.Id));
然后呢,DbContext 也支持同样的操作。
dotnet add package FreeSql.DbContext
现在支持 ToList(a => new Dto()) 这样的简单数据映射。
什么意思?即 Dto 只要有属性名与实体属性相同,就会根据匹配到的字段查询(不是查询所有字段回来再映射)。
然后这个骚操作,还支持多表查询的映射,怎么解决多表存在相同名字的字段问题呢?优先级规则,它会依次序匹配 LeftJoin/InnerJoin/RightJoin 的实体。
以前 .ToList() 会加载两级Join对象;
现在 ISelect.ToList(includeNestedMembers: true) 贪婪加载所有 LeftJoin/InnerJoin/RightJoin 导航数据,不论对象的层级;
支持传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1}。
也就是说 WhereDynaimc 方法输入类型为 object,是不是很方便?还支持联合主键呢。
var result = fsql.Ado.Query<T1, T2>("select from t1; select from t2");
大约是前一段时间的某一天(废话),因为使用 FreeSql 的某项目需要做一个简单的后台功能,以便录入或管理数据。在实施的过程中好怀念当初 dotnetGen 生成器的味道,用它产生 curd 基本功能几乎是秒做;
FreeSql.AdminLTE,是的就是它,前段时间发布过一次。
它是 FreeSql 衍生出来的 .NETCore MVC 中间件、中间件、中间件(重复三遍)扩展包,基于 AdminLTE 前端框架动态产生实体的增删查改界面;
输入:实体1、实体2、实体3
输出:后台管理的功能
只需要传入实体,就可以形成 curd 的管理功能,是不是有些骚啊~~~
先发一张运行后的图片尝个鲜:
这是根据实体产生 curd 界面的 mvc 中间件,开发时预览数据好方便啊。看完预览图不由得再感叹一次 FreeSql 的易用性,那句口号:做 .NETCore 最方便的 ORM! 没有说错。。。作者多次提及:“我们是日式简约风格,没那么复杂的用法”,也验证了这一点。。
中件间产生的界面包括添加、修改数据的功能,普通实体的根据属性的类型与 Html5 UI 一一映射;
比较特殊的映射规则:
c# 类型 | Html5 |
---|---|
布尔 | 复选框 |
枚举 | 下拉选择 |
日期 | 日期控件 |
ManyToOne 导航属性 | 下拉选择 |
ManyToMany 导航属性 | 多选器 |
等等。。。
什么情况会产生【上传文件】控件?
有兴趣的可以了解源码,目前没有开放在外部配置。
中件间为每个实体提供了分页列表查询,每页为20条数据;
除此外,还提供了过滤条件的支持,规则是根据导航属性(ManyToOne、ManyToMany)。比如【文章实体】,内含有【分类id】+【分类对象】,则【文章】列表页会出现按【分类】筛选的UI,详见上面的 demo 示意图,或者下载对应的 demo 版本运行;
中件间为每个实体提供了批量删除的功能;
我们习惯用 sqlite 做测试库,测试完毕直接删除目录,不留垃圾数据,所以下面的 demo 不需要修改任何地方,运行时自动建库、建表;
提供 .net core 2.1、2.2 两种环境的测试 demo 下载:
Demo for dotnet 2.1.zip、Demo for dotnet 2.2.zip
第一步:
dotnet restore
第二步:
dotnet run
一番惊喜过后,你应该会考虑实用性,这样做有什么价值,可用于什么样的场景?
这个扩展包简单的输入,产生巨量的功能反馈。目前来说它是死板的,对外提供的扩展性几乎为零,这样也就限定了它的应用场景。
1、它不可替代我们自身开发的后台管理系统;
2、它不适合摆放在公网正式环境,存在数据安全问题;
3、欢迎补充。。。;
目前的定位是这样的,在开发环境中使用,查阅预览实体数据,同时也比较方便的管理测试数据。
一段拥有无比力量的小段代码,也是中间件界面的功能开启:
//可以配置子目录访问,如:/testadmin/ app.UseFreeAdminLTE("/", typeof(Entities.Song), typeof(Entities.Tag));
我们一直在发布纯技术干货的分享文章,FreeSql 已经基本完成 .NETCore 最方便的 ORM 使命,我们正在筹备生态的建立,比如 ABP 中如何使用 FreeSql 的实现,需要各种各样的扩展包,好多好多工作量。有没有大神愿意无偿参与做这件事情,好吧。。应该没有人!!如果你回心转意了,欢迎联系我们。
欢迎持续关注我们,做 .NETCore 最方便的 ORM !
(QQ群:4336577)
github: https://github.com/2881099/FreeSql
说好的抽奖呢???