EntityFrameworkCoreで少し複雑なエンティティを更新する

c# entity-framework-core

質問

私はEntityFrameworkCoreを試しています 。私はドキュメントを見ましたが、別のエンティティに関連する複雑なエンティティを簡単に更新する方法を見つけることができませんでした。

ここに簡単な例があります。私は2つのクラスを持っています - 会社と従業員。

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Company Company { get; set; }
}

Companyはシンプルなクラスであり、従業員はCompanyクラスを参照するプロパティを含んでいるため、わずかに複雑です。

更新されたエンティティを取り込むアクションメソッドでは、まずidで既存のエンティティを検索し、SaveChangesを呼び出す前に各プロパティを設定します。

[HttpPut]
public IActionResult Update(int id, [FromBody]Employee updatedEmployee)
{
    if (updatedEmployee == null || updatedEmployee.Id != id)
        return BadRequest();

    var existingEmployee = _dbContext.Employees
                             .FirstOrDefault(m => m.Id == id);
    if (existingEmployee == null)
        return NotFound();

    existingEmployee.Name = updatedEmployee.Name;

    if (updatedEmployee.Company == null)
        existingEmployee.Company = null; //as this is not a PATCH            
    else
    {
        var existingCompany = _dbContext.Companies.FirstOrDefault(m =>
                                m.Id == updatedEmployee.Company.Id);
        existingEmployee.Company = existingCompany;
    }

    _dbContext.SaveChanges();

    return NoContent();
}

このサンプルデータを使用して、Employees / 3でHTTP PUT呼び出しを行います。

{
    "id": 3,
    "name": "Road Runner",
    "company":
    {
        "id": 1
    }
}

そして、それは動作します。

しかし、私はこのようにそれぞれのプロパティを設定することを避けることを願っています。既存のエンティティを新しいものに置き換えることができる方法はありますか?このような単純な呼び出しですか?

_dbContext.Entry(existingEmployee).Context.Update(updatedEmployee);

私はこれを試すと、このエラーが発生します:

System.InvalidOperationException:エンティティ型 'Employee'のインスタンスは、同じキーを持つこの型の別のインスタンスが既に追跡されているため、追跡できません。新しいエンティティを追加する場合、ほとんどのキータイプで、キーが設定されていない場合(キープロパティにデフォルト値が割り当てられている場合)に固有のキー値が作成されます。新しいエンティティのキ​​ー値を明示的に設定する場合は、既存のエンティティや他の新しいエンティティ用に生成された一時的な値と衝突しないようにしてください。既存のエンティティをアタッチする場合は、指定されたキー値を持つエンティティインスタンスが1つだけコンテキストにアタッチされていることを確認します。

私はそれを追跡せずに既存のエンティティを取得する場合、私はこのエラーを回避することができます。

var existingEmployee = _dbContext.Employees.AsNoTracking()
                         .FirstOrDefault(m => m.Id == id);

これは単純なエンティティでも機能しますが、このエンティティが他のエンティティへの参照を持つ場合、現在のエンティティ更新のスコープ内にない参照された各エンティティに対してUPDATE文が発生します。 Updateメソッドのドキュメントには、次のように書かれています。

// Microsoft.EntityFrameworkCore.DbContext.SaveChangesが呼び出されたときにデータベース内で更新されるように、Microsoft.EntityFrameworkCore.EntityState.Modified状態で、指定されたエンティティおよびその他の到達可能なエンティティを追跡します。

この場合、従業員エンティティを更新すると、私の会社エンティティは

{
  "id": 1,
  "name": "Acme Products"
}

{
  "id": 1,
  "name": null
}

関連するエンティティの更新を避けるにはどうすればよいですか?


更新

コメントと受け入れられた回答の入力に基づいて、これは私が終わったものです:

会社のナビゲーションプロパティを持つことに加えて、 CompanyIdのプロパティを含むようにEmployeeクラスを更新しました。私は従業員に会社のIDが含まれている2つの方法があるので、これを行うのは嫌いですが、これはEFで最も効果的なものです。

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CompanyId { get; set; }
    public Company Company { get; set; }
}

そして今、私のUpdateようになります:

[HttpPut]
public IActionResult Update(int id, [FromBody]Employee updatedEmployee)
{
    if (updatedEmployee == null || updatedEmployee.Id != id)
        return BadRequest();

    var existingEmployeeCount = _dbContext.Employees.Count(m => m.Id == id);
    if (existingEmployeeCount != 1)
        return NotFound();

    _dbContext.Update(updatedEmployee);

    _dbContext.SaveChanges();

    return NoContent();
}

