找回密码
 立即注册
首页 业界区 业界 【EF Core】优化后的模型

【EF Core】优化后的模型

貊淀 前天 17:26
本文所讲述内容,大伙伴们不必要完全掌握,毕竟,dotnet ef 工具会帮助咱们生成相关代码。不过,为了让各位能够真正了解它,老周会做一次纯手搓代码。
所谓优化后的模型,相当于把实体的配置进行“硬编码”,程序会执行更少的代码来提升效率。放到实际代码编写上,这个活叫运行时模型,对应的类就是 RuntimeModel。与运行时模型相关的类型都是以“Runtime”开头的,对应着模型各个部分。例如:
1、实体—— RuntimeEntityType 类;
2、属性—— RuntimeProperty 类;
3、主键—— RuntimeKey 类;
4、导航—— RuntimeNavigation 类。导航属性使用专门的类,而不归类于 RuntimeProperty;
5、外键—— RuntimeForeignKey 类;
6、索引—— RuntimeIndex 类;
7、其他,这里就不全列出了。
有大伙伴可能会想:都定义实体类了,为什么还要构建 Model 呢?因为这体类本身无法提供额外的信息,比如谁是主键,映射到哪个表哪个列,有哪些列是索引……而且,ef core 还要跟踪每个实体对象的状态,以便在必要时更新数据库。有太多的附加信息要处理,所以得有专门的类型去封装这些信息。
除了上面列出的专用运行时类型,EF Core 框架还用到另一个重要的东西去描述实体——Annotations(批注,或者叫注释)。EF Core 框架还定义了一个 Annotation 类,专门保存批注信息。批注集合是一个字典,故一条批注是以 Name-Value 的形式存在的。Name 是字符串,Value 是任意类型。咱们在代码中能访问到的常用名称由 RelationalAnnotationNames 公开,如 TableName 字段可以返回表示表名称的字符串。另外,不同数据库提供者也可以公开专用的批注名称,如 SQL Server 数据库公开了 SqlServerAnnotationNames,不过,此类位于 Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal 命名空间,帮助文档上是查不到的,属于留给内部使用的。当然,咱们在代码中是可以访问的。
批注名称是用冒号来分隔层次的,有点像 ASP.NET Core 的配置树。比如,表示 SQL Server 标识列的批注是这样的:SqlServer:Identity,它的值是字符串,格式为 1,1 表示从1开始,增量为1。如果从1000开始增量为5,就是 1000,5(用逗号分隔)。
现在说回 RuntimeModel 类。实例化后,通过对应的方法调用来添加模型:
1、AddEntityType 方法,向模型添加实体,返回 RuntimeEntityType 实例;
2、调用 RuntimeEntityType 实例的 AddProperty 方法添加属性,返回 RuntimeProperty 实例;
3、调用 RuntimeEntityType 实例的 AddKey 方法创建主键,返回 RuntimeKey 实例,随后要调用 SetPrimaryKey 方法设置主键;调用 AddForeignKey 方法添加外键,返回 RuntimeForeignKey 实例。
……
总结一下思路:创模型,添实体,加属性,设主键,置外键,最后补批注
上面就是本文所需的知识,运行时模型在建立时所调用的方法成员参数较多,而且不是所有参数都会用到,一般建议写上参数名。用命名参数就不必按顺序传参。这些参数等下面实际演练时再解释。
===============================================================================================
好了,理论上准备得差不多,接下来老周将纯手动打造一个运行时模型。由于运行时模型是不能创建数据库和表的,所以咱们采用 DB First 方案,先创建数据库。本次老周使用 SQL Server,使用以下脚本创建数据库(建议把脚本存为文件,后面写代码时可以参考):
  1. USE master;
  2. GO
  3. -- 创建数据库
  4. CREATE DATABASE ToyDB;
  5. GO
  6. USE ToyDB;
  7. GO
  8. -- 创建表
  9. CREATE TABLE dbo.tb_boys
  10. (
  11.     boy_id INT IDENTITY NOT NULL,
  12.     [name] NVARCHAR(20) NOT NULL,
  13.     [nick] NVARCHAR(20) NULL,
  14.     CONSTRAINT [PK_boy_id] PRIMARY KEY (boy_id ASC)
  15. );
  16. GO
  17. CREATE TABLE dbo.tb_toys
  18. (
  19.     toy_id INT IDENTITY(100, 1) NOT NULL,
  20.     toy_desc NVARCHAR(50) NOT NULL,
  21.     toy_cate NVARCHAR(10) NOT NULL,
  22.     f_boy_id INT NULL,      -- 外键
  23.     CONSTRAINT [PK_toy_id] PRIMARY KEY (toy_id ASC),
  24.     CONSTRAINT [FK_toy_boy] FOREIGN KEY (f_boy_id) REFERENCES dbo.tb_boys (boy_id) ON DELETE CASCADE
  25. );
  26. GO
复制代码
tb_boys 表代表某男孩,tb_toys 表代表他拥有的玩具,每个 boy 在小时候都有自己喜欢的 toy,正如每个进城的农民工都怀揣着梦想一般;随着 boy 们的长大,许多 toy 逐渐被淡忘;随着时光的流逝,农民工们的梦已被风雨打碎,留下千丝万缕的遗恨与怅惘。
咱们程序中定义的实体类与数据表的名称是不相同的,所以后期要做映射。
  1. public class Boy
  2. {
  3.     public int ID { get; set; }
  4.     public string Name { get; set; } = string.Empty;
  5.     public string? Nick { get; set; }
  6.     // 导航属性
  7.     <strong><em>public ICollection<Toy> MyToys { get; set; } = new List<Toy></em></strong><strong><em>();</em></strong>
  8. }
  9. public class Toy
  10. {
  11.     public int ToyID { get; set; }
  12.     public string Desc { get; set; } = string.Empty;
  13.     public string Cate { get; set; } = "经典玩具";
  14.     // 导航属性的反向引用,外键隐藏
  15.     <strong><em>public Boy? TheBoy { get; set</em></strong><strong><em>; }</em></strong>
  16. }
