在 ASP.NET 样板中的子 table 中插入多个数据
Insert multiple data in child table in ASP.NET Boilerplate
背景
我有两个 tables Actions
和 ActionsDetails
.
操作:
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 已经是一个存储库)除此之外我看不出代码有任何问题(不确定你的存储库方法是什么正在做)你可以尝试的是
删除
public 动作()
{
ActionsDetails = new List();
}
来自您的操作实体,因为不需要它
是你的
long actionid= await _actionmanager.CreateActionsAsync(actions);
保存到数据库?如果是这样,那会损失很多性能,因为您可以 return 实体(不保存)然后将其应用于您的 Actionsdetails:
actionsdetails.Action = action;
你的await _actionmanager.CreateActionsDetailsAsync(actionsdetails);
也已经保存到数据库了吗?我不明白为什么你不应该在 foreach 中创建对象,然后将它们作为列表保存到数据库中(也提供了一种更好的调试方法,通常,只尝试保存到数据库一次)
希望这能以任何方式帮助您找到您的问题,因为如果没有完整的方法很难找到它(您的存储库,再次 ef-core 已经是一个存储库)
所以只需尝试执行以下操作:创建动作,使用设置为动作的引用创建动作详细信息,然后保存所有内容
您不必分别插入 Action
和 ActionDetails
。 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);
}
背景
我有两个 tables Actions
和 ActionsDetails
.
操作:
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 已经是一个存储库)除此之外我看不出代码有任何问题(不确定你的存储库方法是什么正在做)你可以尝试的是
删除
public 动作() { ActionsDetails = new List(); }
来自您的操作实体,因为不需要它
是你的
long actionid= await _actionmanager.CreateActionsAsync(actions);
保存到数据库?如果是这样,那会损失很多性能,因为您可以 return 实体(不保存)然后将其应用于您的 Actionsdetails:
actionsdetails.Action = action;
你的await _actionmanager.CreateActionsDetailsAsync(actionsdetails);
也已经保存到数据库了吗?我不明白为什么你不应该在 foreach 中创建对象,然后将它们作为列表保存到数据库中(也提供了一种更好的调试方法,通常,只尝试保存到数据库一次)
希望这能以任何方式帮助您找到您的问题,因为如果没有完整的方法很难找到它(您的存储库,再次 ef-core 已经是一个存储库)
所以只需尝试执行以下操作:创建动作,使用设置为动作的引用创建动作详细信息,然后保存所有内容
您不必分别插入 Action
和 ActionDetails
。 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);
}