我有一个类,有两个对同一个类的引用。更新主类时,我也可以更新引用的类。当我有两个对同一(修改)对象的引用时,我得到一个InvalidOperationException:
附加“ns.entity”类型的实体失败,因为同一类型的另一个实体已具有相同的主键值。如果图中的任何实体具有冲突的键值,则在使用“附加”方法或将实体的状态设置为“未更改”或“已修改”时,可能会发生这种情况。这可能是因为某些实体是新的并且尚未收到数据库生成的键值。在这种情况下,使用“添加”方法或“已添加”实体状态来跟踪图表,然后根据需要将非新实体的状态设置为“未更改”或“已修改”。
简单示例:
public class Example {
public int OldestFriendId {get; set;}
public int BestFriendId {get; set;}
public virtual Friend Oldest {get; set; }
public virtual Friend Best {get; set; }
}
如果在更新示例时,我想更新我的Oldest / Best好友的中间名,只要它们不相同,它就会起作用。但如果它们是相同的,那么我得到上述例外。
我无法弄清楚如何让它发挥作用。我已经尝试将引用设置为null,独立于父类保存它们,将它们中的所有引用设置为null(EF会自动在Friend中创建两个示例列表)。
如果有多个引用对象时,如何保存已更改的对象?
更新:尚未按照我想要的方式工作,但在从Friend中删除示例列表后我取得了一些进展。此外,更新是POST的结果。还在调查......
由于示例代码被要求...这是来自网络应用上的帖子,实际上没有进行任何更改
public ActionResult SaveEdit(int id, [Bind(Include = "OldestFriendId, BestFrinedId, Oldest, Best")] Example example)
{
if (ModelState.IsValid)
{
using (((WindowsIdentity)ControllerContext.HttpContext.User.Identity).Impersonate())
{
using (var _db = new exampleEntities())
{
//example.Best= example.Oldest; // this line would allow the update to work.
//next line is where the exception occurs
_db.Entry(example).State = EntityState.Modified;
_db.SaveChanges();
}
}
}
}
EditorFor模板:
@model Testing.Friend
<div class="col-md-10">
@Html.HiddenFor(model => model.FriendId)
@Html.EditorFor(model => model.FirstName)
@Html.EditorFor(model => model.LastName)
</div>
示例的编辑视图
@model Testing.Example
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Example</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.ExampleId)
<div class="form-group">
@Html.LabelFor(model => model.OldestFriendId, "OldestFriendId", htmlAttributes: new { @class = "control-label col-md-2" })
@Html.HiddenFor(model => model.OldestFriendId)
@Html.EditorFor(model => model.Oldest)
</div>
<div class="form-group">
@Html.LabelFor(model => model.BestFriendId, "BestFriendId", htmlAttributes: new { @class = "control-label col-md-2" })
@Html.HiddenFor(model => model.BestFriendId)
@Html.EditorFor(model=> model.Best)
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
答案 0 :(得分:2)
修改强>
最可能的原因是因为当您回溯对象时,它会将2个朋友反序列化为2个完全不同的对象(即使它们是相同的)。与下面的问题相同,但不是EF反序列化为2个对象,ASP.NET MVC正在这样做。
您需要做的事情如下:
Best = Oldest
SaveChanges()
原始答案
我的猜测是,当您检索数据时,这是Include
的典型问题。
当你这样做时
Context.Examples.Include(x => x.Oldest).Include(x => x.Best).ToList()
即使他们指向相同的记录,EF也会创建两个朋友的对象(最旧和最好)。这是包含的已知问题。
因此,当您在更新后进行保存时,EF将它们视为具有相同密钥(和数据)并抱怨的2个独立实体。
如果是这种情况,您有几个选择:
Friends
的列表,然后检索不包含Example
答案 1 :(得分:1)
我解决这个问题的方法是停止绑定整个对象,然后绑定到各个对象。
public ActionResult SaveEdit(int id, [Bind(Include = "OldestFriendId, BestFrinedId")] Example example,
Bind(Prefix="Oldest", Include = "FriendId, FirstName, MiddleName, LastName")] Friend oldest,
Bind(Prefix="Best", Include = "FriendId, FirstName, MiddleName, LastName")] Friend best) {
if (ModelState.IsValid)
{
using (((WindowsIdentity)ControllerContext.HttpContext.User.Identity).Impersonate())
{
using (var _db = new exampleEntities())
{
// do whatever processing you want on best and/or oldest
example.BestFriendId = best.FriendId;
example.OldestFriendId = oldest.FriendId;
_db.Entry(example).State = EntityState.Modified;
_db.SaveChanges();
}
}
}
}
答案 2 :(得分:0)
编辑:替换为完整的示例代码 这个例子适合我。 我认为你正在尝试的是什么。
using System;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Migrations;
namespace Ef6Test {
public class Program {
public static void Main(string[] args) {
ExecDb1();
}
private static void ExecDb1() {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Ef6Ctx, Ef6MigConf>());
WhichDb.DbName = "MSAMPLEDB";
WhichDb.ConnType = ConnType.CtxViaDbConn;
var sqlConn = GetSqlConn4DBName(WhichDb.DbName);
var context = new Ef6Ctx(sqlConn);
context.Database.Initialize(true);
Console.WriteLine(WhichDb.DbName, context.Database.Exists() );
AddJunk(context);
}
public static class WhichDb {
public static string DbName { get; set; }
public static string ConnectionName { get; set; }
public static ConnType ConnType { get; set; }
}
public enum ConnType {
CtxViaDbConn,
CtxViaConnectionName
}
private static void AddJunk(DbContext context) {
var friend = new Friend();
friend.Name = "Fred";
friend.Phone = "555-1232424";
context.Set<Friend>().Add(friend);
context.SaveChanges();
// break here and check db content.
var eg = new Example();
eg.Best = friend; // set them equal
eg.Oldest = friend;
friend.Name = "Fredie"; // change the name of the fly
friend.Phone = "555-99999"; // and phone is also different
context.Set<Example>().Add(eg); Add the new example
context.SaveChanges();
// result... 2 records.
// The original friend record should be chnaged
}
public static DbConnection GetSqlConn4DBName(string dbName) {
var sqlConnFact = new SqlConnectionFactory(
"Data Source=localhost; Integrated Security=True; MultipleActiveResultSets=True");
var sqlConn = sqlConnFact.CreateConnection(dbName);
return sqlConn;
}
}
public class MigrationsContextFactory : IDbContextFactory<Ef6Ctx> {
public Ef6Ctx Create() {
switch (Program.WhichDb.ConnType) {
case Program.ConnType.CtxViaDbConn:
var sqlConn = Program.GetSqlConn4DBName(Program.WhichDb.DbName); //
return new Ef6Ctx(sqlConn);
case Program.ConnType.CtxViaConnectionName:
return new Ef6Ctx(Program.WhichDb.ConnectionName);
default:
throw new ArgumentOutOfRangeException();
}
}
}
public class Ef6MigConf : DbMigrationsConfiguration<Ef6Ctx> {
public Ef6MigConf() {
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
}
public class Friend {
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
public class Example
{
public int Id { get; set; }
public int? BestFriendId { get; set; }
public int? OldestFriendId { get; set; }
public virtual Friend Best { get; set; }
public virtual Friend Oldest { get; set; }
}
public class Ef6Ctx : DbContext {
public Ef6Ctx(DbConnection dbConn) : base(dbConn, true) { }
public Ef6Ctx(string connectionName) : base(connectionName) { }
public DbSet<Friend> Friends { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Example>()
.HasOptional(t=>t.Best)
.WithMany()
.HasForeignKey(x=>x.BestFriendId);
modelBuilder.Entity<Example>()
.HasOptional(t => t.Oldest)
.WithMany()
.HasForeignKey(x => x.OldestFriendId);
}
}
}