Сохранение проблем с Entity Framework (нужна концептуальная помощь) - PullRequest
1 голос
/ 11 февраля 2011

Описание проблемы: У меня есть сущности Master и Detail. Когда я инициализирую Master (myMaster), он создает экземпляр Details (myMaster.Detail), и оба они сохраняются в базе данных при добавлении myMaster. Однако, когда я перезагружаю контекст и открываю myMasterReloaded.detail, его свойства не инициализируются. Однако, если я извлекаю детали непосредственно из контекста, то это волшебным образом инициализирует myMasterReloaded.detail. Я разобрал это на примере минимального модульного теста ниже. Это «особенность» или я упускаю некоторые важные концептуальные детали?

//DECLARE CLASSES
public class Master
{
    [Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
    public Guid MasterId { get; set; }
    public Detail Detail { get; set; }
    public Master() { Detail = new Detail(); }
}

public class Detail
{
    [Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
    public Guid DetailId { get; set; }
    public Master MyMaster{ get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<Master> Masters { get; set; }
    public DbSet<Detail> Details { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Master>()
            .HasOptional(x => x.Detail)
            .WithOptionalPrincipal(x => x.MyMaster)
            .WillCascadeOnDelete(true);
    }
}

//PERFORM UNIT TEST
[TestMethod]
public void UnitTestMethod()
{
    //Start with fresh DB
    var context = new MyDbContext();
    context.Database.Delete();
    context.Database.CreateIfNotExists();

    //Create and save entities
    var master = context.Masters.Create();            
    context.Masters.Add(master);
    context.SaveChanges();

    //Reload entity
    var contextReloaded = new MyDbContext();
    var masterReloaded = contextReloaded.Masters.First();

    //This should NOT Pass but it does..
    Assert.AreNotEqual(master.Detail.DetailId, masterReloaded.Detail.DetailId);

    //Let's say 'hi' to the instance of details in the db without using it.
    contextReloaded.Details.First();

    //By simply referencing the instance above, THIS now passes, contracting the first Assert....WTF??
    Assert.AreEqual(master.Detail.DetailId, masterReloaded.Detail.DetailId);
}

(Это камень преткновения для более сложного набора сущностей. Я просто разобрал это до самого простого случая, я не могу просто заменить детали сложным типом).

Ура, Rob

Ответы [ 2 ]

2 голосов
/ 11 февраля 2011

I думаю это потому, что когда вы впервые перезагружаете Master , вы не загружаете Detail , поэтому Detail сущность не будет в Entity Framework «граф» (внутренняя память). Единственной вещью в графе будет единственная Master сущность.

Но когда вы запрашиваете Detail ("Давайте скажем привет"), он загружается в график, и ссылка была разрешена на основе ассоциации FK, поэтому ваш последний тест проходит как Master , теперь связанный с Detail .

Хотя я могу ошибаться, но вот как это звучит.

У вас отложенная загрузка включена? Если нет, вам нужно загрузить нужные вам отношения.

Вместо этого:

var masterReloaded = contextReloaded.Masters.First();

Попробуйте это:

var masterReloaded = contextReloaded.Masters.Include(x => x.Detail).First();
0 голосов
/ 13 февраля 2011

Мэтт Гамильтон был прав (см. Выше). Проблема была:

  1. Свойство Detail должно не создаваться внутри конструктора , а также через методы получения / установки через вспомогательный элемент. Если вам удобно создавать экземпляр Entity, содержащий Properties, в вашем новом экземпляре, то может быть полезно иметь отдельный метод инициализации, который не будет автоматически выполняться Entity Framework, так как он восстанавливает объекты из базы данных.
  2. Для правильной работы свойства Detail необходимо объявить virtual в мастер-классе.

Следующая БУДЕТ ПРОЙДЕТ (Как и ожидалось / надежда)

  public class Master
{
    [Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
    public Guid MasterId { get; set; }

    //Key new step: Detail MUST be declared VIRTUAL
    public virtual Detail Detail { get; set; }
}

public class Detail
{
    [Key, DatabaseGenerated(DatabaseGenerationOption.Identity)]
    public Guid DetailId { get; set; }
    //Set this to be VIRTUAL as well
    public virtual Master MyMaster { get; set; }
}

public class MyDbContext : DbContext
{
    public DbSet<Master> Masters { get; set; }
    public DbSet<Detail> Details { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //This sets up a BI-DIRECTIONAL relationship
        modelBuilder.Entity<Master>()
            .HasOptional(x => x.Detail)
            .WithOptionalPrincipal(x => x.MyMaster)
            .WillCascadeOnDelete(true);
    }
}


[TestMethod]
public void UnitTestMethod()
{

   var context = new MyDbContext();
   context.Database.Delete();
   context.Database.CreateIfNotExists();

   //Create and save entities
   var master = context.Masters.Create();

   //Key new step: Detail must be instantiated and set OUTSIDE of the constructor
   master.Detail = new Detail();
   context.Masters.Add(master);
   context.SaveChanges();

   //Reload entity
   var contextReloaded = new MyDbContext();
   var masterReloaded = contextReloaded.Masters.First();

   //This NOW Passes, as it should
   Assert.AreEqual(master.Detail.DetailId, masterReloaded.Detail.DetailId);

   //This line is NO LONGER necessary
   contextReloaded.Details.First();

   //This shows that there is no change from accessing the Detail from the context
   Assert.AreEqual(master.Detail.DetailId, masterReloaded.Detail.DetailId);
   }

Наконец, также нет необходимости иметь двунаправленные отношения. Ссылку на «MyMaster» можно безопасно удалить из класса Detail, а вместо этого можно использовать следующее сопоставление:

modelBuilder.Entity<Master>()
            .HasOptional(x => x.Detail)
            .WithMany()
            .IsIndependent();

С учетом вышеизложенного, выполнение context.Details.Remove (master.Detail) привело к тому, что master.Detail == null был истинным (как и следовало ожидать / надеюсь).

Я думаю, что некоторая путаница возникла из сопоставления X-to-many, где вы можете инициализировать виртуальный список сущностей в конструкторе (например, вызывая myDetails = new List ();), потому что вы не создаете экземпляр сами сущности.

Кстати, в случае, если у кого-то возникли сложности с однонаправленной картой «один ко многим» от Мастера до СПИСОК деталей, мне помогло следующее:

 modelBuilder.Entity<Master>()
             .HasMany(x => x.Details)
             .WithMany()
             .Map((x) =>
                      {
                           x.MapLeftKey(m => m.MasterId, "MasterId");
                           x.MapRightKey(d => d.DetailId, "DetailId");
                       });

Ура, Роб

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