9.3 数据库实体
特别提醒
一旦定义了实体或改变了实体结构或实体配置,需要重新执行 Add-Migration
和 Update-Database
命令。
9.3.1 数据库实体
在面向对象开发思想中,最重要尤为对象二字,在 .NET 开发过去,操作数据库往往采用 DataTable
和 DataSet
来接收数据库返回结果集,而操作数据库也离不开手写 sql
语句。
在过去面向过程和应用不发达的时代,这些操作确实好使。然后随着中国互联网网民的激增,电子化时代的到来,各行各业对应用需求也达到了前所未有的量级。
所以,在过去手写 sql
的时代各种问题显露无疑:
- 程序员能力参差不齐,写出的
sql
性能自然也天差地别 sql
属于字符串硬编程,后期维护难上加难- 许多单表甚至多表结构一致,出现大量重复
sql
代码 sql
本身在不同的数据库提供器中写法有差,后续迁移头痛不已
当然,sql
是时代的产物,我们也离不开 sql
,但对于大多数程序员和项目来说,sql
未必能够带给他们多大的效益。
所以,ORM
就诞生了,所谓的 ORM
就是对象关系映射,英文:Object Relational Mapping
,简单点说,ORM
根据特有的 POCO 贫血模型
规则生成 sql
语句。大大避免了重复 sql
和 sql
能力参差不齐等问题。(当然 ORM
作者 sql
能力也会影响最终性能)
上面所说的 POCO
贫血模型正是我们本章节的 数据库实体。
简单来说,数据库实体就是数据库表的类表现,通过一定的规则使这个类能够一一对应表结构。通常这样的类也称为:POCO
贫血模型,也就是只有定义,没有行为。
9.3.2 如何定义实体
Furion
框架提供多种定义实体的接口依赖:
IEntity
:实体基接口,是所有实体的基接口IEntityNotKey
:无键实体接口,也就是视图、存储过程、函数依赖接口EntityBase
:实体基抽象类,内置了Id
,TenantId
字段Entity
:实体通用抽象类,继承自EntityBase
,同时内置CreatedTime
,UpdatedTime
字段EntityNotKey
:无键实体抽象类,视图、存储过程、函数依赖抽象类
实体定义位置
Furion
框架中有约定,实体统一定义在 Furion.Core
层。
9.3.2.1 实体继承选用原则
- 如果你不需要
Furion
为实体添加任何内置特性,选用IEntity
,无键实体选用IEntityNotKey
- 如果你只需要
Id
属性,选用EntityBase
- 如果你需要
Furion
为你自动添加常用字段,则选用Entity
- 如果你需要视图、存储过程、函数可以通过
DbSet
操作,则继承EntityNotKey
9.3.2.2 IEntity
示范:
using Furion.DatabaseAccessor;
namespace Furion.Core
{
public class User : IEntity
{
/// <summary>
/// 手工定义 Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
}
}
9.3.2.3 EntityBase
示范:
using Furion.DatabaseAccessor;
namespace Furion.Core
{
public class User : EntityBase
{
// 无需定义 Id 属性
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
}
}
9.3.2.4 Entity
示范:
using Furion.DatabaseAccessor;
namespace Furion.Core
{
public class User : Entity
{
// 无需定义 Id 属性
// 并自动添加 CreatedTime,UpdatedTime 属性
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
}
}
9.3.2.5 EntityNotKey
示范:
using Furion.DatabaseAccessor;
namespace Furion.Core
{
public class UserView : EntityNotKey
{
public UserView() : base("视图名称")
{
}
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
}
}
特别注意
在 Furion
框架中,数据库实体必须直接或间接继承 IEntity
才能进行仓储等操作。
9.3.3 自定义公共实体
在实际项目开发中,我们通常每个应用的数据库表都有一些公共的类,比如创建人,创建时间等,这个时候我们就需要自定义公共实体类了。
在 Furion
框架中,创建公共实体类需要满足以下条件:
- 公共实体类必须是公开且是抽象类
- 公共实体类必须含有无参构造函数
- 公共实体类必须提供数据库定位器的支持
如:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Your.Namespace
{
public abstract class CommonEntity : CommonEntity<int, MasterDbContextLocator>
{
}
public abstract class CommonEntity<TKey> : CommonEntity<TKey, MasterDbContextLocator>
{
}
public abstract class CommonEntity<TKey, TDbContextLocator1> : PrivateCommonEntity<TKey>
where TDbContextLocator1 : class, IDbContextLocator
{
}
public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2> : PrivateCommonEntity<TKey>
where TDbContextLocator1 : class, IDbContextLocator
where TDbContextLocator2 : class, IDbContextLocator
{
}
public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3> : PrivateCommonEntity<TKey>
where TDbContextLocator1 : class, IDbContextLocator
where TDbContextLocator2 : class, IDbContextLocator
where TDbContextLocator3 : class, IDbContextLocator
{
}
public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4> : PrivateCommonEntity<TKey>
where TDbContextLocator1 : class, IDbContextLocator
where TDbContextLocator2 : class, IDbContextLocator
where TDbContextLocator3 : class, IDbContextLocator
where TDbContextLocator4 : class, IDbContextLocator
{
}
public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4, TDbContextLocator5> : PrivateCommonEntity<TKey>
where TDbContextLocator1 : class, IDbContextLocator
where TDbContextLocator2 : class, IDbContextLocator
where TDbContextLocator3 : class, IDbContextLocator
where TDbContextLocator4 : class, IDbContextLocator
where TDbContextLocator5 : class, IDbContextLocator
{
}
public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4, TDbContextLocator5, TDbContextLocator6> : PrivateCommonEntity<TKey>
where TDbContextLocator1 : class, IDbContextLocator
where TDbContextLocator2 : class, IDbContextLocator
where TDbContextLocator3 : class, IDbContextLocator
where TDbContextLocator4 : class, IDbContextLocator
where TDbContextLocator5 : class, IDbContextLocator
where TDbContextLocator6 : class, IDbContextLocator
{
}
public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4, TDbContextLocator5, TDbContextLocator6, TDbContextLocator7> : PrivateCommonEntity<TKey>
where TDbContextLocator1 : class, IDbContextLocator
where TDbContextLocator2 : class, IDbContextLocator
where TDbContextLocator3 : class, IDbContextLocator
where TDbContextLocator4 : class, IDbContextLocator
where TDbContextLocator5 : class, IDbContextLocator
where TDbContextLocator6 : class, IDbContextLocator
where TDbContextLocator7 : class, IDbContextLocator
{
}
public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4, TDbContextLocator5, TDbContextLocator6, TDbContextLocator7, TDbContextLocator8> : PrivateCommonEntity<TKey>
where TDbContextLocator1 : class, IDbContextLocator
where TDbContextLocator2 : class, IDbContextLocator
where TDbContextLocator3 : class, IDbContextLocator
where TDbContextLocator4 : class, IDbContextLocator
where TDbContextLocator5 : class, IDbContextLocator
where TDbContextLocator6 : class, IDbContextLocator
where TDbContextLocator7 : class, IDbContextLocator
where TDbContextLocator8 : class, IDbContextLocator
{
}
public abstract class PrivateCommonEntity<TKey> : IPrivateEntity
{
// 注意是在这里定义你的公共实体
public virtual TKey Id { get; set; }
public virtual DateTime CreatedTime { get; set; }
// 更多属性定义
}
}
特别说明
通过上面的格式定义可以完美的支持多数据库操作,建议采用这种格式,而且所有的公共属性都应该定义在 PrivateXXXX
私
有类中。
9.3.4 数据库实体配置
在过去的 EF Core
项目开发中,数据库实体配置需要在 DbContext
的 OnModelCreating
中配置。Furion
为了简化配置和提高开发效率,抽象出了 IEntityTypeBuilder<TEntity>
接口。
通过 IEntityTypeBuilder<TEntity>
接口,我们无需在 DbContext
的 OnModelCreating
中配置,可在任意地方配置。
9.3.4.1 在数据库实体中配置
using Furion.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
namespace Furion.Core
{
public class User : Entity, IEntityTypeBuilder<User>
{
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
// 配置数据库实体
public void Configure(EntityTypeBuilder<User> entityBuilder, DbContext dbContext, Type dbContextLocator)
{
entityBuilder.HasKey(u => u.Id);
entityBuilder.HasIndex(u => u.Name);
}
}
}
9.3.4.2 在任何实例类中配置
using Furion.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
namespace Furion.Core
{
public class SomeClass : IEntityTypeBuilder<User>
{
public void Configure(EntityTypeBuilder<User> entityBuilder, DbContext dbContext, Type dbContextLocator)
{
entityBuilder.HasKey(u => u.Id);
entityBuilder.HasIndex(u => u.Name);
}
}
}
如上面例子,通过 SomeClass
配置 User
数据库实体。
特别注意
SomeClass
必须声明为public
,否则无法自动注册。
更多知识
如需了解实体配置支持哪些配置可查阅 【EFCore - 创建模型】 章节。
9.3.5 数据库实体配置说明
Furion
框架会自动扫描所有继承 IEntity
接口的类进行 DbSet<TEntity>
注册,也就是实现自动配置 DbContext
的 OnModelCreating
。
如果需要跳过自动注册,只需要贴 [Manual]
或 [SuppressSniffer]
特性即可。一旦贴了此特性,那么就需要手动配置 DbContext
的 OnModelCreating
9.3.6 配置列名及列类型
有时候我们需要手动设置列名或列类型,比如 decimal(18,2)
,这时候只需要在属性上面贴 [Column("列名", TypeName="decimal(18,2)")]
即可。
9.3.7 反馈与建议
与我们交流
给 Furion 提 Issue。