Что заставит Entity Framework сохранить незагруженную (но лениво загружаемую) ссылку на существующие данные? - PullRequest
2 голосов
/ 21 июля 2011

Я столкнулся с интересной ошибкой в ​​моем приложении ASP.NET MVC 3, использующей Entity Framework 4.1 Code First. У меня есть три класса / таблицы, которые объединены в последовательности. Есть 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 Ответ

4 голосов
/ 21 июля 2011

Если я правильно понимаю, у вас есть что-то вроде этого:

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();

Но такая процедура выглядит действительно нелепо.

Edit:

Кстати, автоматическое обнаружение изменений вызывает поведение. Этот код ...

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

... не создает новый контакт. Это обнаружение изменений, работающее внутренне в SaveChanges Find, которое думает идентифицировать MainContact в company как новый объект и переводит его в состояние Added в контекст.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...