Entity Framework — проблема с дискриминатором при TPH

#inheritance #mapping #entity-framework-4.1

#наследование #сопоставление #entity-framework-4.1

Вопрос:

Я получаю Invalid column name 'Discriminator' при сохранении записи. (Сначала код, EF4.1)

У меня есть объект, который я хочу отслеживать через EF:

 public class Audit
 public virtual string p1
 public virtual string p2
  

у меня есть специализированный UserAudit, который не добавляет новых виртуальных свойств, он просто настраивает базу, готовую к аудиту

 public class UserAudit : Audit
 public UserAudit() { p1 = someval; }
  

И конфигурация:

 public class AuditConfiguration : EntityTypeConfiguration<Audit>
  {
    public AuditConfiguration()
    {
      ToTable("_AUDIT");
      HasKey(c => c.Id);
      Property(c => c.Id).HasColumnName("AUDIT_ID");
    }
  }
  

И репозиторий:

 public class AuditRepository : IAuditRepository
  {
    public void LogAudit(Audit audit)
    {
      using (var db = new AuditContext())
      {
        db.Audits.Add(audit);
        db.SaveChanges();
      }
    }
  }
  

Что мне нужно сделать, чтобы сообщить EF, чтобы игнорировать / обрабатывать специализацию правильно, когда я repo.LogAudit( userAudit ); ?

Ответ №1:

Из сообщения об исключении Invalid column name 'Discriminator' я бы сделал вывод, что вы не разрешили EF 4.1 создавать таблицу базы данных, _AUDIT потому что в противном случае EF создал бы столбец, вызываемый Discriminator в таблицу. Возможно, у вас есть существующая таблица базы данных без такого столбца. Когда EF пытается сохранить объект, он хочет сохранить значение, представляющее конкретный тип, который вы сохраняете, в столбце дискриминатора, но столбец не существует. Отсюда и исключение.

Редактировать

Итак, вам нужен столбец дискриминатора. Вы можете определить свой собственный столбец дискриминатора следующим образом:

 public class AuditConfiguration : EntityTypeConfiguration<Audit>
{
    public AuditConfiguration()
    {
        ToTable("_AUDIT");
        HasKey(c => c.Id);
        Property(c => c.Id).HasColumnName("AUDIT_ID");

        Map<Audit>(m => m.Requires("Type").HasValue<byte>(0).IsRequired());
        Map<UserAudit>(m => m.Requires("Type").HasValue<byte>(1).IsRequired());
    }
}
  

При этом в tinyint таблице использовался бы не обнуляемый Type столбец _AUDIT , который имеет значение 0 для объектов базового Audit типа и значение 1 для объектов производного UserAudit типа.

Комментарии:

1. Это не то, что я ищу. Мне не нужно было добавлять тип в мою таблицу — действительно, я не могу. Я хочу, чтобы EF игнорировал UserAudit — у него просто есть методы, которые записывают в базовые свойства. Не дискриминируйте, игнорируйте!

2. Хорошо, итак, у меня действительно есть столбец «TypeOfAction». Как мне различать несколько значений?

3. @BobTodd: Вы не можете игнорировать UserAudit отображение EF, а затем добавить UserAudit к Audits набору. Вы могли бы создать только вспомогательный метод, который извлекает Audit из UserAudit (в основном путем копирования всех свойств), а затем сохранить этот Audit объект. Вероятно, вам также понадобится метод для создания UserAudit из Audit объекта, потому что при извлечении объекта из базы данных EF всегда будет создавать экземпляр Audit , а не UserAudit an. Другим обходным путем может быть создание Audit элемента в UserAudit (композиция вместо наследования), который позволяет избежать копирования свойств.

4. @BobTodd: «… различать по нескольким значениям …» Вы имеете в виду, что один и тот же тип может иметь разные значения в этом столбце? Если да, я боюсь, что это невозможно. Но я не уверен. Вам лучше задать это как отдельный вопрос, чтобы получить обоснованный ответ. Мне тоже было бы интересно, есть ли способ.

5. Ооо! Оо! ModelBuilder. Игнорировать(new [] {typeof (UserAudit)});

Ответ №2:

@Slauma абсолютно прав, и вы должны использовать его решение. Я просто добавляю объяснение, почему это происходит.

Наследование в сущностях должно быть смоделировано в базе данных, потому что, когда вы загружаете сущность из базы данных, EF должен знать, должна ли она материализоваться Audit или UserAudit экземпляр. По умолчанию EF использует нечто, называемое наследованием таблицы для каждой иерархии, где базовый тип и все подтипы хранятся в одной таблице. Для поддержки такого сценария EF ожидает дополнительный столбец в таблице, вызываемой дискриминатором по умолчанию. Этот столбец будет использоваться для различения хранимых экземпляров — по умолчанию он включает имена типов.

Ответ №3:

В моем случае модель, отличная от EF, была унаследована от модели EF. Вместо наследования я скопировал все свойства в модель, отличную от EF, и это было решением для меня.

 public class ApplicationContext : DbContext
{
    public DbSet<Person> Persons { get; set; }
}

public class Person
{
    public int PersonId { get; set; }
    public string Name { get; set; }
}
  

Перед:

 public class PersonWithExtraInfo:Person
{
    public string ExtraInfo { get; set; }
}
  

После:

 public class PersonWithExtraInfo
{
    public int PersonId { get; set; }
    public string Name { get; set; }
    public string ExtraInfo { get; set; }
}