既存のエンティティに参照プロパティを保持するエンティティ(EF APIのAdd
メソッド)または更新(Update
EF APIメソッド)エンティティを挿入するときに問題が発生しました。エンティティ(私は、既存のエンティティをデータベースに既に存在し、PKが適切に設定されているエンティティと呼んでいます)。既存のエンティティ(エンティティフレームワークコア1.1.0)との関係で新しいエンティティを挿入するときの問題
モデルはPlace
、Person
、Address
、およびStatus
で構成されています
- 人は多くのアドレスを持っています。
- 場所には複数の人がおり、 複数の住所もあります。
- 場所、人物、住所にはステータスがあります。
- すべてのエンティティは、ID、名前、作成日や更新日を(これらのフィールドはすべての抽象
BaseEntity
に定義されている)持って新しい人と新しいと
私は「場所」のための全体のグラフを作成した場合、アドレスを入力して1ステップで保存すれば、すべて問題ありません。
Addresesでプレースを作成して保存しても、それでも問題はありません。 最後に、既存の人物を追加してプレイスを再保存するときに例外があります。EFは実際に既存の人物を挿入しようとしますが、EFが提供されたIDを持つ行を挿入しようとしたためSQL ServerがエラーをスローしますSQL Serverによって生成される)。
デフォルトでは、EF Core 1.1.0は、リレーションシップを正しくトラバースできず、追加する必要があるエンティティと無視または更新する必要があるエンティティを検出するように見えます。すでにPKが正の値に設定されているエンティティを挿入しようとします。
いくつかの調査をした後、私はEF Core 1.1.0 APIの新しいDbContext.ChangeTracker.Track()
メソッドを発見しました。ルートエンティティの関係をトラバースして検出されたすべてのエンティティに対してコールバックメソッドを実行できます。 これにより、プライマリキーの値に応じて、適切な状態を設定しました。
このコードがないと(DbRepository.ApplyStates()
)、既存のエンティティとリレーションを参照する限り、私の挿入は機能しません。
EF7とDNX CLIでは、DbRepository.ApplyStates()がなくてもこのシナリオが機能することに注意してください。モデル、DbContext、リポジトリおよびテストコード:
すべてがそこにある再現する
ソース。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace EF110CoreTest
{
public class Program
{
public static void Main(string[] args)
{
Seed();
}
private static void Seed()
{
// Repo
var statusRepo = new DbRepository<Status>();
var personRepo = new DbRepository<Person>();
var addressRepo = new DbRepository<Address>();
var placeRepo = new DbRepository<Place>();
// Status
if (!statusRepo.GetAll().Any())
{
statusRepo.InsertOrUpdate(new Status() { Name = "Active" });
statusRepo.InsertOrUpdate(new Status() { Name = "Archive" });
statusRepo.SaveChanges();
}
var statusActive = statusRepo.GetSingle(1);
var statusArchive = statusRepo.GetSingle(2);
// Delete the non static data
foreach(var address in addressRepo.GetAll()) addressRepo.Delete(address);
addressRepo.SaveChanges();
foreach (var place in placeRepo.GetAll()) placeRepo.Delete(place);
placeRepo.SaveChanges();
foreach (var person in personRepo.GetAll()) personRepo.Delete(person);
personRepo.SaveChanges();
Console.WriteLine("Cleared any existing data");
/***********************************************************************/
// Step 1 : a person with status and addresses is saved
var personWithAddresses = new Person()
{
Name = "Jon SNOW",
Status = statusActive,
AddressCollection = new List<Address>()
{
new Address() { City = "Castleblack", Status = statusActive },
new Address() { City = "Winterfel", Status = statusArchive }
}
};
personRepo.InsertOrUpdate(personWithAddresses);
personRepo.SaveChanges();
Console.WriteLine("Step 1 ok");
System.Threading.Thread.Sleep(1000);
/***********************************************************************/
// Step 2 : Create a place with addresses
var placeWithAddress = new Place()
{
Name = "Castleblack",
Status = statusActive
};
placeWithAddress.AddressCollection.Add(new Address() { City = "Castleblack", Status = statusActive });
placeRepo.InsertOrUpdate(placeWithAddress);
placeRepo.SaveChanges();
Console.WriteLine("Step 2 ok");
System.Threading.Thread.Sleep(1000);
/***********************************************************************/
// Step 3 : add person to this place
placeWithAddress.PersonCollection.Add(personWithAddresses);
placeRepo.InsertOrUpdate(placeWithAddress);
placeRepo.SaveChanges();
Console.WriteLine("Step 3 ok");
System.Threading.Thread.Sleep(1000);
}
}
public class DbRepository<T> where T : BaseEntity
{
protected readonly MyContext _context;
public DbRepository() { _context = new MyContext(); }
public T GetSingle(int id) => _context.Set<T>().FirstOrDefault(e => e.Id == id);
public IEnumerable<T> GetAll() => _context.Set<T>().AsEnumerable();
public void Insert(T entity)
{
ApplyStates(entity);
_context.Add(entity);
}
public void Update(T entity)
{
ApplyStates(entity);
_context.Update(entity);
}
public void Delete(T entity)
{
_context.Remove(entity);
}
private void ApplyStates(T entity)
{
_context.ChangeTracker.TrackGraph(entity, node =>
{
var entry = node.Entry;
var childEntity = (BaseEntity)entry.Entity;
entry.State = childEntity.IsNew ? EntityState.Added : EntityState.Modified;
});
}
public void InsertOrUpdate(T entity)
{
if (entity.IsNew) Insert(entity); else Update(entity);
}
public void SaveChanges()
{
var pendingChanges = _context.ChangeTracker.Entries<T>()
.Where(entry => entry.State == EntityState.Added || entry.State == EntityState.Modified)
.Select(e => e.Entity)
.ToList();
foreach (var entity in pendingChanges)
{
entity.Modified = DateTime.Now;
if (entity.Created == null) entity.Created = DateTime.Now;
}
_context.SaveChanges();
}
}
#region Models
public abstract class BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? Created { get; set; }
public DateTime? Modified { get; set; }
[NotMapped]
public bool IsNew => Id <= 0;
}
public class Person : BaseEntity
{
public int? StatusId { get; set; }
public Status Status { get; set; }
public List<Address> AddressCollection { get; set; } = new List<Address>();
}
public class Address : BaseEntity
{
public string Zip { get; set; }
public string City { get; set; }
public int? StatusId { get; set; }
public Status Status { get; set; }
public int? PersonId { get; set; }
public Person Person { get; set; }
public int? PlaceId { get; set; }
public Place Place { get; set; }
}
public class Place : BaseEntity
{
public int? StatusId { get; set; }
public Status Status { get; set; }
public List<Person> PersonCollection { get; set; } = new List<Person>();
public List<Address> AddressCollection { get; set; } = new List<Address>();
}
public class Status : BaseEntity { }
#endregion
#region Context
public class MyContext : DbContext
{
public DbSet<Status> StatusCollection { get; set; }
public DbSet<Person> PersonCollection { get; set; }
public DbSet<Address> AddressCollection { get; set; }
public DbSet<Place> PlaceCollection { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
// Basic event fire of model creation
base.OnModelCreating(builder);
// Status
builder.Entity<Status>().ToTable("Status", "Shared");
// Person
builder.Entity<Person>().ToTable("Person", "Shared");
builder.Entity<Person>()
.HasMany(p => p.AddressCollection)
.WithOne(a => a.Person);
// Address
builder.Entity<Address>().ToTable("Address", "Shared");
builder.Entity<Address>()
.HasOne(p => p.Person)
.WithMany(a => a.AddressCollection);
// Place
builder.Entity<Place>().ToTable("Place", "Shared");
builder.Entity<Place>()
.HasMany(p => p.AddressCollection)
.WithOne(p => p.Place);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EF110CoreTest;Trusted_Connection=True;");
}
}
#endregion
}
Project.json
{ "バージョン" のファイル:「1.0。0- *」、 "buildOptions":{ "emitEntryPoint":真 }、
"dependencies": {
"Microsoft.EntityFrameworkCore": "1.1.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.1.0",
"Microsoft.EntityFrameworkCore.Tools": "1.1.0-preview4-final"
},
"frameworks": {
"net461": {}
},
"tools": {
"Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final"
}
}
例外の詳細
Microsoft.EntityFrameworkCore.DbUpdateException:エラーがしばらく発生しましたSystem.Data.SqlClient.SqlException:IDENTITY_INSERTがOFFに設定されている場合、テーブル 'Person'のID列に明示的な値を挿入できません。
これは多くのコードです。あなたはその例外をいつ取得しますか?例外に関するあなたの質問は何ですか? – CodingYoshi
私はステップ3の例外があります。例外は想定されていませんので、問題は私のコードまたはEFCore 1.1.0(まだEF7という名前のアルファバージョンで動作していたので)に問題があります。 – kall2sollies
答え、コメント、私自身の研究によると、私はより大きな問題をターゲットにしていたので、問題を完全に書き直しました。 – kall2sollies