复制代码
两个类都有导航属性,每个 Boy 的童年不可能只有一件玩具的,每件玩具专属于喜欢它的主人,因此这是一对多的关系。
好了,下面是重头戏,看看怎么构建 RuntimeModel。
1、实例化模型。
  1. RuntimeModel model = new(
  2.     skipDetectChanges: false,   // 开启更改跟踪
  3.     modelId: Guid.NewGuid(),    // 模型ID
  4.     entityTypeCount: 2,         // 模型中有两个实体类型
  5.     typeConfigurationCount: 0   // 未使用自定义类型转换
  6. );
复制代码
skipDetectChanges:99%选 false,因为我们希望跟踪模型的变更。modelId:模型的ID,GUID类型,随便分配,不会重复的。entityTypeCount:这个模型包含几个实体类型,这里明确指出(属于是硬编码了)。typeConfigurationCount:保留默认0即可,实体的类型映射,这里咱们用不上,98%的情形也是用不上的。
2、添加 Boy 实体。
  1. var boyEnt = model.AddEntityType(
  2.     name: "DB.Boy",     // 一般包含命名空间
  3.     type: typeof(Boy),  // CLR 类型
  4.     baseType: null,     // 没有基类(指除Object以外)
  5.     propertyCount: 3,   // 属性个数,不含导航属性
  6.     navigationCount: 1, // 有一个导航属性
  7.     keyCount: 1         // 有一个主键
  8. );
