什么会导致实体框架保存一个卸载(但可延迟的可加载)参考现有的数据? [英] What would cause the Entity Framework to save an unloaded (but lazy loadable) reference over existing data?

查看:101
本文介绍了什么会导致实体框架保存一个卸载(但可延迟的可加载)参考现有的数据?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用Entity Framework 4.1 Code First的ASP.NET MVC 3应用程序中遇到了一个有趣的错误。我有三个类/表依次连接。有一个邀请引用了一个项目,然后引用公司



当我加载公司并保存一切都很好。项目相同然而,当邀请被编辑和保存时,它会清除公司中的字段。他们都只是空白!



当我编辑项目时,我需要显示公司的一些信息,所以我明确地加载了 .Include(x => x.Company)。当我编辑邀请,我不需要公司,所以我没有打扰包括它。



我会认为,如果对象不是那么EF应该不会有什么理由把它标记为已编辑的,对吗?



更新:经过多次调试通过注释代码行已经缩小了一些。



被清除的实际对象是一个联系人对象,被引用由公司。而且由于在构造函数中创建了一个新的联系人,所以并没有真正被清除。所以对于新的公司来说,这不是空的。



所以我猜这改变了我的问题:有没有办法将引用的属性设置为默认值而不会中断EF?

  public class InvitationController 
{
[HttpPost]
public RedirectToRouteResult AcceptInvitation(int id,int companyId,int projectId,Invitation invitation)
{
//该行通过加载公司来触发问题,而不需要
//热切地加载联系人。
CheckAuthorizationEdit(companyId,CommunicationService.GetById(id));

var dbResponse = InvitationService.GetPreviousResponse(companyId,projectId);
dbResponse.WillBid = invitation.WillBid;
InvitationService.Save(dbResponse);

返回RedirectToAction(Response,new {id,companyId});
}

private void CheckAuthorizationEdit(int companyId,Communication communication)
{
var companyIds = communication.DistributionList.Companies.Select(c => c.Id ).ToList();
// CheckAuthorization(companyIds);
}
}

public class InvitationService
{
public Invitation GetPreviousResponse(int companyId,int projectId)
{
return (来自_db.Invitations中的邀请
其中,invite.ProjectId == projectId&&& inv。
}

public void Save(邀请邀请)
{
_db.SaveChanges();
}
}

public class Invitation
{
public int Id {get;组; }
public int ProjectId {get;组; }
[ForeignKey(ProjectId)]
public virtual Project Project {get;组; }
// ...
}


public class Project
{
public int Id {get;组; }
public int CompanyId {get;组; }
[ForeignKey(CompanyId)]
public virtual Company Company {get;组;
// ...
}

public class公司
{
public Company()
{
MainContact =新联系人();
}

public int Id {get;组; }
public virtual联系人MainContact {get;组; }
// ...
}

public class Contact
{
public int Id {get;组; }
public string AddressLine1 {get;组; }
// ...
}


解决方案

如果我明白你有这样的东西:

