英孚自动映射器.更新嵌套集合 [英] EF & Automapper. Update nested collections

查看:16
本文介绍了英孚自动映射器.更新嵌套集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试更新 Country 实体的嵌套集合(Cities).

I trying to update nested collection (Cities) of Country entity.

只是简单的实体和 dto:

Just simple enitities and dto's:

// EF Models
public class Country
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<City> Cities { get; set; }
}

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CountryId { get; set; }
    public int? Population { get; set; }

    public virtual Country Country { get; set; }
}

// DTo's
public class CountryData : IDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<CityData> Cities { get; set; }
}

public class CityData : IDTO
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CountryId { get; set; }
    public int? Population { get; set; }
}

以及代码本身(为简单起见,在控制台应用程序中进行了测试):

And code itself (tested in console app for the sake of simplicity):

        using (var context = new Context())
        {
            // getting entity from db, reflect it to dto
            var countryDTO = context.Countries.FirstOrDefault(x => x.Id == 1).ToDTO<CountryData>();

            // add new city to dto 
            countryDTO.Cities.Add(new CityData 
                                      { 
                                          CountryId = countryDTO.Id, 
                                          Name = "new city", 
                                          Population = 100000 
                                      });

            // change existing city name
            countryDTO.Cities.FirstOrDefault(x => x.Id == 4).Name = "another name";

            // retrieving original entity from db
            var country = context.Countries.FirstOrDefault(x => x.Id == 1);

            // mapping 
            AutoMapper.Mapper.Map(countryDTO, country);

            // save and expecting ef to recognize changes
            context.SaveChanges();
        }

这段代码抛出异常:

操作失败:无法更改关系,因为一个或多个外键属性不可为空.当对关系进行更改时,相关的外键属性将设置为空值.如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象.

The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.

即使上次映射后的实体看起来还不错,并且正确反映了所有更改.

even though entity after last mapping seems just fine and reflects all changes properly.

我花了很多时间寻找解决方案,但没有得到任何结果.请帮忙.

I've spent much time finding solution but got no result. Please help.

推荐答案

问题是您从数据库中检索的 country 已经有一些城市.当您像这样使用 AutoMapper 时:

The problem is the country you are retrieving from database already has some cities. When you use AutoMapper like this:

// mapping 
AutoMapper.Mapper.Map(countryDTO, country);

AutoMapper 正在执行诸如正确创建 IColletion 的操作(在您的示例中使用一个城市),并将这个全新的集合分配给您的 country.Cities 属性.

AutoMapper is doing something like creating an IColletion<City> correctly (with one city in your example), and assigning this brand new collection to your country.Cities property.

问题是 EntityFramework 不知道如何处理旧的城市集合.

The problem is EntityFramework doesn't know what to do with the old collection of cities.

  • 是否应该删除您的旧城市并仅采用新系列?
  • 是否应该合并两个列表并将两者都保存在数据库中?

事实上,EF 无法为您做出决定.如果你想继续使用 AutoMapper,你可以像这样自定义你的映射:

In fact, EF cannot decide for you. If you want to keep using AutoMapper, you can customize your mapping like this:

// AutoMapper Profile
public class MyProfile : Profile
{

    protected override void Configure()
    {

        Mapper.CreateMap<CountryData, Country>()
            .ForMember(d => d.Cities, opt => opt.Ignore())
            .AfterMap(AddOrUpdateCities);
    }

    private void AddOrUpdateCities(CountryData dto, Country country)
    {
        foreach (var cityDTO in dto.Cities)
        {
            if (cityDTO.Id == 0)
            {
                country.Cities.Add(Mapper.Map<City>(cityDTO));
            }
            else
            {
                Mapper.Map(cityDTO, country.Cities.SingleOrDefault(c => c.Id == cityDTO.Id));
            }
        }
    }
}

用于CitiesIgnore() 配置使AutoMapper 只保留EntityFramework 构建的原始代理引用.

The Ignore() configuration used for Cities makes AutoMapper just keep the original proxy reference built by EntityFramework.

然后我们只需使用 AfterMap() 来调用一个动作,完全按照您的想法进行:

Then we just use AfterMap() to invoke an action doing exactly what you thought:

  • 对于新城市,我们从 DTO 映射到 Entity(AutoMapper 创建一个新的实例)并将其添加到国家/地区的集合中.
  • 对于现有城市,我们使用 Map 的重载,其中我们将现有实体作为第二个参数传递,城市代理作为第一个参数,因此 AutoMapper 仅更新现有实体的属性.
  • For new cities, we map from DTO to Entity (AutoMapper creates a new instance) and add it to country's collection.
  • For existing cities, we use an overload of Map where we pass the existing entity as the second parameter, and the city proxy as first parameter, so AutoMapper just updates the existing entity's properties.

那么你可以保留你的原始代码:

Then you can keep your original code:

using (var context = new Context())
    {
        // getting entity from db, reflect it to dto
        var countryDTO = context.Countries.FirstOrDefault(x => x.Id == 1).ToDTO<CountryData>();

        // add new city to dto 
        countryDTO.Cities.Add(new CityData 
                                  { 
                                      CountryId = countryDTO.Id, 
                                      Name = "new city", 
                                      Population = 100000 
                                  });

        // change existing city name
        countryDTO.Cities.FirstOrDefault(x => x.Id == 4).Name = "another name";

        // retrieving original entity from db
        var country = context.Countries.FirstOrDefault(x => x.Id == 1);

        // mapping 
        AutoMapper.Mapper.Map(countryDTO, country);

        // save and expecting ef to recognize changes
        context.SaveChanges();
    }

这篇关于英孚自动映射器.更新嵌套集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