【BCVP升级】泛型主键的使用

(图片来源于SqlSugar官网,5年5.0)

大家假期已经结束了吧,还有80天左右就要到2021年了,你准备好了么?BCVP(Blog.Core&Vue Project)项目已经开源2年多,从来没有停更过,网上出现了很多仿品,当然这是好事儿,我从一开始也是这么鼓励大家的,第一要学习知识点,第二如果学会了自己动手搭一搭,这样不仅自己有了一定的深入理解,从全局上巩固,另外也可以对他人有一个借鉴和参考的不同版本,不过还是建议可以稍微稍稍的说一下,灵感/思路/学习受老张的帮助、影响和借鉴,想必你也明白,一边开源,一边讲解,一边建立社区回答问题,是一个常人无非想象的毅力。最近打算成立一个基于BCVP的开发者社区,感兴趣的可以留言,一起来个Business版本,两三个人即可,是那种真的想设计的,看缘分吧。

今天继续推进BCVP项目的往下进行,新开了一个需求,这个需求来自于网友的提问:目前BlogCore项目默认使用的是int作为主键,并自增,平时开发的时候int或者long这个都是很常见的,但是如果说,我就不想用int,习惯了Guid,当然也为了更方便迁移数据,因为int会乱序,特别是在多库的时候。那这个时候如果我想把int主键,改成guid,工作量是多大,需要改多少地方,怎么处理逻辑,前端修改哪些地方,等等等等。

所以我就尝试了这个新课题:使用泛型主键,这样拿到这个项目的时候,自己修改下主键类型,就可以运行了,不过目前还没有百分百完善,int主键已经调通,其他类型主键,比如Guid或者自定义string还没有完成生产化,但是放心,我肯定会完善的,最终的目的是下载项目后,可以满足自定义配置。

做这个需求的目的,一是为了灵活框架,二也是为了给大家提供一个思路。

别一上来就说没用,你可以不用我的框架,但是这个思路还是可以了解下的,平时ORM中是如何控制的,而且泛型在项目开发中的作用特别大。好啦,下边就先简单说一下思路吧,当然离不开SqlSugar的强大支持。

1、自定义特性

配置服务SqlsugarSetup

既然要实现泛型主键,那我们就需要对主键进行处理,因为只有int类型的主键才需要自增,其他类型的是不需要的,当然如果在非int类型的主键上配置自增了也是会报错的。