复制代码
name 表示实体名称,一般用实体类的完整类型名(含命名空间+类名)。type 表示实体的类型 Type。baseType:如果 Boy 有基类,要附上基类型。这里说的基类是除 Object 以外,此处,Boy 可以认为无基类型。sharedClrType = false,不是共享的实体类型(多个实体共用一个CLR类型)。propertyCount 表示此实体包含属性个数,应为3,不包括导航属性,导航属性有家族背景,需要特殊对待。navigationCount 表示包含多少个导航属性,这里是一个。keyCount 表示实体有几个主键,这里一个。
3、添加 Boy 实体的属性。注意,属性是加在 RuntimeEntity 中。
  1. var boyIdProp = boyEnt.AddProperty(
  2.     name: nameof(Boy.ID),   // 属性名
  3.     clrType: typeof(int),   // 属性类型
  4.     nullable: false,        // 不为null
  5.                             // 引用CLR属性成员
  6.     propertyInfo: typeof(Boy).GetProperty("ID", BindingFlags.
  7. Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  8.     // 无字段成员
  9.     fieldInfo: null,
  10.     // 优先使用属性来设置值
  11.     propertyAccessMode: PropertyAccessMode.PreferProperty,
  12.     // 插入时生成值
  13.     valueGenerated: ValueGenerated.OnAdd,
  14.     // 实体存入数据库后不允许修改此属性
  15.     afterSaveBehavior: PropertySaveBehavior.Throw,
  16.     // 默认值0
  17.     sentinel: 0
  18. );
  19. // SQL Server 值生成配置-标识列
  20. boyIdProp.AddAnnotation(SqlServerAnnotationNames.
  21. ValueGenerationStrategy, SqlServerValueGenerationStrategy.
  22. IdentityColumn);
  23. var nameProp = boyEnt.AddProperty(
  24.     name: nameof(Boy.Name),
  25.     clrType: typeof(string),
  26.     nullable: false,
  27.     propertyInfo: typeof(Boy).GetProperty("Name", BindingFlags.
  28. Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  29.     maxLength: 20,          // 长度限制
  30.     propertyAccessMode: PropertyAccessMode.PreferProperty
  31. );
  32. var nickProp = boyEnt.AddProperty(
  33.     name: nameof(Boy.Nick),
  34.     clrType: typeof(string),
  35.     nullable: true,
  36.     maxLength: 20,
  37.     propertyInfo: typeof(Boy).GetProperty(nameof(Boy.Nick),
  38. BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.
  39. Instance),
  40.     propertyAccessMode: PropertyAccessMode.PreferProperty
  41. );
复制代码
name:属性名,一般与 CLR 类型的属性名相同。clrType:属性值的类型。propertyInfo:引用CLR属性的成员(通过反射来设置或读取值),设置值时要求用 set 访问器。fieldInfo:存储属性值的字段成员,没有的话也可以为 null。propertyAccessMode:表示 EF Core 如何访问属性,这是一个 PropertyAccessMode 枚举,主要值有:
A、Field:必须通过字段来读写属性。使用该方式,实体类的属性必须定义隐藏字段。
B、FieldDuringConstruction:只在实体实例化(调用构造器)时才通字段访问属性。
C、Property:直接读写属性本身。
D、PreferField:先尝试通过字段来读写属性,如果没有,直接访问属性。
E:PreferFieldDuringConstruction:仅在实例构造时,尝试通过字段访问属性,若不行改为直接访问属性。
F:PreferProperty:通过属性直接访问。如果没有属性,或 get/set 不存在(通常不会这样),就改为通过字段读写。
上述枚举值要根据你的 propertyInfo 和 fieldInfo 参数来配置。
nullable:表示此属性是否可为 null。valueGenerated:值生成,如果是标识列,应选择 ValueGenerated.OnAdd,插入数据时生成。beforeSaveBehavior 和 afterSaveBehavior:指示属性值在写入数据库前后是否允许修改。对于标识列,afterSaveBehavior 参数可以设置为 Throw,即如果数据已存入数据库,那么后面就不让你修改(不能改生成的主键)。maxLength:一般用于字符类型,最大长度。unicode:一般用于字符类型,是否使用 Unicode。precision 和 scale:一般用于浮点数,指定精度和位数。sentinel:如果属性未设置,给它一个默认值。
注意:boyid是标识列,要用批注表明它是标识列。
  1. boyIdProp.AddAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy, <strong>SqlServerValueGenerationStrategy.IdentityColumn</strong>);
复制代码
 
4、给 Boy 添加主键。
  1. // 配置主键
  2. var boyKey = boyEnt.AddKey([boyIdProp]);
  3. boyEnt.SetPrimaryKey(boyKey);
  4. // 主键名称
  5. boyKey.<strong>AddAnnotation(RelationalAnnotationNames.Name, "PK_boy_id")</strong>;
复制代码
主键的名称通过批注来设置。AddKey 方法引用一个属性列表,即哪些属性设置为键。
5、 Toy 实体的配置差不多。
  1. var toyEnt = model.AddEntityType(
  2.     name: "DB.Toy",
  3.     type: typeof(Toy),
  4.     baseType: null,
  5.     propertyCount: 3,           // 属性数量不含导航属性
  6.     navigationCount: 1,         // 导航属性个数
  7.     keyCount: 1,                // 主键个数
  8.     foreignKeyCount: 1          // 包含外键
  9. );
  10. var toyidProp = toyEnt.AddProperty(
  11.     name: nameof(Toy.ToyID),
  12.     clrType: typeof(int),
  13.     nullable: false,
  14.     valueGenerated: ValueGenerated.OnAdd,
  15.     afterSaveBehavior: PropertySaveBehavior.Throw,
  16.     propertyAccessMode: PropertyAccessMode.PreferProperty,
  17.     propertyInfo: typeof(Toy).GetProperty(nameof(Toy.ToyID),
  18. BindingFlags.Public | BindingFlags.Instance | BindingFlags.
  19. DeclaredOnly),
  20.     sentinel: 0
  21. );
  22. // 配置标识列
  23. toyidProp.AddAnnotation(SqlServerAnnotationNames.
  24. ValueGenerationStrategy, SqlServerValueGenerationStrategy.
  25. IdentityColumn);
  26. var toydescProp = toyEnt.AddProperty(
  27.     name: nameof(Toy.Desc),
  28.     clrType: typeof(string),
  29.     nullable: false,
  30.     maxLength: 50,
  31.     propertyInfo: typeof(Toy).GetProperty(nameof(Toy.Desc),
  32. BindingFlags.Public | BindingFlags.Instance | BindingFlags.
  33. DeclaredOnly),
  34.     propertyAccessMode: PropertyAccessMode.PreferProperty
  35. );
  36. var toycateProp = toyEnt.AddProperty(
  37.     name: nameof(Toy.Cate),
  38.     clrType: typeof(string),
  39.     maxLength: 10,
  40.     propertyInfo: typeof(Toy).GetProperty(nameof(Toy.Cate),
  41. BindingFlags.Public | BindingFlags.Instance | BindingFlags.
  42. DeclaredOnly),
  43.     propertyAccessMode: PropertyAccessMode.PreferProperty
  44. );
  45. // 以下属性为影子属性,映射到外键
  46. var toytoboyIdProp = toyEnt.AddProperty(
  47.     name: "boy_id",
  48.     clrType: typeof(int),
  49.     propertyInfo: null,     // 影子属性不需要引用属性成员
  50.     nullable: true
  51. );
复制代码
boy_id 是影子属性,因为不需要与 CLR 类型映射,所以,propertyInfo 和 fieldInfo 参数不需要。虽然这货没有在实体类中定义,但 EF Core 内部会用一个字典来管理它。实际上 EF Core 为实体类存了不止一份值,比如“快照”。即当数据从数据库查询出来时会做一个快照,后面会用实体的值跟快照比较,以确定实体是否被修改了。
6、给 Toy 实体设置主键和外键。
  1. // 设置主键
  2. var toyKey = toyEnt.AddKey([toyidProp]);
  3. // 主键名
  4. toyKey.AddAnnotation(RelationalAnnotationNames.Name, "PK_toy_id");
  5. toyEnt.SetPrimaryKey(toyKey);
  6. // 设置外键
  7. var toyforekey = toyEnt.AddForeignKey(
  8.      properties: [toytoboyIdProp],
  9.      principalKey: boyKey,
  10.      principalEntityType: boyEnt,
  11.      deleteBehavior: DeleteBehavior.Cascade,
  12.      unique: false               // 一对多,这里不唯一
  13. );
  14. // 外键名称
  15. toyforekey.AddAnnotation(RelationalAnnotationNames.Name,
  16. "FK_toy_boy");
复制代码
由于 Toy 表中会出现重复的 Boy ID,所以,AddForeignKey 方法的 unique 参数为 false。
7、导航属性,不管是 Boy 中的导航属性还是 Toy 中的,共用一个外键信息。
  1. var boytotoyNav = boyEnt.AddNavigation(
  2.     name: nameof(Boy.MyToys),
  3.     <strong>foreignKey: toyforekey</strong>,
  4.     <strong>onDependent: </strong><strong>false</strong>,
  5.     clrType: typeof(ICollection<Toy>),
  6.     propertyInfo: typeof(Boy).GetProperty(nameof(Boy.MyToys),
  7. BindingFlags.Public | BindingFlags.Instance | BindingFlags.
  8. DeclaredOnly),
  9.     fieldInfo: null,
  10.     propertyAccessMode: PropertyAccessMode.PreferProperty
  11. );
  12. var toytoboyNav = toyEnt.AddNavigation(
  13.     name: nameof(Toy.TheBoy),
  14.     <strong>foreignKey: toyforekey</strong>,
  15.     <strong>onDependent: </strong><strong>true</strong>,
  16.     clrType: typeof(Boy),
  17.     propertyInfo: typeof(Toy).GetProperty(nameof(Toy.TheBoy),
  18. BindingFlags.Public | BindingFlags.Instance | BindingFlags.
  19. DeclaredOnly),
  20.     fieldInfo: null,
  21.     propertyAccessMode: PropertyAccessMode.PreferProperty
  22. );
复制代码
导航属性的添加和一般属性差不多。注意 onDependent 参数的设置,对于 Boy 来说,它应该是“父”,即主体(被 Toy 的外键引用),要设置为 false;而对于 Toy 而言为“子”,即依赖于主体,故 onDependent 为 true。
8、由于实体名和属性名都与数据库不同,所以还要做表、列的映射。这些也是通过批注完成的。
  1. // 1、架构
  2. boyEnt.AddAnnotation(RelationalAnnotationNames.Schema, "dbo");
  3. toyEnt.AddAnnotation(RelationalAnnotationNames.Schema, "dbo");
  4. // 2、表名
  5. boyEnt.AddAnnotation(RelationalAnnotationNames.TableName, "tb_boys");
  6. toyEnt.AddAnnotation(RelationalAnnotationNames.TableName, "tb_toys");
  7. // 3、列名
  8. boyIdProp.AddAnnotation(RelationalAnnotationNames.ColumnName,
  9. "boy_id");
  10. nameProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "name");
  11. nickProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "nick");
  12. toyidProp.AddAnnotation(RelationalAnnotationNames.ColumnName,
  13. "toy_id");
  14. toydescProp.AddAnnotation(RelationalAnnotationNames.ColumnName,
  15. "toy_desc");
  16. toycateProp.AddAnnotation(RelationalAnnotationNames.ColumnName,
  17. "toy_cate");
  18. toytoboyIdProp.AddAnnotation(RelationalAnnotationNames.ColumnName,
  19. "f_boy_id");
