Entity Framework, AutoMapper, обработка обновлений сущностей

Я только недавно начал использовать Entity Framework 1.0 и считаю, что начинаю чувствовать боль, о которой все говорят. Я пытаюсь использовать передовой опыт, поэтому у меня есть набор DTO, который сопоставляется с моими объектами и из них через AutoMapper.

Настоящая загвоздка - это когда я пытаюсь обновить объект. Первая проблема заключалась в том, что я не мог найти способ создать новую сущность, перенести данные из моего DTO, и при этом объект ObjectContext все еще осознавал, что он был изменен. Я использовал следующий код:

public VideoDTO UpdateVideo(VideoDTO pVideo)
        {
            Video video = new Video();
            Mapper.Map(pVideo, video);
            context.Attach(video); //Successfully attaches
            context.ApplyPropertyChanges("Videos", video);  // no changes made as far as entity knows b/c it was attached in it's updated state
            context.SaveChanges(); //doesn't save the entity                
            return pVideo;
        }

Затем я подумал, что, возможно, мне нужно сначала просто получить объект из базы данных, присоединиться к контексту, вызвать метод Map в Mapper, а затем вызвать SaveChanges. Вот что я сделал:

    public VideoDTO UpdateVideo(VideoDTO pVideo)
    {
        Video video = context.Videos.Where(v => v.VideoId == pVideo.VideoId).FirstOrDefault();
        Mapper.Map(pVideo, video); //Error here: Can't change VideoId value on Video entity
        //context.Attach(video);
        //context.ApplyPropertyChanges("Videos", video);
        context.SaveChanges();

        return pVideo;
    }

Теперь мы подходим к прекрасной проблеме EF, заключающейся в том, что нельзя изменять свойство VideoId, потому что оно используется свойством EntityKey в сущности Video. Прекрасный. Я настроил сопоставления так, чтобы при сопоставлении моего DTO с EF Entity свойство EntityKey получало значение. Теперь мне нужен способ сделать исключение из этого правила сопоставления, но я не знаю, с чего начать. Полагаю, я мог бы создать совершенно новое правило сопоставления прямо в этом методе и настроить игнорирование свойств EntityKey и VideoId, но это кажется довольно небрежным. Более того, я не уверен, что созданное на этом этапе сопоставление приживется. Если он переопределит первоначальную настройку, которая позволила DTO сопоставить значение с EntityKey на объекте, это приведет к обратным результатам совершенно по-другому.

У кого-нибудь есть идея получше?


person jason    schedule 29.01.2010    source источник


Ответы (5)


AutoMapper

Ваша первая проблема заключается в том, что, насколько мне известно, AutoMapper не предназначен для перехода от DTO-> Entity only Entity-> DTO. Это могло измениться недавно, поэтому я не совсем уверен. См. Эту ссылку для получения дополнительной информации о том, для чего предназначен automapper: Пример двустороннего сопоставления

Сопоставление PK

Вы говорите: «Правило сопоставления прямо в этом методе и настройте свойства EntityKey и VideoId, которые будут игнорироваться, но это кажется довольно небрежным»

Я не думаю, что это вообще небрежно. Вам действительно не следует прикасаться к EntityKey / PK после того, как он был сохранен, и, вероятно, следует каким-то образом кодифицировать его статичность.

Entity Framework

«Теперь мы подходим к прекрасной проблеме EF, заключающейся в том, что нельзя изменять свойство VideoId, потому что оно используется свойством EntityKey в сущности Video. Прекрасно».

Прекрасный? EF не заставляет вас не обновлять ПК. Внутри сгенерированных моделей есть проверка изменения свойств внутри установщика ваших ключей. Решением было бы изменить сгенерированный код. В зависимости от волатильности вашей модели это может оказаться непрактичным, но это вариант.

