所以我一直在阅读,看来大多数问题是你必须脱离上下文,解决方案通常似乎是创建一些外键。但是,我的问题是在一个实体上而不是与该实体的关系,而且我可以根据需要考虑外键。我认为这与使用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中实现。耶。
答案 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();
}
(此代码未经过测试或编译)