  public class公司
{
public Company()
{
MainContact = new Contact();
}

public int Id {get;组; }
public virtual联系人MainContact {get;组;
}

这样一个简单的代码...

  var company = context.Companies.Find(1); 
context.SaveChanges();

...确实会在数据库中创建一个新的空白联系人。



我将绘制的主要结论是:不要在构造函数中实例化参考导航属性!(实例化导航集合可以,我想,只要你离开内容为空,还可以在构造函数中实例化复杂类型的属性,因为它们不是其他实体。)



如果要确保创建一个新的联系人新公司,也许在公司类中的静态工厂方法是更好的选择:

  public static Company CreateNewCompany()
{
return new Company {MainContact = new Contact()};
}

这也可以工作:

  var company = context.Companies.Find(1); 
context.Entry(company.MainContact).State = EntityState.Detached;
context.SaveChanges();

但是这样一个过程看起来真的很可笑。



编辑:



这是自动更改检测导致行为的方式。这段代码...

  context.Configuration.AutoDetectChangesEnabled = false; 
var company = context.Companies.Find(1);
context.SaveChanges();

...不会创建新的联系人。这是在 SaveChanges 中查找内部的更改检测,认为 MainContact 公司中作为新实体,并将其放入中添加状态上下文。


I'm running into an interesting bug in my ASP.NET MVC 3 application using Entity Framework 4.1 Code First. I have three classes/tables that are joined in sequence. There's an Invitation that has a reference to a Project which then has a reference to Company.

When I load a company and save it everything is fine. Same for projects. However when the invitation gets edited and saved, it wipes out the fields in the company. They're all just blank!

When I edit the project, I need to show some info from the company, so I'm explicitly loading that with .Include(x => x.Company). When I edit the invitation though, I don't need the company so I haven't bothered including it.

I'd think that if the object wasn't ever loaded, then there shouldn't be any reason for EF to flag it as edited, right?

Update: After much debugging via commenting out lines of code I've narrowed it down some.

The actual object being cleared was a Contact object that's referenced by the company. And it wasn't really being cleared so much as a new contact was created in the constructor (so it wouldn't be null for new Companies.)

So I guess that changes my question: Is there a way to have a referenced property set to a default value without breaking EF?

public class InvitationController
{
    [HttpPost]
    public RedirectToRouteResult AcceptInvitation(int id, int companyId, int projectId, Invitation invitation)
    {
        // This line triggered the problem by loading a company, without 
        // eagerly loading the contacts.
        CheckAuthorizationEdit(companyId, CommunicationService.GetById(id));

        var dbResponse = InvitationService.GetPreviousResponse(companyId, projectId);
        dbResponse.WillBid = invitation.WillBid;
        InvitationService.Save(dbResponse);

        return RedirectToAction("Response", new { id, companyId } );
    }

    private void CheckAuthorizationEdit(int companyId, Communication communication)
    {
        var companyIds = communication.DistributionList.Companies.Select(c => c.Id).ToList();
        //CheckAuthorization(companyIds);
    }
}

public class InvitationService
{
    public Invitation GetPreviousResponse(int companyId, int projectId)
    {
        return (from invitation in _db.Invitations
                where invitation.ProjectId == projectId && invitation.SenderCompanyId == companyId
                select invitation).SingleOrDefault();
    }

    public void Save(Invitation invitation)
    {
        _db.SaveChanges();
    }
}

public class Invitation
{
    public int Id { get; set; }
    public int ProjectId { get; set; }
    [ForeignKey("ProjectId")]
    public virtual Project Project { get; set; }
    // ...
}


public class Project
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    [ForeignKey("CompanyId")]
    public virtual Company Company { get; set; }
    // ...
}

public class Company
{
    public Company()
    {
        MainContact = new Contact();
    }

    public int Id { get; set; }
    public virtual Contact MainContact { get; set; }
    // ...
}

public class Contact
{
    public int Id { get; set; }
    public string AddressLine1 { get; set; }
    // ...
}

解决方案

If I understand right you have something like this:

public class Company
{
    public Company()
    {
        MainContact = new Contact();
    }

    public int Id { get; set; }
    public virtual Contact MainContact { get; set; }
}

A simple code like this...

var company = context.Companies.Find(1);
context.SaveChanges();

...will indeed create a new empty contact in the database.

The main conclusion I would draw is: Don't instantiate reference navigation properties in the constructor! (Instantiating navigation collections is OK, I think, as long as you leave their content empty. Also instantiating properties of complex types in the constructor is fine because they are not other entities.)

If you want to make sure to create a new contact with a new company, perhaps a static factory method in the Company class is the better option:

public static Company CreateNewCompany()
{
    return new Company { MainContact = new Contact() };
}

This would also work:

var company = context.Companies.Find(1);
context.Entry(company.MainContact).State = EntityState.Detached;
context.SaveChanges();

But such a procedure looks really ridiculous.

Edit:

It's by the way automatic change detection which causes the behaviour. This code...

context.Configuration.AutoDetectChangesEnabled = false;
var company = context.Companies.Find(1);
context.SaveChanges();

...doesn't create a new contact. It's the change detection working internally in SaveChanges Find which thinks to identify the MainContact in company as a new entity and puts it into Added state into the context.

这篇关于什么会导致实体框架保存一个卸载(但可延迟的可加载)参考现有的数据?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