什么会导致Entity Framework在现有数据上保存一个卸载(但是可以加载的延迟)引用?

时间:2011-07-21 16:11:02

标签: entity-framework lazy-loading

我在使用Entity Framework 4.1 Code First的ASP.NET MVC 3应用程序中遇到了一个有趣的错误。我有三个按顺序连接的类/表。有一个Invitation引用Project,然后引用Company

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

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

我认为如果对象没有被加载,那么EF应该没有任何理由将其标记为已编辑,对吧?

更新:通过注释掉代码行进行了大量调试后,我将其缩小了一些。

要清除的实际对象是公司引用的Contact对象。它并没有真正被清除,因为在构造函数中创建了一个新的联系人(因此新公司不会为空。)

所以我想这改变了我的问题:有没有办法让引用属性设置为默认值而不破坏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; }
    // ...
}

1 个答案:

答案 0 :(得分:4)

如果我理解正确,你会有这样的事情:

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

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

像这样的简单代码......

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

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

我要得出的主要结论是:不要在构造函数中实例化引用导航属性!(实际上,只要你将内容留空,实例化导航集合就可以了。也可以实例化构造函数中复杂类型的属性很好,因为它们不是其他实体。)

如果您想确保与新公司建立新联系人,可能Company类中的静态工厂方法是更好的选择:

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 Find内部进行的更改检测,它认为将MainContact中的company标识为新实体并将其放入{{} 1}}陈述进入上下文。