受け入れられた回答

Updateドキュメントに基づいて

Ref: 更新

SaveChanges()が呼び出されたときにデータベース内で更新されるように、Modified状態の特定のエンティティの追跡を開始します。エンティティのすべてのプロパティは変更済みとしてマークされます。一部のプロパティのみを変更済みとしてマークするには、Attach(Object)を使用してエンティティをUnchanged状態で追跡し、返されたEntityEntryを使用して目的のプロパティを変更済みとしてマークします。コンテキストによってまだトラッキングされていない到達可能なエンティティを見つけるために、ナビゲーションプロパティの再帰的な検索が実行されます。これらのエンティティは、コンテキストによっても追跡され始めます。到達可能なエンティティのプライマリキー値が設定されている場合、そのキーはModified状態で追跡されます。プライマリキーの値が設定されていない場合、追加された状態で追跡されます。主キープロパティがプロパティタイプのCLRデフォルト以外に設定されている場合、エンティティは主キー値が設定されているとみなされます。

あなたのケースでは、 updatedEmployee.Companyナビゲーションプロパティがあります。したがって、 context.Update(updatedEmployee)を呼び出すと、すべてのナビゲーションを再帰的に検索します。 updatedEmployee.Company表されるupdatedEmployee.CompanyはPKプロパティセットを持つため、EFは変更されたエンティティとして追加します。ここで注目すべき点は、 Companyエンティティには、他にはないPKプロパティのみが入っていることです。 (つまり、Nameはnullです)。したがって、EFはid = 1のCompanyがName = nullに変更され、適切な更新ステートメントを発行すると判断します。

自分でナビゲーションを更新するときは、実際にサーバーから(すべてのプロパティが設定されている)会社を探して、それをexistingEmployee.Company追加します。したがって、Companyに変更はないため、 existingEmployee変更のみです。

要約すると、ナビゲーションプロパティが入力されている状態でUpdateを使用する場合は、ナビゲーションで表されるエンティティにPKプロパティ値だけでなくすべてのデータが含まれていることを確認する必要があります。

Company.Idだけがあり、 updatedEmployee満たされた他のプロパティを取得できない場合は、リレーションシップフィックスアップのためにナビゲーション(代わりに完全なエンティティを必要とする)の代わりに外部キープロパティ(PK(またはAK)値が必要) 。

問題のコメントによると、 EmployeeクラスにCompanyIdプロパティを追加する必要があります。 Employeeは、ナビゲーションが存在するため、まだ非ポコ(複雑な)エンティティです。

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? CompanyId {get; set; }
    public Company Company { get; set; }
}

更新アクション中に、次の構造体のupdatedEmployeeを渡します。 (これは同じ量のデータであり、構造化されたビットだけが異なっていることを参照してください)。

{
    "Id": 3,
    "Name": "Road Runner",
    "CompanyId": 1,
    "Company": null //optional
}

あなたのアクションでは、 context.Update(updatedEmployee)を呼び出すだけで、従業員を節約できますが、会社を変更することはありません。

Employeeは複合クラスであるため、引き続きナビゲーションを使用できます。従業員を熱心な読み込み( Include )で読み込んだ場合、 employee.Companyは関連するエンティティ値を持ちます。

ノート:

  • _dbContext.Entry(<any entity>).Contextあなたが与える_dbContextだけなので、あなただけ書くことができ_dbContext.Update(updatedEmployee)直接。
  • AsNoTrackingで把握したように、コンテキストでエンティティを読み込むと、 updatedEmployee Updateを呼び出すことはできません。その時点で、EFによって追跡されるエンティティに変更を適用する必要があるため、各プロパティを手動で変更する必要があります。 Update関数はEFの通知を与え、これは変更されたエンティティであり、それを追跡し始め、 SaveChanges必要なことを行います。このため、 AsNoTrackingを使用するのが適切です。さらに、サーバーからエンティティを取得する目的が従業員のみの存在を確認する場合は、 _dbContext.Employees.Count(m => m.Id == id);問い合せることができます_dbContext.Employees.Count(m => m.Id == id);戻り値を1と比較します。これにより、サーバーからのより少ないデータがフェッチされ、エンティティのマテリアライゼーションが回避されます。
  • プロパティCompanyIdをCLRクラスに追加しないと、シャドウプロパティとしてバックグラウンドで作成されます。 FKプロパティの値を格納するデータベース列があります。それに対してプロパティを定義するか、EF willを定義します。


Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