person John Farrell    schedule 31.01.2010
comment
jfar: Спасибо за ответ. Прежде всего, что вы имеете в виду под AFAIK AutoMapper? Я имею в виду AutoMapper здесь: code.google.com/p/automapperhome. Что касается сопоставления PK, я говорю об определении всех сопоставлений в одном месте и не засорять свой код операторами .CreateMap по всему коду. Проблема здесь в том, что мне нужно косвенно сопоставить мой DTO с моей сущностью по-разному, в зависимости от того, создаю ли я новую запись в своей базе данных или обновляю запись в своей базе данных. Подробнее о самом AutoMapper через секунду ... заканчиваются символы ... - person jason; 01.02.2010
comment
Хорошо, вы упомянули, что Automapper не предназначался для перехода от DTO - ›EF Entity, а предназначался только для EF -› DTO. Если это так, как я должен обновлять свою базу данных с помощью объектов EF, если объекты, используемые в моем представлении и контроллере, являются объектами DTO? Я не пытаюсь быть снисходительным или что-то в этом роде. Я просто пытаюсь найти правильный способ обработки вставок и обновлений, не привязывая свое приложение к конкретной реализации доступа к данным. Я открыт для любых предложений, которые могут у вас возникнуть. - person jason; 01.02.2010
comment
Я просматривал свои открытые вопросы и увидел этот. После прочтения вашего ответа И связанной статьи (RTFM ... Я знаю ...) все становится намного понятнее. На самом деле было довольно свободно - не заставлять мой DTO просто имитировать соглашения, предписанные моей моделью домена. Я понимаю, что я вкладывал в них много наполнителя, которого просто не было. Спасибо за информативный ответ. Извините, что мне потребовалось так много времени, чтобы получить это. - person jason; 09.03.2010

Попробуйте сопоставить существующий объект:

entity = Mapper.Map<MyDTO, NyEntity>(dto, entity); 

И оставьте Ignore () на месте.

http://groups.google.com/group/automapper-users/browse_thread/thread/24a90f22323a27bc?fwc=1&pli=1

person ishakkulekci    schedule 29.12.2011

Я в том же сценарии. Единственное решение, которое я получил, - это игнорировать поле PK в сопоставлении из DTO -> Entity.

Такое правило может быть достигнуто с помощью следующей строки кода во время настройки Automapper:

 Mapper.CreateMap<MyDTO, MyEntity>().ForMember("EntityPK",r=>r.Ignore());

Насколько мне известно, единственный способ заставить EF работать с отдельными объектами - это сопоставить DTO с объектом, который вы получили из БД до SaveChanges (как вы это делали в примере).

person Bitbreaker    schedule 05.06.2010

Это может помочь, если вы не хотите помещать .Ignore()s на каждую сущность, которую вы хотите сопоставить.

http://www.prosoftnearshore.com/blog/post/2012/03/14/Using-AutoMapper-to-update-Entity-Framework-properties.aspx

По сути, вы должны настроить AutoMapper, чтобы игнорировать все свойства Entity, которые не являются скалярными:

AutoMapper.Mapper.CreateMap<EntityType, EntityType>()
    .ForAllMembers(o => {
         o.Condition(ctx =>
             {
                 var members = ctx.Parent.SourceType.GetMember(ctx.MemberName); // get the MemberInfo that we are mapping

                if (!members.Any())
                    return false;
                return members.First().GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).Any(); // determine if the Member has the EdmScalar attribute set
            });
    });

Возможно, можно добавить некоторую дополнительную работу, чтобы избежать сброса, если свойство является PK (свойство в экземпляре EdmScalarPropertyAttribute (EntityKey == true?) сообщает вам об этом).

person Mauricio Morales    schedule 14.03.2012

Имейте в виду, что пример, предоставленный «Маурисио Моралес», будет работать, только если вы не используете префиксы. Если вы их используете, вам нужно немного изменить приведенный выше код более или менее следующим образом:

    Mapper.CreateMap<tempOR_Order, OR_Order>()
        .ForMember(m => m.OR_ID, exp => exp.Ignore())
        .ForMember(m => m.OR_CU_ID, exp => exp.Ignore())
        .ForAllMembers(o => o.Condition(ctx =>
        {
            var members = ctx.Parent.SourceType.GetMember(ctx.MemberName); // get the MemberInfo that we are mapping

            if (!members.Any())
            {
                members = ctx.Parent.SourceType.GetMember("temp" + ctx.MemberName);
                if (!members.Any())
                    return false;
            }

            return members.First().GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).Any(); // determine if the Member has the EdmScalar attribute set
        }));

То есть вам нужно включить дополнительные проверки в оператор if (!members.Any()). Без этого функция возвращает false и отображение не будет работать.

person Mariusz Gorzoch    schedule 09.08.2012