实体框架核心保存单个实体而不是更新

时间:2017-03-01 09:09:02

标签: c# sql entity-framework

所以我一直在阅读,看来大多数问题是你必须脱离上下文,解决方案通常似乎是创建一些外键。但是,我的问题是在一个实体上而不是与该实体的关系,而且我可以根据需要考虑外键。我认为这与使用GUID作为主键有关。

我的犯罪实体是:

public class Customer
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedAt { get; set; }
    public virtual ICollection<Organization> Organizations { get; set; }
}

使用流畅的API配置如下:

    public override void Configure(EntityTypeBuilder<Customer> entity)
    {
        entity.HasKey(customer => customer.Id); // Primary key
        entity.Property(customer => customer.Name).IsRequired().HasMaxLength(255);
        entity.Property(customer => customer.CreatedAt).IsRequired();

        entity.HasMany(customer => customer.Organizations)
            .WithOne(organization => organization.Customer)
            .HasForeignKey(organization => organization.CustomerId);
    }

它生成以下SQL:

CREATE TABLE [Customer] (
    [Id] uniqueidentifier NOT NULL,
    [CreatedAt] datetime2 NOT NULL,
    [Name] nvarchar(255) NOT NULL,
    CONSTRAINT [PK_Customer] PRIMARY KEY ([Id])
);

我用这段代码保存:

public class AddCustomer : ICommand
{
    public Guid CustomerId { get; set; }
    public string Name { get; set; }
}

[Transactional]
internal sealed class HandleAddCustomer : IHandleCommand<AddCustomer>
{
    private readonly IEntityWriter<Customer> _writer;
    public HandleAddCustomer(IEntityWriter<Customer> writer)
    {
        _writer = writer;
    }

    public Task HandleAsync(AddCustomer command)
    {
        var customer = new Customer
        {
            Id = command.CustomerId,
            Name = command.Name,
            CreatedAt = DateTime.Now
        };

        _writer.Save(customer);

        return Task.CompletedTask;
    }
}

entityWriter是我们为更加统一地处理数据库交互而创建的系统,它是一个支持Entity Framework的包装器,并使用SimpleInjector和DI包装它。

参数命令从API端点填充:

    [HttpPost("AddCustomer")]
    public async Task<IActionResult> AddCustomer(AddCustomer customer)
    {
        await _commands.ExecuteAsync(customer);

        return Ok("Customer Saved");
    }

使用以下有效负载:

{
    "CustomerId": "692da47a-3886-42bb-a1ac-d853d315109a",
    "Name": "Test Customer"
}

在我发送相同的ID之前,哪一切都很好用。

{
    "CustomerId": "692da47a-3886-42bb-a1ac-d853d315109a",
    "Name": "New name"
}

然后我得到一个外键违规,因为它试图将它作为新行插入,这当然会导致重复的主键。所以,我认为,我的罪魁祸首是对Id的处理。我见过人们建议

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]

但是我的主键不是由数据库生成的(来自现有的不相交系统),所以我只是希望它遵守主键并看到它是同一个实体并且必须更新它。我是否需要先使用一些

查询数据库

if(_queries<Customer>().Where(c => c.Id == command.Id).FirstOrDefault() != null) { ...//The customer already exists, update the name in the entity and save it again

每次执行命令时手动检查现有实体只会感觉有点意外 - 但是虽然我对EF的体验有限(尤其是EF Core),但可能只是我期待有点过多从它。

编辑:我一直在挖掘过去员工编写的一些代码,看来EF集成(特别是保存)只设置了实体的实际状态:

 public void Save(TEntity entity)
        {
            var context = _contextProvider();
            var entry = context.Entry(entity);

            // If it is not tracked by the context, add it to the context
            if (entry.State == EntityState.Detached)
            {
                // This also sets the entity state to added.
                context.Set<TEntity>().Add(entity);
            }
            else
            {
                // Tells the context that the entity should be updated during saving changes
                entry.State = EntityState.Modified;
            }
        }

然后使用SaveChangesAsync()处理工作单元:

public async Task ExecuteAsync()
        {
            await _contextProvider().SaveChangesAsync().ConfigureAwait(false);
        }

但是关于Frederik的建议,我可能需要更新库以使用AddOrUpdate()偷偷摸摸的编辑,这似乎还没有在EF Core中实现。耶。

3 个答案:

答案 0 :(得分:0)

是的,如果ID已经存在,您需要先查询数据库。

答案 1 :(得分:0)

有一种名为AddOrUpdate()

的方法
db.Customer.AddOrUpdate(customer); 

你可以使用。这是一种扩展方法,您可以在此处找到更多信息: https://msdn.microsoft.com/en-us/library/hh846520(v=vs.103).aspx

答案 2 :(得分:0)

您可能需要更改以下内容的执行:_writer.Save(customer); 看起来应该是这样的:

public void Save(Customer customer)
{
   var dalCustomer = context.Customers.FirstOrDefault(c => c.Id = entity.Id);
   if (dalCustomer == null)
   {
      dalCustomer = new Customer();
      context.Customers.Add(dalCustomer);
   }

   dalCustomer.Name = customer.Name;
   dalCustomer.CreatedAt = DateTime.Now;

   context.SaveChanges();
}

(此代码未经过测试或编译)