Entity to Model и объекты внешнего ключа - PullRequest
1 голос
/ 21 марта 2012

У меня есть объект EF под названием SportDivision. Для простоты я не буду включать все поля, только те, которые имеют отношение:

[Table("SportDivision", Schema = "dbo")]
public class SportDivision: BaseReferenceEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int SportId { get; set; }

    [ForeignKey("SportId")]
    public virtual Sport Sport { get; set; }
}

Итак, у него есть SportId и внешний ключ, указывающий на таблицу Sport.

Теперь я не могу просто использовать объект EF в своих представлениях, поэтому у меня есть класс модели, который сопоставлен с SportDivision и называется SportDivisionModel:

public class SportDivisionModel: BaseReferenceModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int SportId { get; set; }

    //Read only fields
    public string Sport { get; set; }
}

Я использую Autopper для передачи данных из SportDivision в SportDivisionModel и наоборот. Отображение выглядит так:

Mapper.CreateMap<SportDivision, SportDivisionModel>()
      .ForMember(x => x.Sport, c => c.MapFrom(e => e.Sport.Name));
Mapper.CreateMap<SportDivisionModel, SportDivision>();

И у меня есть универсальный сервис, который CRUDs и переводит данные от объекта к модели или от модели к объекту. Все отлично работает, кроме Create, функция которого показана ниже:

public TModel Create<TModel, TEntity>(TModel entry)
        where TModel : BaseReferenceModel
        where TEntity : BaseReferenceEntity
    {
        var dm = ServiceLocator.Current.GetInstance<ICrudService<TEntity>>();
        var raw = Mapper.Map<TModel, TEntity>(entry);
        var created = dm.CreateOrUpdate(raw);
        return Mapper.Map<TEntity, TModel>(dm.FindById(created.Id));
    }

В самой последней строке, где вы видите dm.FindById(created.Id), он возвращает объект SportDivisionModel без спортивного имени. Исключение с нулевой ссылкой найдено в .ForMember(x => x.Sport, c => c.MapFrom(e => e.Sport.Name));. Спорт не загружался после того, как запись была только что создана в базе данных.

Я отладил код и вижу, что запись с действительным SportId заносится в таблицу SportDivision моей базы данных, но когда я пытаюсь перенести ее в свое приложение MVC, он не получает все информация.

Это только проблема при создании. Если я просто получаю данные из базы данных, не создавая их заранее, или если я редактирую информацию, то поле Спорт в моем объекте модели заполняется. Я не знаю, почему это происходит, и я не могу использовать .Include в моем общем вызове службы (потому что не все классы BaseReferenceEntity имеют внешний ключ, указывающий на Sport).

Пожалуйста, сообщите. Заранее спасибо.

1 Ответ

2 голосов
/ 22 марта 2012

