Вставка Entity Framework конфликтует с другим внешним ключом - PullRequest
0 голосов
/ 13 июня 2019

У меня проблемы со вставкой данных со связанными объектами.

public class Status : Entity, IAggregateRoot
 {
    //other properties
     public readonly List<Video> _videos;
     public readonly List<Photo> _photos;
 }

-

public class Log : Entity, IAggregateRoot
  {
    //other properties
     public readonly List<Video> _videos;
     public readonly List<Photo> _photos;
     public readonly List<Sound> _audios;
  }

-

public class Photo : Entity, IAggregateRoot
    {
        //other properties
        public string Type { get; set; }
        public int TypeId { get; set; }
    }

В основном, объект статусаможет иметь ноль или более видео или изображений.Объект журнала также может содержать ноль или более видео, звука или изображений.Ниже приведен свободный API-код, используемый для достижения этой цели:

class LogEntityTypeConfiguration : IEntityTypeConfiguration<Log>
    {
        public void Configure(EntityTypeBuilder<Log> logConfiguration)
        {
            logConfiguration.HasMany(b => b.Videos)
              .WithOne()
              .HasForeignKey("TypeId")
              .OnDelete(DeleteBehavior.Cascade);

            logConfiguration.HasMany(b => b.Photos)
              .WithOne()
              .HasForeignKey("TypeId")
              .OnDelete(DeleteBehavior.Cascade);

            logConfiguration.HasMany(b => b.Audios)
              .WithOne()
              .HasForeignKey("TypeId")
              .OnDelete(DeleteBehavior.Cascade);
        }
    }

-

public void Configure(EntityTypeBuilder<Status> statusConfiguration)
    {
        statusConfiguration.HasMany(b => b.Videos)
          .WithOne()
          .HasForeignKey("TypeId")
          .OnDelete(DeleteBehavior.Cascade);

        statusConfiguration.HasMany(b => b.Photos)
          .WithOne()
          .HasForeignKey("TypeId")
          .OnDelete(DeleteBehavior.Cascade);
    }

Это просто прекрасно, на рисунке ниже показаны сгенерированные внешние ключи.enter image description here

У меня есть класс хранилища журналов, при попытке вставить объект журнала я получаю следующую ошибку:

System.Data.SqlClient.SqlException (0x80131904): оператор INSERT конфликтовал с ограничением FOREIGN KEY
"FK_Photos_Statuses_TypeId".Конфликт произошел в базе данных «xxxx», таблице «dbo.Statuses», столбце «Id»

public async Task<Log> AddAsync(Log log, LogFiles files)
    {
        var strategy = _context.Database.CreateExecutionStrategy();

        await strategy.ExecuteAsync(async () => {

            using (var txn = _context.Database.BeginTransaction())
            {
                try
                {
                    if (log.IsTransient())
                    {
                        _context.Logs.Add(log);
                        _context.SaveChanges();

                        if (files.Video != null)
                        {
                            Video vid = new Video();
                            vid = files.Video;
                            log._videos.Add(vid);
                        }

                        if(files.Picture != null)
                        {
                            Photo ph = new Photo();
                            ph = files.Picture;
                            log._photos.Add(ph);
                        }

                        if(files.Audio != null)
                        {
                            Sound aud = new Sound();
                            aud = files.Audio;
                            log._audios.Add(aud);
                        }
                        _context.SaveChanges();

                        txn.Commit();
                    }

                }
                catch (Exception ex)
                {
                    txn.Rollback();
                }
            }

        });

        return log;
    }

Я также не понимаю, почему внешний ключ объекта состояния отображается средисписок ошибок, когда я пытаюсь вставить объект журнала ??

ps, если у вас есть лучший способ, которым я мог бы смоделировать отношения, пожалуйста, поделитесь.

1 Ответ

0 голосов
/ 14 июня 2019

Это, скорее всего, из-за разрозненных лиц.Каков источник свойств Log & LogFiles?Я предполагаю, что они исходят от веб-клиента?

