添加实体

ABP踩坑记录-目录

这里我以问答模块为例,记录一下我在创建实体类过程中碰到的一些坑。

审计属性

具体什么是审计属性我这里就不再介绍了,大家可以参考官方文档

这里我是通过继承定义好的基类来获得相应的审计属性,大家如果有需求的话,也可以自己通过接口定义。

其中,abp提供的审计基类有两种,一种只包含UserId的FullAuditedEntity<TPrimaryKey>,另一种则是添加了User的导航属性的FullAuditedEntity<TPrimaryKey, TUser>,后一种可方便之后用AutoMapper来获取用户信息。

FullAuditedEntity实质为FullAuditedEntity<int>

这里可能会出现的坑就是一时手误会写成FullAuditedEntity<User>,这样的话它是把User类型实体的主键,算是不容易察觉的坑。

一对多关系

根据约定,在定义好实体间导航关系之后,EF Core会为其自动创建关系。

但在实际开发中,有时我们并不希望将一些导航属性暴露出来,例如:Image类理应包含指向QuestionAnswer的导航属性。为此,我们可以通过隐藏属性(Shadow Properties)来化解这一尴尬。

QincaiDbContext中,我们重载OnModelCreating方法:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Image>(e =>
{
// 添加隐藏属性
e.Property<int>("QuestionId");
// 配置外键
e.HasOne(typeof(Question))
.WithMany(nameof(Question.Images))
.HasForeignKey("QuestionId");
});
}

以上就是完整的步骤,当然有人会觉得奇怪因为完全不做配置也是可以用的,这是EF Core已经根据约定自动为我们创建了隐藏属性:

Shadow properties can be created by convention when a relationship is discovered but no foreign key property is found in the dependent entity class. In this case, a shadow foreign key property will be introduced. The shadow foreign key property will be named <navigation property name><principal key property name> (the navigation on the dependent entity, which points to the principal entity, is used for the naming).

-- From Microsoft Docs

这里EF Core为我们创建的隐藏属性将命名为<导航属性名称><对应主键名称>,即像我们这里有一个导航属性Question,其Question类的主键为Id,那么隐藏属性就是QuestionId

复合主键

在一些特殊情况下,我们所需的主键可能是由多个属性决定的,比如QuestionTag就是以QuestionIdTagName为主键。

这里我们需要通过Fluent API来进行配置,在重载的OnModelCreating方法中添加:

modelBuilder.Entity<QuestionTag>(qt =>
{
qt.HasKey(e => new { e.QuestionId, e.TagName });
});

通过表达式的形式,我们可以很方便的创建新的复合主键。

另外,因为在QuestionTag中的真正主键是QuestionIdTagName,所以我们还需要覆盖掉继承来的Id属性:

public class QuestionTag : Entity<string>
{
/// <summary>
/// 无效Id,实际Id为QuestionId和TagName
/// </summary>
[NotMapped]
public override string Id => $"{QuestionId}-{TagName}"; /// <summary>
/// 问题Id
/// </summary>
public int QuestionId { get; set; } /// <summary>
/// 标签名称
/// </summary>
public string TagName { get; set; } // ...
}

默认值

在官方文档中,使用默认值的方式是在构造函数中赋值,这里我使用的是C# 6.0中的属性初始化语法(Auto-property initializers)。从我目前的结果来说,与预期效果基本一致,而且更易于阅读。

形式如下:

public class Question : FullAuditedAggregateRoot<int, User>, IPassivable
{
/// <summary>
/// 问题状态(默认为true)
/// </summary>
public bool IsActive { get; set; } = true; // ...
}

构造函数

这是个一直被我忽略的地方,在此之前常常使用的是默认空构造函数,但若需要一个有参构造函数,且这个参数并不直接对应某个属性,如:

// 此处仅为举例说明
public class Question
{
public Category Category { get; set; } // ... // 这里构造的参数并不直接对应某个属性
public Question(string categoryName)
{
Category = new Category { Name = categoryName };
}
}

当你添加迁移的时候就会报如下错误:No suitable constructor found for entity type 'Question'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'categoryName' in 'Question(string categoryName)'.

大概就是EF Core不能推断出categoryName是什么。

解决方法很简单,手动添加一个空构造函数即可。

按照常识,我们添加新的构造函数:

public class Question
{
// ... // 空的构造函数
public Question() {}
}

可事实上,我们并不希望有人使用这个空的构造函数,因为它会缺少一些空值检测等判定。

经过查找资料,我在微软的eShopOnWeb示例项目中找到了如下写法:

public class Order : BaseEntity, IAggregateRoot
{
// 注意这里是private
private Order()
{
// required by EF
} // 含参构造函数包括了空值检测
public Order(string buyerId, Address shipToAddress, List<OrderItem> items)
{
Guard.Against.NullOrEmpty(buyerId, nameof(buyerId));
Guard.Against.Null(shipToAddress, nameof(shipToAddress));
Guard.Against.Null(items, nameof(items)); BuyerId = buyerId;
ShipToAddress = shipToAddress;
_orderItems = items;
}
public string BuyerId { get; private set; } public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;
public Address ShipToAddress { get; private set; } private readonly List<OrderItem> _orderItems = new List<OrderItem>();
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly(); // ...
}

回过头,我又去确认了EF Core的文档:

When EF Core creates instances of these types, such as for the results of a query, it will first call the default parameterless constructor and then set each property to the value from the database

...

The constructor can be public, private, or have any other accessibility.

-- From Microsoft Docs

也就是,EF Core在创建实例时,会首先去调用无参构造函数,且无论该构造函数是何访问类型。

那么问题就解决了,我们只需添加私有的无参构造函数即可。