复制代码
 
好了,模型就配好了。下面是完整代码。老周用了一个静态类来封装(你也可以像 dotnet-ef 工具那样,从 RuntimeModel 类派生)。
  1. #pragma warning disable EF1001 // Internal EF Core API usage.
  2. public static class ModelHelpers
  3. {
  4.     private static readonly RuntimeModel _model;
  5.     static ModelHelpers()
  6.     {
  7.         _model = BuildRuntimeModel();
  8.     }
  9.     public static RuntimeModel DemoModel => _model;
  10.     private static RuntimeModel BuildRuntimeModel()
  11.     {
  12.         RuntimeModel model = new(
  13.             skipDetectChanges: false,   // 开启更改跟踪
  14.             modelId: Guid.NewGuid(),    // 模型ID
  15.             entityTypeCount: 2,         // 模型中有两个实体类型
  16.             typeConfigurationCount: 0   // 未使用自定义类型转换
  17.         );
  18.         // 配置第一个实体
  19.         var boyEnt = model.AddEntityType(
  20.             name: "DB.Boy",     // 一般包含命名空间
  21.             type: typeof(Boy),  // CLR 类型
  22.             baseType: null,     // 没有基类(指除Object以外)
  23.             propertyCount: 3,   // 属性个数,不含导航属性
  24.             navigationCount: 1, // 有一个导航属性
  25.             keyCount: 1         // 有一个主键
  26.         );
  27.         // 添加属性
  28.         var boyIdProp = boyEnt.AddProperty(
  29.             name: nameof(Boy.ID),   // 属性名
  30.             clrType: typeof(int),   // 属性类型
  31.             nullable: false,        // 不为null
  32.                                     // 引用CLR属性成员
  33.             propertyInfo: typeof(Boy).GetProperty("ID", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  34.             // 无字段成员
  35.             fieldInfo: null,
  36.             // 优先使用属性来设置值
  37.             propertyAccessMode: PropertyAccessMode.PreferProperty,
  38.             // 插入时生成值
  39.             valueGenerated: ValueGenerated.OnAdd,
  40.             // 实体存入数据库后不允许修改此属性
  41.             afterSaveBehavior: PropertySaveBehavior.Throw,
  42.             // 默认值0
  43.             sentinel: 0
  44.         );
  45.         // SQL Server 值生成配置-标识列
  46.         boyIdProp.AddAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy, SqlServerValueGenerationStrategy.IdentityColumn);
  47.         var nameProp = boyEnt.AddProperty(
  48.             name: nameof(Boy.Name),
  49.             clrType: typeof(string),
  50.             nullable: false,
  51.             propertyInfo: typeof(Boy).GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  52.             maxLength: 20,          // 长度限制
  53.             propertyAccessMode: PropertyAccessMode.PreferProperty
  54.         );
  55.         var nickProp = boyEnt.AddProperty(
  56.             name: nameof(Boy.Nick),
  57.             clrType: typeof(string),
  58.             nullable: true,
  59.             maxLength: 20,
  60.             propertyInfo: typeof(Boy).GetProperty(nameof(Boy.Nick), BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance),
  61.             propertyAccessMode: PropertyAccessMode.PreferProperty
  62.         );
  63.         // 配置主键
  64.         var boyKey = boyEnt.AddKey([boyIdProp]);
  65.         boyEnt.SetPrimaryKey(boyKey);
  66.         // 主键名称
  67.         boyKey.AddAnnotation(RelationalAnnotationNames.Name, "PK_boy_id");
  68.         // 添加第二个实体
  69.         var toyEnt = model.AddEntityType(
  70.             name: "DB.Toy",
  71.             type: typeof(Toy),
  72.             baseType: null,
  73.             propertyCount: 3,           // 属性数量不含导航属性
  74.             navigationCount: 1,         // 导航属性个数
  75.             keyCount: 1,                // 主键个数
  76.             foreignKeyCount: 1          // 包含外键
  77.         );
  78.         // 添加属性
  79.         var toyidProp = toyEnt.AddProperty(
  80.             name: nameof(Toy.ToyID),
  81.             clrType: typeof(int),
  82.             nullable: false,
  83.             valueGenerated: ValueGenerated.OnAdd,
  84.             afterSaveBehavior: PropertySaveBehavior.Throw,
  85.             propertyAccessMode: PropertyAccessMode.PreferProperty,
  86.             propertyInfo: typeof(Toy).GetProperty(nameof(Toy.ToyID), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  87.             sentinel: 0
  88.         );
  89.         // 配置标识列
  90.         toyidProp.AddAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy, SqlServerValueGenerationStrategy.IdentityColumn);
  91.         var toydescProp = toyEnt.AddProperty(
  92.             name: nameof(Toy.Desc),
  93.             clrType: typeof(string),
  94.             nullable: false,
  95.             maxLength: 50,
  96.             propertyInfo: typeof(Toy).GetProperty(nameof(Toy.Desc), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  97.             propertyAccessMode: PropertyAccessMode.PreferProperty
  98.         );
  99.         var toycateProp = toyEnt.AddProperty(
  100.             name: nameof(Toy.Cate),
  101.             clrType: typeof(string),
  102.             maxLength: 10,
  103.             propertyInfo: typeof(Toy).GetProperty(nameof(Toy.Cate), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  104.             propertyAccessMode: PropertyAccessMode.PreferProperty
  105.         );
  106.         // 以下属性为影子属性,映射到外键
  107.         var toytoboyIdProp = toyEnt.AddProperty(
  108.             name: "boy_id",
  109.             clrType: typeof(int),
  110.             propertyInfo: null,     // 影子属性不需要引用属性成员
  111.             nullable: true
  112.         );
  113.         // 设置主键
  114.         var toyKey = toyEnt.AddKey([toyidProp]);
  115.         // 主键名
  116.         toyKey.AddAnnotation(RelationalAnnotationNames.Name, "PK_toy_id");
  117.         toyEnt.SetPrimaryKey(toyKey);
  118.         // 设置外键
  119.         var toyforekey = toyEnt.AddForeignKey(
  120.             properties: [toytoboyIdProp],
  121.             principalKey: boyKey,
  122.             principalEntityType: boyEnt,
  123.             deleteBehavior: DeleteBehavior.Cascade,
  124.             unique: false               // 一对多,这里不唯一
  125.         );
  126.         // 外键名称
  127.         toyforekey.AddAnnotation(RelationalAnnotationNames.Name, "FK_toy_boy");
  128.         // 建立导航关系
  129.         var boytotoyNav = boyEnt.AddNavigation(
  130.             name: nameof(Boy.MyToys),
  131.             foreignKey: toyforekey,
  132.             onDependent: false,
  133.             clrType: typeof(ICollection<Toy>),
  134.             propertyInfo: typeof(Boy).GetProperty(nameof(Boy.MyToys), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  135.             fieldInfo: null,
  136.             propertyAccessMode: PropertyAccessMode.PreferProperty
  137.         );
  138.         var toytoboyNav = toyEnt.AddNavigation(
  139.             name: nameof(Toy.TheBoy),
  140.             foreignKey: toyforekey,
  141.             onDependent: true,
  142.             clrType: typeof(Boy),
  143.             propertyInfo: typeof(Toy).GetProperty(nameof(Toy.TheBoy), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  144.             fieldInfo: null,
  145.             propertyAccessMode: PropertyAccessMode.PreferProperty
  146.         );
  147.         // 表、列映射
  148.         // 1、架构
  149.         boyEnt.AddAnnotation(RelationalAnnotationNames.Schema, "dbo");
  150.         toyEnt.AddAnnotation(RelationalAnnotationNames.Schema, "dbo");
  151.         // 2、表名
  152.         boyEnt.AddAnnotation(RelationalAnnotationNames.TableName, "tb_boys");
  153.         toyEnt.AddAnnotation(RelationalAnnotationNames.TableName, "tb_toys");
  154.         // 3、列名
  155.         boyIdProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "boy_id");
  156.         nameProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "name");
  157.         nickProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "nick");
  158.         toyidProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "toy_id");
  159.         toydescProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "toy_desc");
  160.         toycateProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "toy_cate");
  161.         toytoboyIdProp.AddAnnotation(RelationalAnnotationNames.ColumnName, "f_boy_id");
  162.         // 完事,收工
  163.         return model;
  164.     }
  165. }
  166. #pragma warning restore EF1001 // Internal EF Core API usage.
