我需要使用Azure App Service建立1:n关系。实体I followed this tutorial。
我的客户端实体(与教程相同,但我添加了一个构造函数):
public abstract class Entity {
// defines id, version, createdAt, updatedAt, deleted
}
public class Animal : Entity {
public string Name { get; set; }
}
public class Zoo : Entity {
public Zoo() { Animals = new List<Animal>(); }
public string Name { get; set; }
public List<Animal> Animals { get; set; }
}
我的服务器实体(与教程相同,但我添加了一个构造函数):
public class Animal : EntityData {
public string Name { get; set; }
}
public class Zoo : EntityData {
public Zoo() { Animals = new List<Animal>(); }
public string Name { get; set; }
public virtual ICollection<Animal> Animals { get; set; }
}
ZooController
表控制器(库存脚手架代码,但我添加了属性):
[HttpPatch]
public Task<Zoo> PatchZoo(string id, Delta<Zoo> patch) {
return UpdateAsync(id, patch);
}
[HttpPost]
public async Task<IHttpActionResult> PostZoo(Zoo item) {
Zoo current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
Animal
有一个类似的表控制器。
现在出现问题。我从客户端执行插入和更新:
// create animal (child)
var animal = new Animal() { Name = "Dumbo" };
await animalTable.InsertAsync(animal);
// create zoo (parent)
var zoo = new Zoo() { Name = "Tokyo National Zoo" };
await zooTable.InsertAsync(zoo);
// add animal to zoo
zoo.Animals.Add(animal);
await zooTable.UpdateAsync(zoo);
// push
await zooTable.MobileServiceClient.SyncContext.PushAsync();
这会抛出异常。在服务器上,它会抛出StatusCode: 409, ReasonPhrase: 'Conflict'
,并在客户端上抛出"The operation failed due to a conflict: 'Violation of PRIMARY KEY constraint 'PK_dbo.Animals'. Cannot insert duplicate key in object 'dbo.Animals'
。基本上,服务器说我试图两次插入相同的Animal
记录。
我做错了什么?
答案 0 :(得分:1)
请尝试如下所示。
// create animal (child)
var animal = new Animal() { Name = "Dumbo" };
// create zoo (parent)
var zoo = new Zoo() { Name = "Tokyo National Zoo" };
zoo.Animals.Add(animal);
await zooTable.InsertAsync(zoo);
注意:由于FK关系,上面的代码也会自动将记录插入animalTable
。你不需要手动完成它。
答案 1 :(得分:1)
我很确定你遇到了Entity Framework“detatched entities”问题。参见例如Many to Many Relationships not saving。问题是实体框架没有将子项加载到其上下文中,因此它认为它需要插入子项以及父项。 (实体框架中已经存在长期的功能请求来解决此问题,但该功能从未添加过。)
在服务器上,您需要检查引用的子实体是否已存在于数据库中,如果存在,只需添加对现有项的引用,而不是插入。
答案 2 :(得分:1)
对于任何挣扎于此的人。这是我基于@ lindydonna的反馈的解决方案。
[HttpPost]
public async Task<IHttpActionResult> PostZoo(Zoo item) {
// replace child entities with those from the context (so they are tracked)
if (item.Animals.Any()) {
// find animals which are already in the db
var ids = item.Animals.Select(x => x.Id);
var oldItems = context.Animals.Where(x => ids.Contains(x.Id)).ToList();
var newItems = item.Animals.Where(x => !oldItems.Select(y => y.Id).Contains(x.Id));
// add new animals if there are any
if (newItems.Any()) {
foreach (var i in newItems) {
context.Animals.Add(i);
}
await context.SaveChangesAsync();
}
// replace
item.Animals.Clear();
foreach (var i in oldItems) item.Animals.Add(i);
foreach (var i in newItems) item.Animals.Add(i);
}
// now add the Zoo (which contains tracked Animal entities)
Zoo current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
这很有效。虽然它可能不具备表现力。
您可能还应该添加:
modelBuilder.Entity<Zoo>().HasMany(e => e.Animals);
......虽然出于某种原因对我来说没有那么好。
修改强>
更好的方法,它不允许新的子实体与父实体一起发送。如果允许,那么它们将不会有id
,createdAt
等。应该将这些子实体插入到客户端上下文中以填充这些属性。然后,在执行推送时,SDK似乎足够聪明,可以在POST之前发布它们。
[HttpPost]
public async Task<IHttpActionResult> PostZoo(Zoo item) {
// replace child entities with those from the context (so they are tracked)
if ((item.Animals != null) && item.Animals.Any()) {
// find animals which are already in the db
var ids = item.Animals.Select(x => x.Id);
var oldAnimals = _context.Animals.Where(x => ids.Contains(x.Id)).ToList();
var newAnimals = item.Animals.Where(x => !oldAnimals.Select(y => y.Id).Contains(x.Id));
// don't allow new animal entities
if (tagsNew.Any()) {
var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
var body = new JObject(new JProperty("error", "New Animals must be added first"));
response.Content = new StringContent(body.ToString(), Encoding.UTF8, "application/json");
throw new HttpResponseException(response);
}
// replace
item.Animals.Clear();
foreach (var i in oldAnimals) item.Animals.Add(i);
}
// now add the Zoo (which contains tracked Animal entities)
Zoo current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}