Я должен сыграть в Шерлока Холмса и попытаться вывести то, что могло бы быть содержанием CreateOrUpdate и FindById из показаний в вашем вопросе:

  • Вы говорите, что не используете Include из-за универсального сервиса. Я предполагаю, что вы также не используете явную загрузку (Load), потому что вы столкнетесь с той же проблемой, что вы не можете сделать ее общей.

  • Вывод: поскольку свойство навигации Sport в SportDivision загружается в определенных сценариях (Правка), это может произойти только из-за отложенной загрузки. Вывод подтверждается тем фактом, что свойство Sport помечено как virtual.

  • Ленивая загрузка зависит от прокси. Если ваша SportDivision сущность является прокси, тогда

    1. либо загрузка Sport объекта работает
    2. или вы получите исключение, сообщающее, что контекст уже удален (если вы удалили контекст)
  • Номер 2 не соответствует действительности -> Вывод: Номер 1 должен иметь место , если предварительное условие выполнено

  • Но номер 1 тоже не тот случай (загрузка Sport делает не работает)

  • Вывод: предварительное условие, что ваша сущность SportDivision является прокси, не соответствует действительности.

  • Итак: SportDivision не является прокси. Может ли это означать, что у вас отключена отложенная загрузка? Нет: поскольку вы говорите, что редактирование работает, это означает, что при загрузке сущностей из базы данных они загружаются в качестве прокси и поддерживают отложенную загрузку.

  • Редактирование работает, отложенная загрузка не отключена, но создание нового объекта не работает так, как загружается объект Sport, когда вы продолжаете использовать вновь созданный объект.

  • Вывод: Ваш вновь созданный объект (возвращенный из CreateOrUpdate) не является прокси-сервером, и CreateOrUpdate выглядит примерно так:

    public TEntity CreateOrUpdate(TEntity raw) where TEntity : class
    {
        if (blabla)
            ; //update
        else
        {
            context.Set<TEntity>().Add(raw);
            context.SaveChanges();
    
            return raw;
        }
    }
    

    и FindById это просто:

    public TEntity FindById(int id)
    {
        return context.Set<TEntity>().Find(id);
    }
    
  • Поскольку вы передаете raw непосредственно в Add метод DbSet<T>, возникает вопрос, откуда берется raw и как он создается.

  • Очевидно, AutoMapper создает объект после этой строки: var raw = Mapper.Map<TModel, TEntity>(entry);

  • Как Automapper создает сущность? Возможно, позвонив по номеру new TEntity или используя некоторый код отражения, например Activator.CreateInstance или ...

  • На самом деле не имеет значения, как, но наверняка AutoMapper не создает экземпляр прокси-сервера Entity Framework, который должен был быть создан:

    var entity = context.Set<TEntity>().Create();
    

Если все это правда, я чувствую себя полностью подавленным AutoMapper и общими излишествами. Если бы все это не было общим, мы могли бы решить проблему следующим образом:

context.Set<SportDivision>().Add(raw);
context.SaveChanges();

context.Entry(raw).Reference(r => r.Sport).Load();

Вместо этого мы должны попробовать некоторые уродливые трюки:

context.Set<TEntity>().Add(raw);
context.SaveChanges();

context.Entry(raw).State = EntityState.Detached;
// We hope that raw is now really out of the context

raw = context.Set<TEntity>().Find(raw.Id);
// raw must be materialized as a new object -> Hurray! We have a proxy!

return raw;

(Я действительно не уверен, что приведенный выше трюк Detached работает. Кроме того, вы вынуждены перезагрузить сущность из базы данных, которую вы только что создали и сохранили, что как-то глупо.)

Потенциальный трюк № 2 (без перезагрузки из БД, но за счет того, что он является еще более уродливым шагом):

context.Set<TEntity>().Add(raw);
context.SaveChanges();

context.Entry(raw).State = EntityState.Detached;
// We hope that raw is now really out of the context

var anotherRaw = context.Set<TEntity>().Create();  // Proxy!
anotherRaw.Id = raw.Id;
context.Set<TEntity>().Attach(anotherRaw);
context.Entry(anotherRaw).CurrentValues.SetValues(raw);
context.Entry(anotherRaw).State = EntityState.Unchanged;

return anotherRaw; // Proxy! Lazy loading will work!

Имеет ли AutoMapper функцию «пользовательского распределителя или инстанциатора» и могут ли быть предоставлены пользовательские данные (контекст)? Тогда будет возможность позволить AutoMapper вызывать context.Set<TEntity>().Create();. Или можно создать экземпляр объекта вручную, передать его AutoMapper, и AutoMapper просто обновит свойства объекта?

Кстати: линия ...

context.Entry(anotherRaw).CurrentValues.SetValues(raw);

... является своего рода встроенным в EF "AutoMapper". Параметр SetValues является общим System.Object (может быть вашим ...Model объектом), и метод отображает значения свойств из предоставленного объекта в свойства присоединенных сущностей с помощью идентичных имен свойств. Возможно, вы можете использовать эту функцию как-то вместо того, чтобы использовать отображение из модели в сущность, выполненное AutoMapper.

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