ABP框架入门踩坑-添加实体

添加实体

ABP踩坑记录-目录html

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

类图

审计属性

具体什么是审计属性我这里就再也不介绍了,你们能够参考官方文档github

这里我是经过继承定义好的基类来得到相应的审计属性,你们若是有需求的话,也能够本身经过接口定义。app

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

FullAuditedEntity实质为FullAuditedEntity<int>ide

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

一对多关系

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

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

QincaiDbContext中,咱们重载OnModelCreating方法:code

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是如何调用私有构造的过程,但愿知道的大佬能指点一下。

相关文章
相关标签/搜索