复制代码
 
下面定义上下文类。
  1. public class DemoContext : DbContext
  2. {
  3.     protected override void OnConfiguring(DbContextOptionsBuilder ob)
  4.     {
  5.         ob.UseSqlServer(@"server=你的服务器; database=你的数据库")
  6.             .LogTo(o => Console.WriteLine(o))
  7.             .EnableSensitiveDataLogging(true)
  8.             .<strong>UseModel(ModelHelpers.DemoModel)</strong>;
  9.     }
  10.     public DbSet<Boy> BoySet { get; set; }
  11.     public DbSet<Toy> ToySet { get; set; }
  12. }
复制代码
熟悉的配方,不用多解释了。
 
下面是测试代码。
  1. using DemoContext dc = new();
  2. if (!dc.BoySet.Any())
  3. {
  4.     Boy b1 = new()
  5.     {
  6.         Name = "小张",
  7.         Nick = "灰太狼"
  8.     };
  9.     b1.MyToys.Add(new()
  10.     {
  11.         Desc = "电动风车",
  12.         Cate = "电动玩具"
  13.     });
  14.     b1.MyToys.Add(new()
  15.     {
  16.         Desc = "米老鼠高压水枪",
  17.         Cate = "气动玩具"
  18.     });
  19.     dc.BoySet.Add(b1);
  20.     Boy b2 = new Boy
  21.     {
  22.         Nick = "哈巴狗",
  23.         Name = "小李"
  24.     };
  25.     b2.MyToys.Add(new() { Desc = "库洛牌", Cate = "卡牌" });
  26.     b2.MyToys.Add(new() { Desc = "yoyo", Cate = "光电玩具" });
  27.     dc.BoySet.Add(b2);
  28.     // 更新
  29.     dc.SaveChanges();
  30. }
复制代码
插入的数据如下:
1.png

2.png

 
================================================================================================
上面内容是不实用的,只是方便学习,下面老周演示一下如何用 dotnet-ef 工具生成运行时模型。这才是咱们在实际项目中要用的(除非特殊需求,要自己去写)。
1、新建一个控制台项目。
  1. dotnet new console -n Demo -o .
复制代码
2、添加要用的包。
  1. dotnet add package microsoft.entityframeworkcore
  2. dotnet add package microsoft.entityframeworkcore.sqlserver
