2016-08-24 11 views
2

Azure App Serviceを使用して1:nの関係が必要です。エンティティの場合はI followed this tutorialです。Azure Appサービスで1:nの関係を持つエンティティを挿入するには

私のクライアントエンティティ(チュートリアルと同じで、私はコンストラクタを追加した以外):

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レコードを2度挿入しようとしていると言っています。

私は間違っていますか?

+0

私はいくつかのデバッグを行い、 'Animal'コントローラのPOSTメソッドが呼び出され、その後は' Zoo'コントローラのPOSTに気付きました。したがって、2回目のPOST操作では、2重挿入が行われます。でも、どうやって止めるの? –

+0

animalTable.InsertAsync()を削除しようとしましたか?ちょうど私が持っていた考え。 – juunas

答えて

1

エンティティフレームワーク「デタッチされたエンティティ」に問題があると思います。例えば、 Many to Many Relationships not saving。問題は、Entity Frameworkが子アイテムをそのコンテキストにロードしていないため、子アイテムと親を挿入する必要があると考えていることです。 (これを解決するために、Entity Frameworkに長年にわたる機能要求がありましたが、この機能は一度も追加されていません)。

サーバー上では、参照された子エンティティがデータベースに既に存在するかどうかを確認する必要があります。挿入するのではなく、既存のアイテムへの参照を追加するだけです。

+0

AHA!はい、あなたは正しいです。このチュートリアルでは、テーブルコントローラーには魔法がいくつかあると思っていましたが、実際には古いEFです。 SDKの魔法は、サーバーではなくクライアント上でうまくいくようです。 :-) –

+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にレコードを挿入します。手動で行う必要はありません。

+0

はい通常のEFを使用していた場合、私はそれを行いますが、ここではエンティティをクライアントのコンテキストに挿入する必要があると考えています。そうでない場合、 'id'、' createdAt'などのプロパティはすべてnullです。私はPOSTメソッドでサーバー上のそれらを設定することができますが、クライアントはそれらの値を知りませんでした... –

+0

クライアントシナリオの回避策を見つける必要があります。しかし、あなたはサーバーでやったことをすることはできません。それがエラーを示す理由です。あなたがFKの関係を持っているなら、上記のところでそれをしなければなりません。それ以外の方法はありません。 – Sampath

+0

さて、別の時間にエンティティを追加するとどうなりますか?このようにして、原子操作として実行する必要があります。後でそれを追加したい場合、私の例のように、失敗するでしょう... –

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

...私のために、何らかの理由でそれは同様に動作しなくてけれども。新しい子エンティティは親エンティティと一緒に送信することはできません

EDIT
良い方法、。許可されている場合は、idcreatedAtなどはありません。これらの子エンティティは、クライアント側のコンテキストに挿入して、それらのプロパティを設定する必要があります。その後、プッシュを実行すると、SDKは親をポストする前に最初にポストするほどスマートに見えます。

[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); 
    } 
+0

これはおそらくコンテキストの 'Attach()'メソッドを使ってクリーンアップすることができます。 –

関連する問題