上面扯了堆没用的。下面咱们进入主题——值转换器。从名字就知道是用来给数据值做类型转换的。有大伙伴会问是 .NET 类型与数据库类型吗EF 自己不是有类型映射吗。老周觉得准确地讲还真不是。应该说是 .NET 类型之间转换才对。不是吧.NET 类型之间也要搞值转换器对的因为在 EF Core 的数据处理过程会有两个值1、Provider这个不要以为是数据库的类型其实是数据库映射到 .NET 的类型。比如你数据库中某列的类型是 NVARCHAR那在 .NET 中就是 String2、Model这个是你定义的实体类中某个属性的类型。通常情况你实体类中的属性类型和数据库提供者映射的 .NET 类型是一致的。比如你有一个实体中Age 属性的类型是 int那么数据库中的类型是 INT。这种情况下你的属性和数据库默认映射的 .NET 类型是一样的不考虑值转换器。但凡事总有例外的。比如你的某个属性类型是枚举而数据库中对应列是 VARCHAR 类型这使得默认映射的 .NET 类型是 String。而你是想着在存入数据库时使用枚举的成员名称。这里头就需要一个“中间人”角色在写入数据库时将枚举值对应的成员名称转为字符串从数据库查询读时将字符串重新 Parse 为枚举值。再比如你的实体中有个表示坐标的属性类型是 Point 类可它只有 X 和 Y 两个属性你觉得没有必要把 Point 也当成实体没必要在数据库中为它建表。其实只要转为像“10085”这样的字符串然后以文本方式保存到数据库读出来的时候还原回 Point 对象就好了。咱们来演示一下。首先在 SQL Server 中执行以下脚本创建测试用的数据库。USE master; GO CREATE DATABASE my_doudou_db; GO USE my_doudou_db; GO CREATE TABLE [dbo].tb_ufo ( id INT IDENTITY NOT NULL, -- ID [desc] NVARCHAR(150) NOT NULL, -- UFO是什么样子的 discover NVARCHAR(20) NOT NULL, -- 谁发现了UFO pos NVARCHAR(64) NOT NULL, -- UFO被发现时所在位置 -- 主键 CONSTRAINT [PK_Ufo_id] primary key (id asc) ); GO然后定义实体类—— Ufo。/// summary /// 表示位置坐标的结构 /// /summary public struct Point { public int X; public int Y; } public class Ufo { /// summary /// 标识 /// /summary public int Id { get; set; } /// summary /// UFO描述 /// /summary public required string Desc { get; set; } /// summary /// 发现者 /// /summary public required string Discover { get; set; } /// summary /// UFO位置 /// /summary public Point Pos { get; set; } }注意咱们这里用 Point 结构来表示坐标但在数据库中是文本类型。毕竟咱们没必要为 Point 单独做表映射更何况结构是不能进行映射的。所以这里头就需要做转换了。可以看到值转换有时候不一定只是类型转换也可能用于转换数据值的表示格式。在派生 DbContext 类时重写 OnModelCreating 方法实现模型配置。public class MyContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // 配置连接字符串 optionsBuilder.UseSqlServer(Data Source(localdb)\\MSSQLLocalDB;Initial Catalogmy_doudou_db;Integrated SecurityTrue); } #region 转换方法 private string PointToStr(Point p) { return ${p.X},{p.Y}; } private Point StrToPoint(string s) { int idx s.IndexOf(,); if(idx 1 || idx s.Length - 1) { return new Point(); } int c1 int.Parse(s.Substring(0, idx)); int c2 int.Parse(s.Substring(idx 1)); return new Point { X c1, Y c2 }; } #endregion protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityUfo(ent { // 属性与列映射 ent.Property(x x.Id).HasColumnName(id); ent.Property(x x.Desc).HasMaxLength(150).HasColumnName(desc); ent.Property(w w.Discover).HasMaxLength(20).HasColumnName(discover); ent.Property(d d.Pos) .HasColumnType(nvarchar(64)) .HasColumnName(pos) // 转换器.HasConversion( m PointToStr(m), p StrToPoint(p) );// 配置主键 ent.HasKey(d d.Id).HasName(PK_Ufo_id); // 映射表名 ent.ToTable(tb_ufo); }); } /// summary /// 数据集合查询访问入口 /// /summary public DbSetUfo Ufos { get; set; } }其他配置相信大伙伴们都很熟了这里重点关注值转换器的配置。主要通过 HasConversion 方法实现。这个方法的重载可多了本次示例咱们用的是以下重载public virtual PropertyBuilderTProperty HasConversionTProvider( ExpressionFuncTProperty, TProvider convertToProviderExpression, ExpressionFuncTProvider, TProperty convertFromProviderExpression);这个版本之所以简单就在于不用定义新类型直接使用两个委托传参。此处类型参数 TProperty 指的是实体类中属性的类型而 TProvider 自然就是数据库提供者默认所需要的类型。本示例中TProperty 是 PointTProvider 是 String。所以第一个委托绑定的方法应该以 Point 为输入类型返回 String第二个委托是以 String 为输入返回 Point 结构实例。私有方法 PointToStr 和 StrToPoint 用于实现转换逻辑。1、Point 转为字符串比较简单直接变成 100,200 的形式即可用逗号隔开2、字符串转回 Point 实例。老周用了简单的方法在字符串搜索逗号记录它在字符串中的位置。接着逗号左边的就是X坐标右边的就是Y坐标。private Point StrToPoint(string s) { int idx s.IndexOf(,); // 逗号不应该出现在开头或结尾 if(idx 1 || idx s.Length - 1) { return new Point(); } // 取逗号左边的子串 int c1 int.Parse(s.Substring(0, idx)); // 取逗号右边的子串 int c2 int.Parse(s.Substring(idx 1)); return new Point { X c1, Y c2 }; }如果你觉得 StrToPoint 的实现不够逼格那也可以改用正则表达式来处理字符串。private Point StrToPoint(string s) { var res Regex.Match(s, (\\d)\\s*,\\s*(\\d)); if(res.Success res.Groups.Count 3) { int x int.Parse(res.Groups[1].Value); int y int.Parse(res.Groups[2].Value); return new Point { X x, Y y }; } return new Point(); }\d 表示至少出现一个数字字符加括号代表数字字符为一个分组。后面可通过 res.Groups 集合获取。中间的 \s*,\s* 代表逗号两边可能有空格。Groups 集合中会包含三个结果第一个元素是匹配的整个字符串不是咱们所需要的咱们要的是包含数字的两个分组。所以应从 Groups 集合的第二个元素起获取坐标值。如果你希望值转换器具有独立性或者能通用于其他代码咱们可以封装一下让其变成一个类——派生自 ValueConverterTModel, TProvider 类。public class MyValueConverter : ValueConverterPoint, string { #region 两个转换方法移到这里 protected static string PointToStr(Point p) { return ${p.X},{p.Y}; } protected static Point StrToPoint(string s) { var res Regex.Match(s, (\\d)\\s*,\\s*(\\d)); if (res.Success res.Groups.Count 3) { int x int.Parse(res.Groups[1].Value); int y int.Parse(res.Groups[2].Value); return new Point { X x, Y y }; } return new Point(); } #endregion // 构造函数 public MyValueConverter() : base(pPointToStr(p),sStrToPoint(s)) { } }ValueConverter 的构造函数需要两个委托作为表达式其含义和前面 HasConversion 方法调用时一样第一个委托表示实体属性的 Point 类型转换为数据库所需要的 String第二个委托则是查询数据时从数据库读取的 String 到实体属性的 Point 类型。顺便把两转换方法也移到 MyValueConverter 类中这样能成为一个整体。回到 OnModelCreating 方法咱们依旧使用 HasConversion 方法进行配置。这个方法有几十个生载呢它支持咱们自定义的转换类。modelBuilder.EntityUfo(ent { // 属性与列映射 …… ent.Property(d d.Pos) .HasColumnType(nvarchar(64)) .HasColumnName(pos) .HasConversionMyValueConverter(); // 配置主键 …… });以上转换器配置均针对Ufo单个实体的不过有些时候Point 可能用在多个实体中。这种情况没必要逐个实体去配置转换器除非特定需求使用批量配置更方便。这就要用到约定了。在 EF Core 中约定仅用于自动发现和配置实体也可以用于批量配置。配置方法在 DbContext 派生类中重写 ConfigureConventions 方法然后调用 ModelConfigurationBuilder.Properties 方法完成属性配置。protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { base.ConfigureConventions(configurationBuilder); configurationBuilder.PropertiesPoint(p { // 配置值转换器 p.HaveConversionMyValueConverter(); }); }这样配置后只要实体中有属性是 Point 类型的都会自动配置值转换器。---------------------------------------------------------------------------------------------------------------------------------EF Core 自身也内置了许多值转换器。因此许多常用且简单的转换不需要调用 HasConversion 方法进行配置EF Core| 自己会完成配置。1、string 与 char 类型的转换当从 string 转换为 char 时候若原字符串长长度大于 1只返回字符串中第一个字符。2、DateOnly 与 String 类型的转换转为字符串时调用 ToString 方法。从字符串中取回 Datelnly 时调用 DateOnly.Parse 方法。3、DateTimeOffset、TimeOnly、TimeSpan、Datetime 与 string 之间的转换与 DateOnly 与 string 的转换一样。4、GUID 与 String 之间的转换。转为字符串时调用 ToString 方法string 转换回 GUID 时调用 Guid 类的构建函数。5、枚举与文本变量间的转换。转换为字符串时直接应用 ToString 方法反过来可用 Enum.TryParse 方法。6、数值类型与 string 间的转换。数值类型包括 int、long、short、byte、decimal、float、double 等转为字符串时用的并不是 ToString 方法而是 Format 方法从 string 转回数值类型时调用的是 Parse 方法。7、Uri 与 string 之间的转换。转为字符串时直接用 ToString 方法反过来用的是 Uri 构造函数把字符串传给它。8、bool 类型。整数 0、1字符串“true”falseTrueFalseYN 都能转为 bool 类型。9、字节数组可以与 base64 字符串之间转换。使用的是 Convert.ToBase64String 和 Convert.FromBase64String 方法。……这么转换的怎么记啊去记它干吗用就是了要是哪里不能转换了它会报异常到时候再自己实现自定义就好了。下面老周举个栗子使用到 Uri 与字符串的转换以及 byte[] 与字符串的转换。由于是内部支持的所以不需要配置任何转换器。好习惯性流程咱们过一遍。1、写实体类。public class WebImage { public int Id { get; set; } public requiredUri Source{ get; set; } public string? Alter { get; set; } public requiredbyte[] InnerData{ get; set; } }