AutoMapper + nullable foreign key + Entity Framework Core

2

Status Update (12/19/18)

The problem is that when Map<Product>(cmd) executes, AutoMapper maps a new Color type (with null values) to Product.


Question:
Could this be a bug when AutoMapper tries to “flatten’ the ColorSystemName member?


ProductCmd.cs

public class ProductCmd
{
    public virtual int? ColorId { get; set; }
    public virtual ColorCmd Color { get; set; }
    public virtual string ColorSystemName { get; set; }
}

I found this by removing the Color navigation property from Product and ProductCmd and executing AssertConfigurationIsValid:

Startup.cs

public class Startup
{
    public void Configure(IApplicationBuilder app, 
                          IHostingEnvironment env, 
                          IMapper autoMapper)
    {
        autoMapper.ConfigurationProvider.AssertConfigurationIsValid();
    }
}

Now, I get:

AutoMapper.AutoMapperConfigurationException
HResult=0x80131500
Source=AutoMapper
StackTrace: Cannot evaluate the exception stack trace

‘AutoMapperConfigurationException: Unmapped members were found’.

This problem does not occur when I remove the ColorSystemName member:

ProductCmd.cs

public class ProductCmd
{
    public virtual int? ColorId { get; set; }
    public virtual ColorCmd Color { get; set; }
}

Question:
Did AutoMapper instantiate a new Color type while trying to flatten ColorSystemName in the original configuration which contained a null Color navigation property?


Original Post

I want AutoMapper to map a null (EF Core navigation) property Color from ProductCmd to Product.

I have tried using a single navigation property as well as a fully-defined relationship (including an inverse navigation property).

Product.cs

public class Product
{
    public virtual int? ColorId { get; set; }
    public virtual Color Color { get; set; }
}

ProductCmd.cs

public class ProductCmd
{
    public virtual int? ColorId { get; set; }
    public virtual ColorCmd Color { get; set; }
    public virtual string ColorSystemName { get; set; }
}

Color.cs

public class Color
{
    public virtual int Id { get; set; }
    public virtual string SystemName { get; set; }
}

ColorCmd.cs

public class ColorCmd
{
    public virtual int Id { get; set; }
    public virtual string SystemName { get; set; }
}

ProductTypeConfiguration.cs

public class ProductTypeConfiguration : IEntityTypeConfiguration<Product>
{
    public void Configure(EntityTypeBuilder<Product> builder)
    {
        builder.ToTable("Product", "dbo");
        builder.HasKey(a => a.Id);
        builder.HasOne(a => a.Color)
               .WithMany()
               .HasForeignKey(s => s.ColorId)
               .OnDelete(DeleteBehavior.ClientSetNull)
               .IsRequired(false);
    }
}

ColorTypeConfiguration.cs

public class ColorTypeConfiguration : IEntityTypeConfiguration<Color>
{
    public void Configure(EntityTypeBuilder<Color> builder)
    {
        builder.ToTable("Color", "dbo");
        builder.HasKey(a => a.Id);
    }
}

InitialInventoryDbMigration.cs

public partial class InitialInventoryDbMigration : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Color",
            schema: "dbo",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                SystemName = table.Column<string>(maxLength: 256, nullable: false),
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Color", x => x.Id);
            });

        migrationBuilder.CreateTable(
            name: " Product",
            schema: "dbo",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                ColorId = table.Column<int>(nullable: true),
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_ Product", x => x.Id);
                table.ForeignKey(
                    name: "FK_Product_Color_ColorId",
                    column: x => x.ColorId,
                    principalSchema: "dbo",
                    principalTable: " Color",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
            });

    }

Startup.cs

public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        _services.AddAutoMapper(cfg =>
            {
                cfg.AllowNullDestinationValues = true;
            },
            new[] 
                    typeof(AutoMapperProfiles_Inventory)
            });
    }
}

AutoMapperProfiles_Inventory.cs

public class AutoMapperProfiles_Inventory
{
    public List<Profile> GetProfiles()
    {
        var profiles = new List<Profile>()
        {
            new AutoMapperProfile_Inventory_Cmd()
        };
        return profiles;
    }
}

AutoMapperProfile_Inventory_Cmd.cs

using features = Module.Inventory.ApplicationCore.BLL.Domain.Features;
using pocos = Module.Inventory.ApplicationCore.BLL.Domain.Entities;
using AutoMapper;

namespace Dcs.NetCore.Module.Inventory.ApplicationCore.BLL.Domain.Entities.Mappers.Profiles
{
    public class AutoMapperProfile_Inventory_Cmd : Profile
    {
        public AutoMapperProfile_Inventory_Cmd()
        {
            CreateMap<pocos.Color, features.Colors.Cmd>().ReverseMap();
            CreateMap<pocos.Product, features.Products.Cmd>()
            .ReverseMap();
        }
    }
}

Libraries utilized:

  • Microsoft.EntityFrameworkCore v.2.1.4
  • Microsoft.NETCore.App v.2.1
  • AutoMapper.Extensions.Microsoft.DependencyInjection v.6.0.0
  • AutoMapper v.8.0.0

Problem also occurs with:

  • AutoMapper.Extensions.Microsoft.DependencyInjection v.5.0.1
  • AutoMapper v.7.0.1

I could not find a solution to this scenario in AutoMapper Documentation or various online resources.

Here is sample code showing the mapping results and error:

public class Example
{
    private readonly IMapper _mapper;
    private readonly IProductRepository _repository;

    public Example(IMapper mapper, IProductRepository repository)
    {
        _mapper = mapper;
        _repository = repository;
    }

    public void Add()
    {
        var cmd = new Cmd()
        {
            Color = null,
            ColorId = null,
            Id = 0,
            ProductBrand = null,
            ProductBrandId = 2
        };

        var dao = _mapper.Map<Product>(cmd);
        //
        //
        // at this point:
        //
        //
        // dao.Color == Color(with null values)
        // dao.Color.Id == 0
        // dao.ColorId == null
        // dao.Id == 0
        // dao.ProductBrand == ProductBrand(with null values)
        // dao.ProductBrand.Id == 0
        // dao.ProductBrandId == 1
        //
        //
        _repository.AddAsync(dao);
        //
        // executes dbSet.Add(dao);
        //
        //
        // at this point:
        //
        // dao.Color == Color(with null values)
        // dao.Color.Id == -2147482643
        // dao.ColorId == -2147482643
        // dao.Color.Products[0].Color.Id == -2147482643
        // dao.Color.Products[0].Id == -2147482647
        // dao.Id == -2147482647
        // dao.ProductBrand == ProductBrand(with correct values)
        // dao.ProductBrand.Id == 1
        // dao.ProductBrandId == 1
        // 
        //
        _repository.SaveChangesAsync();
        //
        // executes _dbContext.SaveChangesAsync();
        //
        //
        // at this point:
        //
        // System.Data.SqlClient.SqlException(0x80131904): 
        // "Cannot insert the value NULL into column 'AddedBy', 
        // table 'dbo.Color'; 
        // column does not allow nulls. 
        // INSERT fails.\r\nThe statement has been terminated."            
        //
    }

}

c#
entity-framework-core
automapper
asked on Stack Overflow Dec 1, 2018 by RandyDaddis • edited Dec 20, 2018 by RandyDaddis

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0