如何在Azure App Service中插入具有1:n关系的实体

时间:2016-08-24 13:30:15

标签: c# entity-framework azure azure-mobile-services

我需要使用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记录。

我做错了什么?

3 个答案:

答案 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);

......虽然出于某种原因对我来说没有那么好。

修改
更好的方法,它不允许新的子实体与父实体一起发送。如果允许,那么它们将不会有idcreatedAt等。应该将这些子实体插入到客户端上下文中以填充这些属性。然后,在执行推送时,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);
  }