复制代码
3、要使用 dotnet-ef 工具,还得添加设计时包。
  1. dotnet add package microsoft.entityframeworkcore.design
复制代码
添加完毕后,项目应该引用了三个库:
  1.   <ItemGroup>
  2.     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
  3.     <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.8">
  4.       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  5.       <PrivateAssets>all</PrivateAssets>
  6.     </PackageReference>
  7.     <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.8" />
  8.   </ItemGroup>
复制代码
设计时库被设置为“私有资产”,你在代码中无法访问。如果你在代码要用,最简单方法是把 IncludeAssets、PrivateAssets 节点干掉。
 
4、定义实体类。
  1. public class Light
  2. {
  3.     /// <summary>
  4.     /// 主键
  5.     /// </summary>
  6.     public Guid LightID { get; set; }
  7.     /// <summary>
  8.     /// 灯光颜色
  9.     /// </summary>
  10.     public string[] Color { get; set; } = ["白"];
  11.     /// <summary>
  12.     /// 功率(瓦)
  13.     /// </summary>
  14.     public float Power { get; set; }
  15.     /// <summary>
  16.     /// 是否为 RGB LED 灯
  17.     /// </summary>
  18.     public bool? IsRGBLed { get; set; }
  19. }
复制代码
5、定义上下文类。
  1. public class MyDbContext : DbContext
  2. {
  3.     public MyDbContext(DbContextOptions<MyDbContext> options)
  4.         : base(options)
  5.     {
  6.     }
  7.     // 配置模型
  8.     protected override void OnModelCreating(ModelBuilder mb)
  9.     {
  10.         mb.Entity<Light>().HasKey(e => e.LightID).HasName("PK_light_id");
  11.         mb.Entity<Light>().Property(g => g.LightID)
  12.             .ValueGeneratedOnAdd();
  13.         mb.Entity<Light>().Property(x => x.Color)
  14.             .HasMaxLength(200).IsRequired();
  15.         mb.Entity<Light>().Property(s => s.Power)
  16.             .HasPrecision(5, 2);
  17.         mb.Entity<Light>().ToTable("tb_lights", "dbo", tb =>
  18.         {
  19.             tb.Property(a => a.LightID).HasColumnName("light_id");
  20.             tb.Property(a => a.Color).HasColumnName("light_colors");
  21.             tb.Property(a => a.Power).HasColumnName("light_pwr");
  22.             tb.Property(a => a.IsRGBLed).HasColumnName("is_rgbled");
  23.         });
  24.     }
  25.     public DbSet<Light> Lights { get; set; }
  26. }
复制代码
6、定义一个类,实现 IDesignTimeDbContextFactory 接口。在执行 dotnet ef 命令时可以用来创建 MyDbContext 实例(设计阶段专用)。
  1. public class CustDesigntimeFactory : IDesignTimeDbContextFactory<MyDbContext>
  2. {
  3.     public MyDbContext CreateDbContext(string[] args)
  4.     {
  5.         // 创建选项
  6.         DbContextOptions<MyDbContext> options = new DbContextOptionsBuilder<MyDbContext>()
  7.             .UseSqlServer(@"server=你的服务器; database=你的数据库")
  8.             .LogTo(s => Console.WriteLine(s))
  9.             .Options;
  10.         // 实例化上下文对象
  11.         return <strong>new</strong><strong> MyDbContext(options)</strong>;
  12.     }
  13. }
复制代码
代码不用往下写了,这个时候,就可以用 ef 工具生成优化的模型了。
  1. dotnet ef dbcontext optimize -c <em>MyDbContext</em> -n <em>DB</em> -o <em>DB</em>
复制代码
-c 指定要用到的 DbContext 子类,这里可以省略,工具会自动搜索项目中的 MyDbContext 类。-n 表示生成代码使用的命名空间,这里指定了 DB。-o 表示生成的代码文件存放到哪个目录中,这里存到 DB 目录下(相对于项目目录)。
工具先编译一下项目,然后实例化 MyDbContext 类,执行一遍模型配置,最终生成相关代码。
3.png

 MyDbContextModel.cs 和 MyDbContextModelBuilder.cs 都是模型类相关。
  1. namespace DB
  2. {
  3.     <strong>[DbContext(</strong><strong>typeof(MyDbContext))]
  4. </strong>    public partial class MyDbContextModel : RuntimeModel
  5.     {
  6.         private static readonly bool _useOldBehavior31751 =
  7.             System.AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31751", out var enabled31751) && enabled31751;
  8.         static MyDbContextModel()
  9.         {
  10.             var model = new MyDbContextModel();
  11.             if (_useOldBehavior31751)
  12.             {
  13.                 model.Initialize();
  14.             }
  15.             else
  16.             {
  17.                 var thread = new System.Threading.Thread(RunInitialization, 10 * 1024 * 1024);
  18.                 thread.Start();
  19.                 thread.Join();
  20.                 void RunInitialization()
  21.                 {
  22.                     model.Initialize();
  23.                 }
  24.             }
  25.             model.Customize();
  26.             _instance = (MyDbContextModel)model.FinalizeModel();
  27.         }
  28.         private static MyDbContextModel _instance;
  29.         public static IModel Instance => _instance;
  30.         partial void Initialize();
  31.         partial void Customize();
  32.     }
  33. }