在SqlsugarSetup.cs服务类中,配置自定义特性

 listConfig.Add(new ConnectionConfig(){ConfigId = m.ConnId.ObjToString().ToLower(),ConnectionString = m.Connection,DbType = (DbType)m.DbType,IsAutoCloseConnection = true,IsShardSameThread = true,AopEvents = new AopEvents{OnLogExecuting = (sql, p) =>{if (Appsettings.app(new string[] { "AppSettings", "SqlAOP", "Enabled" }).ObjToBool()){Parallel.For(0, 1, e =>{MiniProfiler.Current.CustomTiming("SQL:", GetParas(p) + "【SQL语句】:" + sql);LogLock.OutSql2Log("SqlLog", new string[] { GetParas(p), "【SQL语句】:" + sql });});}}},MoreSettings = new ConnMoreSettings(){//IsWithNoLockQuery = true,IsAutoRemoveDataCache = true},// 从库SlaveConnectionConfigs = listConfig_Slave,// 自定义特性ConfigureExternalServices = new ConfigureExternalServices(){EntityService = (property, column) =>{if (column.IsPrimarykey && property.PropertyType == typeof(int)){column.IsIdentity = true;}}},InitKeyType = InitKeyType.Attribute}

核心的就是ConfigureExternalServices这个方法,如果是主键,并且是int,才会增加它的自增属性,否则不处理。

■ ■■■■

修改实体基类RootEntityTkey

这里我重写了一个基于泛型主键的实体基类RootEntityTkey,因为有了上边的配置,所以就不需要在主键上增加自增了,只需要配置一个属性:是否为主键即可,因为肯定不为空,另一个参数IsNullable可以不写:

现在配置好了自定义特性,那就开始今天的重头戏了——设计泛型。

■ ■■■■

2、设计泛型主键结构

实体基类增加泛型参数

上边我们已经重新设计了一个实体基类,在它的基础上,我们可以先增加一个泛型参数:

 public class RootEntityTkey<Tkey> where Tkey : IEquatable<Tkey>{/// <summary>/// ID/// 泛型主键Tkey/// </summary>[SugarColumn(IsNullable = false, IsPrimaryKey = true)]public Tkey Id { get; set; }}

这都是很简单的,就是新增了一个参数Tkey,我就不多说了,只要是用过泛型的肯定一眼就能明白,如果看不明白,可以学习下基础知识了。

这里有一个小疑问,你可能会说,那我int类型有一个数字自增,但是如果其他类型的时候,如何配置默认值呢,别担心Sqlsugar已经提供了Guid的默认值,你可以查看源码,是这么设计的:

这样的话,我们的实体类的如果是Guid,就算是一个空的对象实例,存入的时候也会有值,具体的写法我下文会举例说明的。

定义好了基类,那我们就需要动手数据库实体类了,可能稍微复杂一点,因为会涉及另一个重要的概念。

■ ■■■■

普通实体模型继承基类,并传递参数

刚刚已经定义好了泛型基类,那现在我们来设计下实体类,这里有两个情况,一种是普通的类结构,比如角色表自己不和其他交互,只有主键Id,另一种是有外键的复杂的类结构,比如用户角色表中,有Uid和Rid。咱们先说下普通类型的。

/// <summary>
/// 角色表
/// </summary>
public class Role : RootEntityTkey<int>
{// 因为继承了RootEntityTkey,所以就不用写主键Id了/// <summary>///获取或设置是否禁用,逻辑上的删除,非物理删除/// </summary>[SugarColumn(IsNullable = true)]public bool? IsDeleted { get; set; }/// <summary>/// 角色名/// </summary>[SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true)]public string Name { get; set; }// 等等其他的字段...}

这里用角色表Role举例,直接继承父类RootEntityTkey<int>,然后定义该实体除主键以外的属性和字段等即可,还是很简单的,也是很普通的写法。

■ ■■■■

复杂的实体模型

上边写了简单的方案,但是平时开发肯定不会是这样的,不免会出现有关系的情况,也就是外键的问题,比如用户角色关系表UserRole,它里边除了主键Id以外,肯定也会包含Uid和Rid,那如何设计呢,如果单纯的继承RootEntityTkey肯定是不行的,因为如果这么操作了,这个关系表中肯定就不能和User表或者Role表保持一致了,所以这三个字段都应该设计成泛型的格式,那如何设计的?

我参照着实体泛型基类,又单独针对特定的有外键需求的实体,抽离了一个中间父类,请注意我的命名:实体类-->父类(非必须)-->泛型基类,用UserRole来举例。

1、还是先定义UserRole的实体类内容

/// <summary>
/// 用户跟角色关联表
/// 基础表
/// </summary>
public class UserRole : UserRoleRoot<int>
{/// <summary>///获取或设置是否禁用,逻辑上的删除,非物理删除/// </summary>[SugarColumn(IsNullable = true)]public bool? IsDeleted { get; set; }/// <summary>/// 创建ID/// </summary>[SugarColumn(IsNullable = true)]public int? CreateId { get; set; }// 其他属性字段
}

2、然后抽离父类,对外键和Pid等单纯处理

/// <summary>
/// 用户跟角色关联表
/// 父类
/// </summary>
public class UserRoleRoot<Tkey> where Tkey : IEquatable<Tkey>
{/// <summary>/// 用户ID/// </summary>public Tkey UserId { get; set; }/// <summary>/// 角色ID/// </summary>public Tkey RoleId { get; set; }
}

3、最后将抽离的父类来继承泛型基类

/// <summary>
/// 用户跟角色关联表
/// 父类
/// </summary>
public class UserRoleRoot<Tkey> : RootEntityTkey<Tkey> where Tkey : IEquatable<Tkey>
{/// <summary>/// 用户ID/// </summary>public Tkey UserId { get; set; }/// <summary>/// 角色ID/// </summary>public Tkey RoleId { get; set; }
}

这样不仅可以满足这种外键的问题,也可以来处理一些特殊的情况,比如Pid,你想一下,主键如果都泛型了,总不能Pid父id这种还是int吧,这里用接口表的抽离父类举例:

/// <summary>
/// 接口API地址信息表 
/// 父类
/// </summary>
public class ModulesRoot<Tkey> : RootEntityTkey<Tkey> where Tkey : IEquatable<Tkey>
{/// <summary>/// 父ID/// </summary>[SugarColumn(IsNullable = true)]public Tkey ParentId { get; set; }}

BlogCore项目我已经修改完成,最终的结果是这样的:

核心的就是RootTkey这个文件夹下,就是这次修改的主要部分,其他的实体模型基本不用修改,只需要继承特定的专属父类/基类即可: RootEntityTkey<Tkey>。

■ ■■■■

3、其他重要提醒

不要把抽离的父类生成到数据库

在BlogCore项目中,我用的是自动CodeFirst并可以生成种子数据,当生成表结构的时候,我是根据命名空间来处理的,你在设计抽离的父类,比如UserRoleRoot<Tkey>的时候,注意修改命名空间,别生成到了数据库里,当然肯定也生成不进去,会报错的,这里只是提个醒,因为是CodeFirst的逻辑是根据命名空间:

// 创建数据库表,遍历指定命名空间下的class,
// 注意不要把其他命名空间下的也添加进来。
Console.WriteLine("Create Tables...");
var modelTypes = from t in Assembly.GetExecutingAssembly().GetTypes()where t.IsClass && t.Namespace == "Blog.Core.Model.Models"select t;
modelTypes.ToList().ForEach(t =>
{if (!myContext.Db.DbMaintenance.IsAnyTable(t.Name)){Console.WriteLine(t.Name);myContext.Db.CodeFirst.InitTables(t);}
});

当然,你也可以自己优化下,比如来个特性,或者继承一个接口啥的来限制只有实体模型才可以生成到数据库等等,看你的需要了。

■ ■■■■

生成种子数据的时候,反序列化要注意

我也同时优化了种子数据json的反序列化,比如整型用的是0,不是"0",这样的问题。

然后反序列化的方法也改用Newtonsoft.Json组件了,之前我之前自己写的,在反序列化的时候有不识别null的问题,所以需要配置一个setting来处理掉null,具体的代码,可以查看DBSeed.cs 这个文件。这里举一个例子:

 JsonSerializerSettings setting = new JsonSerializerSettings();JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>{//日期类型默认格式化处理setting.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";//空值处理setting.NullValueHandling = NullValueHandling.Ignore;//setting.Converters.Add(new BoolConvert("是,否"));return setting;});if (!await myContext.Db.Queryable<TopicDetail>().AnyAsync())
{var data = JsonConvert.DeserializeObject<List<TopicDetail>>(FileHelper.ReadFile(string.Format(SeedDataFolder, "TopicDetail"), Encoding.UTF8), setting);myContext.GetEntityDB<TopicDetail>().InsertRange(data);Console.WriteLine("Table:TopicDetail created success!");
}
else
{Console.WriteLine("Table:TopicDetail already exists...");
}

■ ■■■■

项目如何初始化自定义主键类型

现在我的项目中,已经完全配置好了int类型的模式了,如果你想使用Guid的话,应该如何操作呢,很简单,只需要直接修改下泛型参数就行,这里用Advertisement举例子说明下:

1、修改泛型参数为Guid:

public class Advertisement : RootEntityTkey<Guid>
{// 属性字段等...
}

2、执行Add操作

var ad = await _advertisementServices.Add(new Advertisement());

3、注意仓储执行方法

因为之前我们都是使用的int作为主键,然后用的.ExecuteReturnIdentityAsync()方法,这样返回的是对应的id。

但是现在用了Guid以后,就不能这么用了,因为这样使用的话,这个方法是无效的.ExecuteReturnIdentityAsync(),不仅不会正常的返回id值,也无非自动生成Guid的默认值,你可以使用.ExecuteCommandAsync(),当然可以直接使用.ExecuteReturnEntityAsync()这个方法,来返回实体,然后从实体里,获取对应的Id,这样的话,不论是int还是Guid,都能返回出来了。

4、查看效果

设置了Guid以后,就可以看看效果了,上边的0000-000-0000-000这样的值,就是因为使用的.ExecuteReturnIdentityAsync(),下边的是正常的使用Command方法,或者直接ReturnEntity的方法。

总体来说还是很方便的。

好啦,今天的分享暂时就是这样的,希望能提供一个思路,无论是对BCVP项目,还是你自己的项目。

  end 

BCVP开发者社区推荐

欢迎你来

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307368.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode404. 左叶子之和(迭代和递归)

一:题目 二:上码 迭代 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right…

Dotnet Core使用特定的SDKRuntime版本

Dotnet Core的SDK版本总在升级&#xff0c;怎么使用一个特定的版本呢&#xff1f;假期过完了&#xff0c;心情还在。今天写个短的。一、前言写这个是因为昨天刷微软官方文档&#xff0c;发现global.json在 SDK 3.0 后&#xff0c;更新了一些内容。文档提到了这个更新&#xff0…

Spring中IOC的理解(通俗易懂版)

文章目录1.IOC提出背景2:IOC的核心概念3:IOC的实现方式4:IOC的入门案例(1):思路分析(2):代码解析5:DI入门案例(1):思路分析(2):代码解析6:DI依赖注入的方式(1):前言(2):Set方式注入(3):构造器注入(4):依赖的自动装配7:注解开发模式的依赖注入(1):前言介绍(2):注解模式的依赖注入…

首个使用Blazor 技术实现的社区软件 BlazorCommunity 发布

BlazorCommunity 是首个使用Blazor 实现的开源社区软件&#xff0c; 其组件基于Element-Blazor &#xff0c; Element-Blazor 是一个 API 模仿 Element&#xff0c;CSS 直接使用 Element 样式&#xff0c;HTML 结构直接使用 Element HTML 结构 的 Web开发库。由于基于了…

leetcode112. 路径总和

一:题目 二:上码 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

全球顶级开源大神们现身 COSCon'20

点击上方“开源社”关注我们| 作者&#xff1a;Will Wang| 编辑&#xff1a;沈于蓝| 责编&#xff1a;王皓月业界最具影响力的开源年度盛会2020中国开源年会 (COSCon20) 将于 10月24-25日由开源社举办。COSCon 以其独特定位及日益增加的影响力&#xff0c;吸引越来越多的顶级企…

做.NET开发多年,公司要我转Java...

10月13日&#xff0c;.NET5发布了(Release Candidate)RC2版本&#xff0c;包含语言新版本C#9和F#5等&#xff0c;这是正式版前的最后更新&#xff01;终于&#xff0c;万众期待的.NET5真的要来了&#xff01;公司让我转Java&#xff0c;我成功说服老板&#xff01;机会永远留给…

ASP.NET Core Blazor WebAssembly 之 .NET JavaScript互调

Blazor WebAssembly可以在浏览器上跑C#代码&#xff0c;但是很多时候显然还是需要跟JavaScript打交道。比如操作dom&#xff0c;当然跟angular、vue一样不提倡直接操作dom&#xff1b;比如浏览器的后退导航。反之JavaScript也有可能需要调用C#代码来实现一些功能&#xff0c;毕…

leetcode654. 最大二叉树

一:上码 二:上码 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

浅议C#客户端和服务端通信的几种方法:Rest和GRPC和其他

本文来自&#xff1a;https://michaelscodingspot.com/rest-vs-grpc-for-asp-net/浅议C#客户端和服务端通信的几种方法&#xff1a;Rest和GRPC在C&#xff03;客户端和C&#xff03;服务器之间进行通信的方法有很多。一些功能强大&#xff0c;而其他功能则不是很多。有些非常快…

leetcode106. 从中序与后序遍历序列构造二叉树(java详解版)

一:题目 二:上码 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

COSCon’20开源教育论坛介绍

点击上方“开源社”关注我们| 作者&#xff1a;周添一| 编辑&#xff1a;沈于蓝| 设计&#xff1a;冯艺怡| 责编&#xff1a;王皓月论坛简介在当今数据驱动的智能社会&#xff0c;软件作为数字智能社会的数据基础设施&#xff0c;承载了社会的高效运转。在这个软件定义世界的潮…

leetcode105. 从前序与中序遍历序列构造二叉树

一:题目 二:上码 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

想进BAT等大厂,到底要怎么做?

职场&认知洞察 丨 作者 / findyi这是findyi公众号的第82篇原创文章最近我的公众号多了很多学生读者&#xff0c;很多人问我&#xff1a;如果要进BAT等大厂&#xff0c;我是应该直接工作还是考研&#xff1f;对于未来要从事程序员行业的朋友来说&#xff0c;要不要考研&…

.NET 5.0 RC 2 发布,正式版将在 11 月 .NET Conf 大会上发布

原文&#xff1a;http://dwz.win/ThX作者&#xff1a;Richard翻译&#xff1a;精致码农-王亮说明&#xff1a;1. 本译文舍弃了少许我实在不知道如何翻译但又不是很重要的语句。2. 本文有不少超链接&#xff0c;由于微信公众号和头条平台外链会被剔除 URL 地址&#xff0c;所以原…

leetcode216. 组合总和 III

一:题目 二:上码 class Solution {/**递归深度根据k值确定,宽度的话那就[1,9]因为递归不允许重复&#xff0c;那么的话我们需要每次在横向遍历的起始位置1*/private List<List<Integer>> ans new ArrayList<>();private List<Integer> path new Arr…

BeetleX之Websocket协议分析详解

Websocket应用协议已经普及多年了&#xff0c;它是HTTP1.1的内部升级协议&#xff0c;主要作用是补充HTTP1.1无法灵活地主动推送消息给客户端的缺陷问题。在这里主要介绍一下使用组件如何扩展一个完整的Websocket协议。协议介绍Websocket并不复杂&#xff0c;但协议文档内容还是…

甲骨文是否可以要求 Java API 享有版权?这场10年官司怎么结

美国最高法院10月7日就 Oracle 甲骨文诉 Google 谷歌一案进行口头辩论。案件一直以来争议的核心是&#xff0c;甲骨文是否可以要求 Java API 享有版权&#xff0c;如果可以&#xff0c;那么谷歌是否侵犯了这些版权&#xff1f;Java API 是否可以享有版权&#xff1f;Sun 公司在…

C#知多少 | 每个版本都更新了什么?

总所周知&#xff0c;.NET5.0马上就要来了&#xff0c;最后一个预览版RC2也已经发布了&#xff0c;在11月的时候&#xff0c;我们就正式的发布了&#xff0c;然后我们就可以迁移使用了&#xff0c;当然今天说的重点不是.NET&#xff0c;今天说的是伴随着.NET5一起到来的C#9.0&a…

leetcode40. 组合总和 II(树层去重)

一:题目 二:上码 class Solution {/**注意这里的去重:1.我们分为树层去重比如[1,1 2,5] target 8,那么如果我们用树枝去重的话,那么就会出现[1,2,5],[1,2,5]那么的话我们就需要树层去重 就是横向遇见相同的元素跳过。2.树枝去重的 树枝去重就是 我们数组前面使用过的元素不…