在 ASP.NET 样板中的子 table 中插入多个数据

Insert multiple data in child table in ASP.NET Boilerplate

背景

我有两个 tables ActionsActionsDetails.

操作:

CREATE TABLE [dbo].[Actions]
(
    [ActionId] [BIGINT] IDENTITY(1,1) NOT NULL,
    [DeviceId] [NVARCHAR](125) NOT NULL,
    [TenantId] [INT] NOT NULL,

    CONSTRAINT [PK_ActionId]
        PRIMARY KEY CLUSTERED ([ActionId] ASC)
        WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

操作详情:

CREATE TABLE [dbo].[ActionsDetails]
(
    [ActionsDetailsId] [BIGINT] IDENTITY(1,1) NOT NULL,
    [ActionId] [BIGINT] NOT NULL,
    [ActionName] [NVARCHAR](125) NOT NULL,
    [Description] [NVARCHAR](800) NOT NULL,

    CONSTRAINT [PK_ActionsDetailsId]
        PRIMARY KEY CLUSTERED ([ActionsDetailsId] ASC)
        WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[ActionsDetails] WITH CHECK
    ADD CONSTRAINT [FK_ActionsDetails_ActionsId]
        FOREIGN KEY([ActionId]) REFERENCES [dbo].[Actions] ([ActionId])
GO

ALTER TABLE [dbo].[ActionsDetails] CHECK CONSTRAINT [FK_ActionsDetails_ActionsId]
GO

我想做的是在Actions中插入一行数据,然后为插入的记录捕获ActionId,然后在ActionId中插入多个值ActionsDetails table。使用 EF Core 插入父子 table 的非常简单的情况,只是那个子 table 可以有父行的多个记录。

这是 ActionsAppService class:

中的一段代码
public async Task CreateActions(CreateActionDto input)
{
    Actions actions = new Actions();
    ActionsDetails actionsdetails = new ActionsDetails();

    MapToEntity(input, actions);

    // Insert into Actions and return last generated ActionId

    long actionid = await _actionmanager.CreateActionsAsync(actions);

    // looping here since child table can have multiple record to be
    // inserted

    foreach (var actionname in input.ActionDetails)
    {
        // TODO: This can go to a mapping method as well
        actionsdetails.ActionName = actionname.ActionName;
        actionsdetails.Description = actionname.Description;
        actionsdetails.ActionId = actionid;

        // Inserting details into ActionsDetails table for the
        // corresponding ActionId

        await _actionmanager.CreateActionsDetailsAsync(actionsdetails);
    }
}

输入DTO与实体之间的数据映射:

private void MapToEntity(CreateActionDto actionsdto, Actions actions)
{
    actions.DeviceId = actionsdto.DeviceId;
    actions.TenantId = (int)(AbpSession.TenantId);
}

ActionManager class:

public async Task<long> CreateActionsAsync(Actions input)
{
    return await _actionsRepository.InsertAndGetIdAsync(input);
}

public async Task CreateActionsDetailsAsync(ActionsDetails input)
{
    await _actionsdetailsRepository.InsertAsync(input);
}

问题

我看到的问题是 ActionDetails 列表只包含插入的集合的最后一个值。例如,如果我传递这个值:

{
  "deviceId": "zainnewdevice",
  "actionDetails": [
    {
      "actionId": 0,
      "actionName": "switchtube1",
      "description": "this would swtich on Tube1"
    },
    {
      "actionId": 0,
      "actionName": "switchtube2",
      "description": "This would switch on Tube2"
    }
  ]
}

集合的最后一个值,即"switchtube2""This would switch on Tube2",插入ActionDetails table。

也许我做事的方式全错了,或者可能只是我对 Entity Framework 及其工作原理缺乏了解,但我觉得 actionsdetails 对象需要在 ActionManager 中调用 CreateActionsDetailsAsync 之前添加到上下文中。我不知道该怎么做,因为所有上下文都在 DbContext class.

中设置

此外,我在想也许将 actionsdetails 添加到列表,然后将该列表对象传递给 ActionManager class 中的 CreateActionsDetailsAsync 会有所帮助。也许我可以在那里遍历列表对象并在循环中调用 await _actionsdetailsRepository.InsertAsync(input),但该策略也失败了。

DTO:

public class CreateActionDto
{
    [Required]
    public string DeviceId { get; set; }

    public ICollection<ActionsDetailsDto> ActionDetails { get; set; }
}

实体:

public class Actions : Entity<long>, IMustHaveTenant
{
    [Column("ActionId")]
    [Key]
    public override long Id { get; set; }

    [Required]
    public string DeviceId { get; set; }

    [Required]
    public int TenantId { get; set; }

    public virtual ICollection<ActionsDetails> ActionsDetails { get; set; }

    public Actions()
    {
        ActionsDetails = new List<ActionsDetails>();
    }
}

public class ActionsDetails : Entity<long>
{
    [ForeignKey("ActionId")]
    public virtual Actions Actions { get; set; }

    [Column("ActionsDetailsId")]
    [Key]
    public override long Id { get; set; }

    [Required]
    public long ActionId { get; set; }

    [Required]
    public string ActionName { get; set; }

    [Required]
    public string Description { get; set; }
}

集合中的所有行都应出现在 ActionsDetails table.

因为您有一个 EF 知道的 Id 的父子关系,所以您不必设置 Id。以下代码应该有效:

using (var context = new YourContext())
{
    var action= new Action();
    MapToEntity(input, action);// I asume your details are also included in the input
    context.Actions.Add(action);
    context.SaveChanges();
}

看到带有 entity framework 的存储库模式有点奇怪(因为 ef-core 已经是一个存储库)除此之外我看不出代码有任何问题(不确定你的存储库方法是什么正在做)你可以尝试的是

  1. 删除

    public 动作() { ActionsDetails = new List(); }

来自您的操作实体,因为不需要它

  1. 是你的

    long actionid= await _actionmanager.CreateActionsAsync(actions);

保存到数据库?如果是这样,那会损失很多性能,因为您可以 return 实体(不保存)然后将其应用于您的 Actionsdetails:

actionsdetails.Action = action;

你的await _actionmanager.CreateActionsDetailsAsync(actionsdetails);也已经保存到数据库了吗?我不明白为什么你不应该在 foreach 中创建对象,然后将它们作为列表保存到数据库中(也提供了一种更好的调试方法,通常,只尝试保存到数据库一次)

希望这能以任何方式帮助您找到您的问题,因为如果没有完整的方法很难找到它(您的存储库,再次 ef-core 已经是一个存储库)

所以只需尝试执行以下操作:创建动作,使用设置为动作的引用创建动作详细信息,然后保存所有内容

您不必分别插入 ActionActionDetails。 EF 将负责插入,只需将完整实体传递给插入方法即可:

public async Task CreateActions(CreateActionDto input)
{
    Actions actions = new Actions();            
    ActionsDetails actionsdetails = new ActionsDetails();

    // Do your mapping
    MapToEntity(input, actions);

    // Add the details
    foreach (var actionname in input.ActionDetails)
    {
        actionsdetails.ActionName = actionname.ActionName;
        actionsdetails.Description = actionname.Description;

        // Add it to the collection
        actions.ActionsDetails.Add(actionsdetails);
    }

    // Save
    await _actionmanager.CreateActionsAsync(actions);        
}

避免由两个单独的插入语句引起的问题的最佳方法是将所有内容包装在一个事务中,因为您使用的是 EF,这是其工作方式的默认机制。

显式映射

The issue what I am seeing is that ActionDetails list only last value of collection which is inserted.

这是因为您将每个 DTO 映射到同一个实体。

// ActionsDetails actionsdetails = new ActionsDetails(); // Move this...

foreach (var actionname in input.ActionDetails)
{
    ActionsDetails actionsdetails = new ActionsDetails(); // ...here

    actionsdetails.ActionName = actionname.ActionName;
    actionsdetails.Description = actionname.Description;
    actionsdetails.ActionId = actionid;

    await _actionmanager.CreateActionsDetailsAsync(actionsdetails);
}

相关:

AutoMapper

另一种方法是利用 ABP 的 Object To Object Mapping

配置:

[AutoMapTo(typeof(Actions))] // Add this
public class CreateActionDto
{
    [Required]
    public string DeviceId { get; set; }       

 // public ICollection<ActionsDetailsDto> ActionDetails { get; set; } // Rename this
    public ICollection<ActionsDetailsDto> ActionsDetails { get; set; }
}

[AutoMapTo(typeof(ActionsDetails))] // Add this
public class ActionsDetailsDto
{
    // ...
}

用法:

public async Task CreateActions(CreateActionDto input)
{
    Actions actions = new Actions();

    MapToEntity(input, actions);

    long actionId = await _actionmanager.CreateActionsAsync(actions);
}

private void MapToEntity(CreateActionDto actionsdto, Actions actions)
{
    _objectMapper.Map(actionsdto, actions);
}