复制代码
注意,MyDbContextModel 类是 partial 的,意味着你可以自定义扩展它,只要写在其他文件中,若模型修改了,重新运行 ef dbcontext 命令也不会被覆盖。
此类应用了 DbContextAttribute 特性类,表明此模型与 MyDbContext 关联。
LightEntityType.cs 文件就是配置 Light 实体的代码了,看看里面有啥。
  1.     public partial class LightEntityType
  2.     {
  3.         public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null)
  4.         {
  5.             var runtimeEntityType = model.<strong>AddEntityType</strong>(
  6.                 "DataBS.Light",
  7.                 typeof(Light),
  8.                 baseEntityType,
  9.                 propertyCount: 4,
  10.                 keyCount: 1);
  11.             var lightID = runtimeEntityType.AddProperty(
  12.                 "LightID",
  13.                 typeof(Guid),
  14.                 propertyInfo: typeof(Light).GetProperty("LightID", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  15.                 fieldInfo: typeof(Light).GetField("<LightID>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  16.                 valueGenerated: ValueGenerated.OnAdd,
  17.                 afterSaveBehavior: PropertySaveBehavior.Throw,
  18.                 sentinel: new Guid("00000000-0000-0000-0000-000000000000"));
  19.             var overrides = new StoreObjectDictionary<RuntimeRelationalPropertyOverrides>();
  20.             var lightIDTb_lights = new RuntimeRelationalPropertyOverrides(
  21.                 lightID,
  22.                 StoreObjectIdentifier.Table("tb_lights", "dbo"),
  23.                 true,
  24.                 "light_id");
  25.             overrides.Add(StoreObjectIdentifier.Table("tb_lights", "dbo"), lightIDTb_lights);
  26.             lightID.AddAnnotation("Relational:RelationalOverrides", overrides);
  27.             lightID.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None);
  28.             var color = runtimeEntityType.AddProperty(
  29.                 "Color",
  30.                 typeof(string[]),
  31.                 propertyInfo: typeof(Light).GetProperty("Color", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  32.                 fieldInfo: typeof(Light).GetField("<Color>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  33.                 maxLength: 200);
  34.             var overrides0 = new StoreObjectDictionary<RuntimeRelationalPropertyOverrides>();
  35.             var colorTb_lights = new RuntimeRelationalPropertyOverrides(
  36.                 color,
  37.                 StoreObjectIdentifier.Table("tb_lights", "dbo"),
  38.                 true,
  39.                 "light_colors");
  40.             overrides0.Add(StoreObjectIdentifier.Table("tb_lights", "dbo"), colorTb_lights);
  41.             color.AddAnnotation("Relational:RelationalOverrides", overrides0);
  42.             color.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None);
  43.             var isRGBLed = runtimeEntityType.AddProperty(
  44.                 "IsRGBLed",
  45.                 typeof(bool?),
  46.                 propertyInfo: typeof(Light).GetProperty("IsRGBLed", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  47.                 fieldInfo: typeof(Light).GetField("<IsRGBLed>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  48.                 nullable: true);
  49.             var overrides1 = new StoreObjectDictionary<RuntimeRelationalPropertyOverrides>();
  50.             var isRGBLedTb_lights = new RuntimeRelationalPropertyOverrides(
  51.                 isRGBLed,
  52.                 StoreObjectIdentifier.Table("tb_lights", "dbo"),
  53.                 true,
  54.                 "is_rgbled");
  55.             overrides1.Add(StoreObjectIdentifier.Table("tb_lights", "dbo"), isRGBLedTb_lights);
  56.             isRGBLed.AddAnnotation("Relational:RelationalOverrides", overrides1);
  57.             isRGBLed.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None);
  58.             var power = runtimeEntityType.AddProperty(
  59.                 "Power",
  60.                 typeof(float),
  61.                 propertyInfo: typeof(Light).GetProperty("Power", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  62.                 fieldInfo: typeof(Light).GetField("<Power>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
  63.                 precision: 5,
  64.                 scale: 2,
  65.                 sentinel: 0f);
  66.             var overrides2 = new StoreObjectDictionary<RuntimeRelationalPropertyOverrides>();
  67.             var powerTb_lights = new RuntimeRelationalPropertyOverrides(
  68.                 power,
  69.                 StoreObjectIdentifier.Table("tb_lights", "dbo"),
  70.                 true,
  71.                 "light_pwr");
  72.             overrides2.Add(StoreObjectIdentifier.Table("tb_lights", "dbo"), powerTb_lights);
  73.             power.AddAnnotation("Relational:RelationalOverrides", overrides2);
  74.             power.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None);
  75.             var key = runtimeEntityType.AddKey(
  76.                 new[] { lightID });
  77.             runtimeEntityType.SetPrimaryKey(key);
  78.             key.AddAnnotation("Relational:Name", "PK_light_id");
  79.             return runtimeEntityType;
  80.         }
  81.         public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
  82.         {
  83.             runtimeEntityType.AddAnnotation("Relational:FunctionName", null);
  84.             runtimeEntityType.AddAnnotation("Relational:Schema", "dbo");
  85.             runtimeEntityType.AddAnnotation("Relational:SqlQuery", null);
  86.             runtimeEntityType.AddAnnotation("Relational:TableName", "tb_lights");
  87.             runtimeEntityType.AddAnnotation("Relational:ViewName", null);
  88.             runtimeEntityType.AddAnnotation("Relational:ViewSchema", null);
  89.             Customize(runtimeEntityType);
  90.         }
  91.         static partial void Customize(RuntimeEntityType runtimeEntityType);
  92.     }
复制代码
是不是和咱们前文中自己手写的很像?所以,实际开发中咱们是不用自己动手写的,用工具生成即可。
MyDbContextAssemblyAttributes.cs 文件有一个程序集级别的特性应用。
  1. [assembly: DbContextModel(typeof(MyDbContext), typeof(MyDbContextModel))]
复制代码
有了这个特性,EF Core 就能自己查找 RuntimeModel,不需要调用 DbContextOptionsBuilder 的 UseModel 方法来添加外部模型了。
 
顺便总结一下,EF Core 框架是按这个顺序查找模型的:
1、UseModel 方法指定的外部模型(请看上一篇水文),如果从选项类设置了,就用这个模型;
2、如果选项类没有用 UseModel 方法设置外部模型,那就找一下有没 dotnet-ef 工具生成的模型(就是咱们刚刚做的事)。如果有,就用它,原理是根据程序集上应用的 DbContextModelAttribute 特性找到生成的模型类,如本例中的 MyDbContextModel。接着查找模型类中名为 Instance 的静态属性,读取这个属性的值,就能获取模型实例了。显然,运行时模型实例是静态的,即只实例化一次。
可以看看源代码:
  1. static IModel? FindCompiledModel(Type contextType)
  2. {
  3.      var contextAssembly = contextType.Assembly;
  4.      IModel? model = null;
  5.      foreach (var modelAttribute in contextAssembly.
  6. GetCustomAttributes<DbContextModelAttribute>())
  7.      {
  8.          if (modelAttribute.ContextType != contextType)
  9.          {
  10.              continue;
  11.          }
  12.          var modelType = modelAttribute.ModelType;
  13.          var instanceProperty = modelType.GetProperty("Instance",
  14. BindingFlags.Public | BindingFlags.Static);
  15.          if (instanceProperty == null
  16.              || instanceProperty.PropertyType != typeof(IModel))
  17.          {
  18.              throw new InvalidOperationException(CoreStrings.
  19. CompiledModelMissingInstance(modelType.DisplayName
  20. ()));
  21.          }
  22.          if (model != null)
  23.          {
  24.              throw new InvalidOperationException(
  25.                  CoreStrings.CompiledModelDuplicateAttribute(
  26.                      contextAssembly.FullName, contextType.
  27. DisplayName()));
  28.          }
  29.          model = (IModel)instanceProperty.GetValue(null)!;
  30.      }
  31.      return model;
  32. }
复制代码
3、如果找不到 ef 工具生成的运行时模型,就调用内部模型构建,即调用 DbContext 的 OnModelCreating 方法以设计时形态构建模型。构建完毕后,通过预置约定 RuntimeModelConvention 生成 RuntimeModel。当然了,前面多次提到,如果要在代码中创建数据库或迁移,是不使用运行时模型的,而是直接跑 OnModelCreating 方法的配置代码,当然,预置约定也会全部跑一遍。
 
=========================================================================================
上周六,老周作为外三路辅助招聘大师,和两家企业的内部招聘人员交流。他们抱怨说现在的码农怎么回事,技术水平咋感觉一代不如一代,是不是现在工具太好用了,还有 AI 辅助,反而让他变菜了?就此老周也发表了自己的荒唐观点:
过度依赖 AI 以及其他工具并不是他们菜的原因,而是他们菜的结果。工具本身没啥,爱用就用,不爱用就不用,就是辅助的,真正干事的还是人类。但核心事件是——他们就是菜,从骨子里透出来的菜,而且,菜还不练!整天做出一副要改革职场的屌样。你们又不是缺人缺到项目写不动的程度,真要是那么缺人,要不分一点给我,我帮你做。既然招不到人项目还是能继续干的,那就不着急,总能找到不菜的人。我不相信国内的程序猿全都那么菜。
如果你想老周分析一下,现在很多码仔那么菜的原因,那对不起了,老周又要说你们不爱听的话了。那些人就是被当今网络环境忽悠成菜鸟的。如果你在十几年前关注过一些培训班,不管培训钢琴、古筝、编程啥的,它们很喜欢一句口号——“XXX 内速成”,比如一个月内速成,三个月内速成。有脑子都知道是P话,但架不住许多驴子和家长会相信。后来央视曝光过,现在“速成”二字很少见了。但是,这些骗子仍旧屎心不改,换个模式接着忽悠。于是出现了学 XXX 拿 YYY 万高薪。不出意外,也有许多驴子相信了。当初还有些大学森问过老周,报不报这些?老周说:“你学校没得学吗?图书馆地震了吗?没开课吗?何必呢?那些‘老师’估计比你们学校的老师还菜”。要是你听了它们的鬼话,估计连面试都过不了,还想什么高薪。
在网上曾看到过一个笑话,A说:上次有个哥们来我们这面试,问了一个 C++ 虚函数表的问题,那家伙直跑了。简历上还写着“精通C++”。然后下面,B网友说“那是北大青X出来的吧”。
现在,坑人模式升级了,不,准确来说没升级,2005 年前后网上就有这些货了,只是那时候没有短视频,也没那么多人上网。现在短视频里教别人编程的,老周可以不客气的说:全是坑人的(包括老周自己录的,也是忽悠你的)。
在老周心中,什么样的视频教程是合格的?如果各位和老周一样足够老的话,一定看过孙鑫老师的 C++ 视频,20集,时长惊人,含金量不用多说。说简单点,视频教程要达到这种层次,才是有观看价值的。你看看现在各种短视频里面,有这种水平的吗?
还是看书好,就算有些书内容不怎么样,但至少内容是相对全面系统化的,视频、博文、贴子可以作为辅助学习。
王经理说老周面试时太仁慈,只问些基础知识,从来不问项目经验。我就给王经理解释了一通。实际应用中,个个项目是不同的,就是有个别的书本会讲项目上的事,但对于实际工作中还是没多大意义的。工作经验是靠总结,每个人的想法都可能不一样,没办法作为知识点让你一条一条去学,学了也没用。其实项目经验这东西,把你放项目里面呆几个月基本就有经验了,不用学的,自然就会。
可是,技术和基础知识则不同,这些都必须去学的。你得先把这一关过了,项目上要安排你做什么你随机应变就行,你只要技术够硬,就马上就能想到这个东西要用什么解决。比如这里是不是要用 Web API 实现,那里是不是要用 SHAXXX 加密一下,那里是不是要用到压缩数据,某个窗口是不是要开后台线程处理,某个工序是不是要创建一个队列来排单……
要是你基础知识没学好,我叫你改一下那个页面,登录加密加个下拉表列,可以选 MD5、SHA1、SHA256 算法,然后提交前用对应的算法 Hash 一下密码。这里你完全可以用反射来做,.NET 的 MD5 等类,都有一个静态的 Create 方法……懂我的意思了吧。但是,如果你连反射是啥都不知道,自然不会想到这样解决,可能会用多 if 语句处理。虽说不是不行,但不如反射来得通用且简便。
说多了,其实就是现在很多人不愿意去实践,学了不去试,懒得动手,等真正要用的时候就不知所措了。
 

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册