我在插入EF API的Add
方法)或更新(EF Update
EF API方法)实体时遇到了一个问题,这些实体将参考属性保存到现有实体(我称现有实体已经是一个实体存在于数据库中,并正确设置其PK。)
该模型包含Place
,Person
,Address
和Status
:
BaseEntity
定义)如果我为“地方”创建一个完整的图表,使用新的人员和新的地址,并将其保存在一个步骤中,一切都很好。
如果我创建一个带有地址的地方然后保存它,它仍然可以。 但最后当我添加一个现有人并重新放置Place时,我有一个例外:EF实际上试图插入现有的人,并且SQL Server抛出一个错误,因为EF试图插入一个带有提供的Id的行(PK设置为由SQL Server生成。
这意味着默认情况下,EF Core 1.1.0看起来无法正确遍历关系并发现应添加哪些enitite,以及应忽略或更新哪些enitite。它尝试插入已将其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文件
{ “version”:“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:更新条目时发生错误。有关详细信息,请参阅内部异常---&GT; System.Data.SqlClient.SqlException:当IDENTITY_INSERT设置为OFF时,无法在表'Person'中为identity列插入显式值。
答案 0 :(得分:1)
我修改了一些代码,请查看它。
在课程DbRepository
中添加了另一个构造函数,以确保不同DbContext
中存在相同的DbRepository
。
public DbRepository(MyContext myContext)
{
_context = myContext;
}
在课程Person
中添加了2个属性,以确保Person
和Place
之间的关系。
public int? PlaceId { get; set; }
public Place Place { get; set; }
在函数Seed
中,修改了一些带有上述修改的代码。
首先,在初始化存储库的部分。
// Repo
var myContext = new MyContext();
var statusRepo = new DbRepository<Status>(myContext);
var personRepo = new DbRepository<Person>(myContext);
var addressRepo = new DbRepository<Address>(myContext);
var placeRepo = new DbRepository<Place>(myContext);
这将使所有存储库使用相同的数据库连接。
其次,由于这些变化,明确的流程也应该改变订单。
// Delete the non static data
foreach (var address in addressRepo.GetAll()) addressRepo.Delete(address);
addressRepo.SaveChanges();
foreach (var person in personRepo.GetAll()) personRepo.Delete(person);
personRepo.SaveChanges();
foreach (var place in placeRepo.GetAll()) placeRepo.Delete(place);
placeRepo.SaveChanges();
在Step 1
中,我使用CatsleBlack
提取地址,因为我猜Person
中的那个和Place
中的另一个应该是相同的。
因此,当您初始化新的Person
时,它将是
var castleBlack = new Address {City = "Castleblack", Status = statusActive};
var personWithAddresses = new Person()
{
Name = "Jon SNOW",
Status = statusActive,
AddressCollection = new List<Address>()
{
castleBlack,
new Address() { City = "Winterfel",
Status = statusArchive }
}
};
初始化Place
var placeWithAddress = new Place()
{
Name = "Castleblack",
Status = statusActive
};
placeWithAddress.AddressCollection.Add(castleBlack);
这些是我所做的,可以成功保存。 db中的Person
记录也有PlaceId
。