PS:但还是没找到EF Core是如何调用私有构造的过程,希望知道的大佬能指点一下。

ABP框架入门踩坑-添加实体的更多相关文章

  1. ABP框架入门踩坑-配置数据库表前缀

    配置数据库表前缀 ABP踩坑记录-目录 本篇其实和ABP关系并不大,主要是EF Core的一些应用-.-. 起因 支持数据库表前缀应该是很多应用中比较常见的功能,而在ABP中并没直接提供这一功能,所以 ...

  2. ABP框架入门踩坑-配置User Secrets

    配置User Secrets ABP踩坑记录-目录 起因 因为以往习惯在User Secrets中保存连接字符串之类信息,但当我把连接字符串移到secrets.json中后,却发现在迁移过程中会报如下 ...

  3. ABP框架入门踩坑-使用MySQL

    使用MySQL ABP踩坑记录-目录 起因 因为我自用的服务器只是腾讯云1核1G的学生机,不方便装SQL Server,所以转而MySQL. 这里使用的MySQL版本号为 8.0. 解决方案 删除Qi ...

  4. 我的微信小程序入门踩坑之旅

    前言 更好的阅读体验请:我的微信小程序入门踩坑之旅 小程序出来也有一段日子了,刚出来时也留意了一下.不过赶上生病,加上公司里也有别的事,主要是自己犯懒,就一直没做.这星期一,赶紧趁着这股热乎劲,也不是 ...

  5. 基于ASP.NET MVC的ABP框架入门学习教程

    为什么使用ABP 我们近几年陆续开发了一些Web应用和桌面应用,需求或简单或复杂,实现或优雅或丑陋.一个基本的事实是:我们只是积累了一些经验或提高了对,NET的熟悉程度. 随着软件开发经验的不断增加, ...

  6. Farseer.net轻量级开源框架 入门篇:添加数据详解

    导航 目   录:Farseer.net轻量级开源框架 目录 上一篇:Farseer.net轻量级开源框架 入门篇: 分类逻辑层 下一篇:Farseer.net轻量级开源框架 入门篇: 修改数据详解 ...

  7. 小程序框架MpVue踩坑日记(一)

    小程序也做了几个小功能模块了,总觉得需要总结一下,踩坑什么的还是得记录一下啊. 好吧,其实是为了方便回顾 首先,说到小程序框架,大家都知道wepy,不过,我是没用过 美团开发团队到mpvue到是个实在 ...

  8. Go ORM框架 - GORM 踩坑指南

    今天聊聊目前业界使用比较多的 ORM 框架:GORM.GORM 相关的文档原作者已经写得非常的详细,具体可以看这里,这一篇主要做一些 GORM 使用过程中关键功能的介绍,GORM 约定的一些配置信息说 ...

  9. ABP框架入门

    技术要求 在开始使用 ABP 框架之前,您需要在计算机上安装一些工具. IDE/编辑器 本书假设您使用的是Visual Studio 2022(支持 .NET 6.0 的 v10.0)或更高版本.如果 ...

随机推荐

  1. .net为图片添加水印(转) jpg png和gif格式

    .net为图片添加水印(转) jpg png和gif格式 .net为图片添加水印(转) jpg png和gif格式,转自csdn的hyde82,现在跟大家一起来分享下: 利 用.net中System. ...

  2. swift 移除所有子控件

    /// 移除所有子控件 func removeAllSubViews(){ if self.view.subviews.count>0{ self.view.subviews.forEach({ ...

  3. 2018年UI设计趋势概览

    ​互联网产品的用户界面设计趋势是根据用户的不同需求而不断变化的.在仔细分析了过去几年用户界面设计的趋势和创新之后,我们可以发现其背后的一些规律,2018年UI界面设计的趋势如下. 渐变色 在过去的几年 ...

  4. 用AI制作炫酷效果

    PART1:制作第一个效果 步骤一:新建一个800*600的画布. 骤二:从工具栏选“矩形工具”,创建一个800*600的矩形.白色的是画布,浅红色(我的AI之前保留的填充颜色,每个人都不一样)的是你 ...

  5. CreateThread

    CreateThread(NULL,0,ReportResultThread,this,0,&g_dwThreadId) 2. 参数说明: 第一个参数 lpThreadAttributes 表 ...

  6. Vue.js2 + Laravel5 采用 CORS 方式解决 AJAX 跨域的问题

    一.建立中间件 php artisan make:middleware CorsAjax 二.编写中间件 CorsAjax <?phpnamespace App\Http\Middleware; ...

  7. 何时开始phonics学习及配套阅读训练zz

    引子:自从11月份俱乐部第一批孩子开始英文阅读,到现在三.四个月的时间过去了.很多孩子从不知道怎么读绘本甚至排斥英语,到现在能很投入地看原版书, 有些甚至主动地去寻找拼读规律.我家小宝目前也从前期的阅 ...

  8. 2018.06.30 BZOJ 3932: [CQOI2015]任务查询系统(主席树)

    3932: [CQOI2015]任务查询系统 Time Limit: 20 Sec Memory Limit: 512 MB Description 最近实验室正在为其管理的超级计算机编制一套任务管理 ...

  9. spring boot使用java读取配置文件,DateSource测试,BomCP测试,AnnotationConfigApplicationContext的DataSource注入

    一.配置注解读取配置文件         (1)@PropertySource可以指定读取的配置文件,通过@Value注解获取值   实例:           @PropertySource(val ...

  10. “无后端”的web应用开发模式

    最近看到前端趋势2013大会上的一篇文章,题目是<各位快看,不用后端>,觉得有点意思,恰好近期的一次讨论及半年前的一次开发实践也涉及到这种模式,简单谈谈我的想法. 不得不说,文章的题目确实 ...