Чтобы обрисовать проблему с проходящими объектами: Давайте посмотрим на объект Photo, у которого есть ссылка Status.

public class Status
{
   public int StatusId { get; set; }
   public string Name { get; set; }
}
public class Photo
{
   public int PhotoId { get; set; }
   public virtual Status Status { get; set; }
}

Теперь, если япойти и загрузить набор фотографий из DbContext. Я мог бы вернуть 2 фотографии со статусом «Новый».

Что касается "экземпляров", у меня было бы:

 Photo (ID: 1)  \
                  ==> Status (ID: 1 [New])
 Photo (ID: 2)  /

Проблема в том, что когда я отправляю эти отключенные объекты обратно в нечто вроде контроллера, они десериализуются и будут выглядетьнапример:

 Photo (ID: 1)  ==> Status (ID: 1 [New])
 Photo (ID: 2)  ==> Status (ID: 1 [New])

В вашем случае вы возвращаете новую фотографию (в порядке), но она должна быть связана с существующим статусом.Ваша сущность Photo, вероятно, будет настроена для создания PK, но поиск типа Status не будет.В любом случае, если EF не «знает» о статусе, он будет рассматриваться как новый объект вместе с фотографией.Это приводит к ограничению FK, поскольку EF пытается вставить идентификатор состояния 1.

Передача сущностей обратно в контроллеры приводит к всевозможным проблемам.Если вы выполняете, например, Редактирование фотографии, возвращая фото ID 1, вы обнаружите, что вам нужно как-то сообщить EF о фотографии # 1 (используя Attach и, например, установить для параметра «Состояние» значение «Изменено»), а затемтакже сталкиваются с ошибками FK вокруг любых связанных объектов к фотографии.Присоединение связанных сущностей (таких как Status) первоначально решит вашу проблему, но затем приведет к дополнительным сложностям, подобным описанным выше, когда множественные ссылки на один и тот же Status фактически являются отдельными экземплярами объекта Status.Вызов присоединения к первому экземпляру будет работать, но затем вы получите исключение, как только сохраните что-то в этом контексте с тем же статусом.Различные ссылки и EF будут жаловаться на то, что экземпляр с одинаковым идентификатором связан с контекстом, если вы попытаетесь присоединить 2-й.

Присоединение объектов от самого клиента - опасная практика, поскольку вы неявно доверяете поступающим данным.назад от клиента.Опытный, злонамеренный пользователь может легко изменить данные любым количеством способов, помимо того, что позволяет ваша веб-страница, повредив хранилище данных.

Главный совет, который я даю разработчикам в EF, - не передавайте сущности вокруг».Ничего хорошего из этого не выйдет.:) Если вы передадите модели просмотра журнала, модели просмотра фотографий и т. Д., Тогда это сократит объем данных, передаваемых туда и обратно между сервером и клиентом (что делает систему более быстрой и менее ресурсоемкой), и заставляет задуматься о поступлении данных.назад.

Например, если я заберу LogInsertViewModel и набор связанных PhotoInsertViewModels

public async Task<Log> AddAsync(LogInsertViewModel logVm, ICollection<PhotoInsertViewModel> photoVms)
{
   // TODO: Validate that the log & files are correct/complete and applicable to the current session user...

   // If I need to lookup values from collections... (1 hit to DB to get all applicable)
   statusIds = photoVms.Select(x => x.StatusId).ToList();
   var statuses = context.Statuses.Where(x => statusIds.Contains(x.StatusId)).ToList();

   // Alternatively if I know all new photos were going to be associated a "New" status...
   var newStatus = context.Statuses.Single(x => x.Status = Statuses.New);

   // Create a Log.
   var log = new Log
   {
      //.. copy values.


      Photos = photoVms.Select(x => new Photo
      {
         // copy values.
         Status = statuses.Single(s => s.StatusId = x.StatusId); // or newStatus
      }).ToList();
   };
   context.Logs.Add(log);
   context.SaveChanges();
}
...