EntityFramework Core - 复制实体并将其放回数据库

问题描述 投票:0回答:4

是否有复制实体、根据用户输入对其进行一些更改,然后将其重新插入数据库的最佳实践?

其他一些 Stackoverflow 线程提到,即使数据库中存在相同的主键,EF 也会为您处理插入新对象,但我不太确定 EF Core 是如何处理它的。每当我尝试复制对象时,都会收到错误

Cannot insert explicit value for identity column in table when IDENTITY_INSERT is set to OFF

基本上,我只需要一种干净的方法来复制对象,根据用户输入对其进行一些更改,然后将该副本插入回数据库,并让 Id 正确自动递增。是否有最佳实践或简单的方法来执行此操作,而无需手动将属性设置为 null 或空?

编辑:从数据库检索对象的示例代码:

    public Incident GetIncidentByIdForCloning(int id)
    {
        try
        {
            return _context.Incident.Single(i => i.IncidentId == id);
        }
        catch
        {
            return null;
        }
    }

检索对象后的代码(因为某些字段是自动生成的,例如 RowVersion,它是时间戳):

public IActionResult Clone([FromBody]Incident Incident)
    {
        var incidentToCopy = _incidentService.IncidentRepository.GetIncidentByIdForCloning(Incident.IncidentId);
        incidentToCopy.IncidentTrackingRefId = _incidentService.IncidentRepository.GetNextIdForIncidentCategoryAndType(
            Incident.IncidentCategoryLookupTableId, Incident.IncidentTypeLookupTableId).GetValueOrDefault(0);
        incidentToCopy.RowVersion = null;
        incidentToCopy.IncidentId = 0; //This will fail with or without this line, this was more of a test to see if manually setting would default the insert operation, such as creating a brand new object would normally do.
        incidentToCopy.IncidentCategoryLookupTableId = Incident.IncidentCategoryLookupTableId;
        incidentToCopy.IncidentTypeLookupTableId = Incident.IncidentTypeLookupTableId;
        var newIncident = _incidentService.IncidentRepository.CreateIncident(incidentToCopy);
...

我意识到我可以创建一个全新的对象并进行左手复制,但这似乎效率非常低,我想知道 EF Core 是否提供更好的解决方案。

c# asp.net-mvc entity-framework asp.net-core entity-framework-core
4个回答
12
投票

因此,我在创建这个线程之前比我最初偶然发现它时更多地经历了“可能的重复”线程,并且有一个我忽略的不太高投票的解决方案,它本质上只是获取所有值从数据库检索对象时立即执行 - 并且在此过程中不会检索对该对象的引用。我的代码现在看起来像这样:

try
{
    var incidentToCopy = _context.Incident.Single(i => i.IncidentId == id);
    return (Incident) _context.Entry(incidentToCopy).CurrentValues.ToObject();
}

7
投票

在您的

IncidentRepository
类中尝试使用
Incident
获取
AsNoTracking
,并且在添加它时应该将其作为新实体进行跟踪。

public void Clone(int id)
{
    // Prevent tracking changes to the object.
    var incident = _context.AsNoTracking().SingleOrDefault(i => i.Id == id);

    // Setting back to 0 should treat the object Id as unset.
    incident.Id = 0;

    // Add the Incident while it is untracked will treat it as a new entity.
    _context.Incidents.Add(incident);
    _context.SaveChanges();
}

0
投票

我相信这里发生的事情如下:

当您从数据库检索值时,它会存储在类似

的内容中
context.ChangeTracker.Entries<Incident>

这是正在跟踪的事件条目的集合。当您更改已检索的事件对象的 id 属性时,您有点以效率的名义滥用了 ChangeTracker。 ChangeTracker 不相信您已经创建了一个新对象。您也许可以尝试类似在 ChangeTracker 中查找条目并将其状态设置为分离,然后在将 id 设置为 0 后将对象添加回 context.DbSet 但此时您可能已经做得更多了比简单地复制对象复杂。


0
投票

最后,我们采用了基于这个答案的通用方法,这样我们就可以全部使用它。

saveChanges
标志默认为
true
,但如果在本地构建复杂的 ORM 结构,则允许控制其关闭和打开。

public async Task<T> CloneAsync(T source, bool saveChanges = true)
{
    var destination = await _dbContext.Set<T>()
        .AsNoTracking()
        .FirstOrDefaultAsync(r => r.Id == source.Id);

    destination.Id = 0;
    // Additional values you might need to set in your implementation (not required).
    destination.Identifier = Guid.NewGuid();

    // Call the repository AddAsync() method.
    destination = await AddAsync(destination, saveChanges);
    return destination;
}
© www.soinside.com 2019 - 2024. All rights reserved.