1

I have a Fixture model :

public partial class Fixture
{
    public int FixtureId { get; set; }
    public string Season { get; set; }
    public byte Week { get; set; }

    //foreign key
    public int AwayTeamId { get; set; }
    //navigation properties
    public virtual Team AwayTeam { get; set; }

    //foreign key
    public int HomeTeamId { get; set; }
    //navigation properties
    public virtual Team HomeTeam { get; set; }

    public byte? AwayTeamScore { get; set; }
    public byte? HomeTeamScore { get; set; }
}

And a Fixture DTO :

public class FixtureDTO
{
    public int Id { get; set; }
    public string Season { get; set; }
    public byte Week { get; set; }
    public string AwayTeamName { get; set; }
    public string HomeTeamName { get; set; }
    public byte? AwayTeamScore { get; set; }
    public byte? HomeTeamScore { get; set; }
}

I am using AutoMapper for the mapping and this is my first attempt using it. Here is my mapping :

CreateMap<Fixture, FixtureDTO>()
            .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.FixtureId))
            .ForMember(dest => dest.AwayTeamName, opt => opt.MapFrom(src => src.AwayTeam.TeamName))
            .ForMember(dest => dest.HomeTeamName, opt => opt.MapFrom(src => src.HomeTeam.TeamName));
        CreateMap<FixtureDTO, Fixture>();

It works fine in taking the Fixture and mapping it to the FixtureDTO which I use to display the data. But when I want to update the data and pass the FixtureDTO back to map it back to Fixture I get an error.

public HttpResponseMessage PutFixture(int id, FixtureDTO fixture)
    {
        if (ModelState.IsValid && id == fixture.Id)
        {
            //do mapping manually here?

            var updated = _repository.UpdateFixture(Mapper.Map<Fixture>(fixture));
            return Request.CreateResponse(updated ? HttpStatusCode.OK : HttpStatusCode.NotFound);
        }
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }

This is the error I get :

Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.

Can anybody help with this?

EDIT : Reverse mapping :

CreateMap<Fixture, FixtureDTO>()
            .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.FixtureId))
            .ForMember(dest => dest.AwayTeamName, opt => opt.MapFrom(src => src.AwayTeam.TeamName))
            .ForMember(dest => dest.HomeTeamName, opt => opt.MapFrom(src => src.HomeTeam.TeamName));
        CreateMap<FixtureDTO, Fixture>()
            .ForMember(dest => dest.FixtureId, opt => opt.MapFrom(src => src.Id))
            .ForMember(dest => dest.AwayTeam.TeamName, opt => opt.MapFrom(src => src.AwayTeamName))
            .ForMember(dest => dest.HomeTeam.TeamName, opt => opt.MapFrom(src => src.HomeTeamName));
Mightymuke
  • 5,094
  • 2
  • 31
  • 42
user517406
  • 13,623
  • 29
  • 80
  • 120

3 Answers3

0

Generally that exception is thrown by EF when it detects one of the following:

Opimistic Concurrency Violation: This usually occurs when the entity you are trying to edit was modified else where during the time you loaded, edited and saved it. (see: Entity Framework: "Store update, insert, or delete statement affected an unexpected number of rows (0).")

An Incorrectly set ID: No ID set for Pks or FKs. I've also seen an exception like this when I mistakenly set an Entity's FK to a value and set the associated object to an object with a different ID.

Most likely this exception is being thrown because of some code in your repository. If you post the code in your repository we might get a better idea of what is causing the exception.

Community
  • 1
  • 1
jstromwick
  • 1,206
  • 13
  • 22
  • This is the relevant code in my repository : public bool UpdateFixture(Fixture fixture) { _db.Entry(fixture).State = EntityState.Modified; try { _db.SaveChanges(); return true; } catch (DbUpdateConcurrencyException) { return false; } } – user517406 Nov 29 '12 at 20:33
  • In that case I would assume that it is probably an issue with your mapping. @lazyberezovsky is right, by default you can't map TO child properties (e.g. `dest.AwayTeam.TeamName`), you would need to write your own custom resolver (https://github.com/AutoMapper/AutoMapper/wiki/Custom-value-resolvers). – jstromwick Nov 29 '12 at 22:19
0

First, you need to create reverse mapping from DTO to entity (provide custom member mappings, if needed):

Mapper.CreateMap<FixtureDTO, Fixture>();

And second - retrieve, map, and update existing entity

if (ModelState.IsValid && id == fixture.Id)
{
    Fixture entity = _repository.FindById(fixture.Id);
    Mapper.Map(fixture, entity); // Use this mapping method!
    var updated = _repository.UpdateFixture(entity);
    // etc
} 
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • The Mapper.Map line gives me the following error : "Missing type map configuration or unsupported mapping.\n\nMapping types:\r\nFixtureDTO -> Fixture_E5E73096DAC63B49875D0DFBD914D9991F1B1DE2AF3D92F26A5A11D99374E380\"}. Why would it be trying to map FixtureDTO to Fixture_E5E73096DAC63B49875D0DFBD914D9991F1B1DE2AF3D92F26A5A11D99374E380? – user517406 Nov 29 '12 at 20:30
  • @user517406 that's what I talked about in `First` part - you need to create map for this mapping. And automapper trying to map FixtureDTO to Fixture, because you need to update your Fixture. – Sergey Berezovskiy Nov 29 '12 at 20:34
  • Doing the reverse mapping from FixtureDTO to Fixture (see edit in original post) results in the error : Expression 'dest => dest.AwayTeam.TeamName' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead. – user517406 Nov 29 '12 at 21:03
  • @user517406 That's because of `AwayTeam` property (same with HomeTeam - because it is object of type Team). If you don't change team names in UI, then just ignore those properties on reverse mapping (remove mapping for those properties). – Sergey Berezovskiy Nov 29 '12 at 21:33
0

Extension to @lazyberezovsky's answer:

This is an (untested) example of the reverse mapping you might require:

CreateMap<FixtureDTO, Fixture>()
        .ForMember(dest => dest.FixtureId,
                   opt => opt.MapFrom(src => src.Id))
        .ForMember(dest => dest.AwayTeam,
                   opt => opt.MapFrom(src => new Team
                                                 {
                                                     TeamName = src.AwayTeamName
                                                 }))
        .ForMember(dest => dest.HomeTeam,
                   opt => opt.MapFrom(src => new Team
                                                 {
                                                     TeamName = src.HomeTeamName
                                                 }));

I've used this format in the past and its worked fine, but alternatively you could also create specific mappings for team. Eg:

CreateMap<string, Team>()....

Also, I presume you are doing it, but assert that your configuration is valid. Eg:

[Test]
public void AutoMapper_Configuration_IsValid()
{
    Mapper.Initialize(m => m.AddProfile<MyProfile>());
    Mapper.AssertConfigurationIsValid();
}
Mightymuke
  • 5,094
  • 2
  • 31
  • 42
  • I think this is on the right track, but I have a problem with the fact that I have more than 1 team object, and the mapping does not map the TeamId so I end up with 2 teamIds of 0, which results in this error : 'An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key'. Do I need to go into the database and also map the TeamIds? – user517406 Nov 30 '12 at 09:18
  • There wasn't enough information in the question to define how `Team` should be mapped, so I simply adapted your mapping. You have a couple of options, either get the `Team.ID` in the original request and persist it via a hidden field in the view, or query the database using the team name to retrieve the ID again during postback. – Mightymuke Nov 30 '12 at 10:12