阻止EF添加或更新某些子实体

时间:2018-07-27 10:07:30

标签: c# entity-framework automapper

tl; dr;

在Entity Framework 6中是否可以指示EF不要添加或更改某些实体,而是在保存时分配给父级时隐式Attach

背景

我正在使用AutoMapper映射到DTO。我有一个这样的模型:

赞助人

  • SponsorId
  • EntityId
  • (其他字段)...

实体

  • EntityId
  • 地址(地址的ICollection)
  • (其他字段)...

地址

  • AddressId
  • ...(其他字段)...
  • StateId
  • 状态
  • CountryId
  • 国家
  • ...(其他字段)

国家

  • CountryId
  • 国家代码
  • CountryName

状态

  • StateId
  • StateCode
  • StateName

我将以下JSON主体作为DTO发送到Web API,并通过AutoMapper转换为实体(带有子代):

{
        ... other stuff ...

        "addresses": [
            {
                "countryId": 1,
                "stateId": 34,
                "country": {
                    "countryId": 1,
                    "countryCode": "USA",
                    "countryName": "United States"
                },
                "state": {
                    "stateId": 34,
                    "stateCode": "NV",
                    "stateName": "Nevada"
                }
            }
        ]
    }

当我将JSON / DTO映射到我的EF模型时,我逐步执行代码,一切似乎都很好-ID是正确的。但是,当我在上下文中调用SaveChanges()时,它将添加重复的State和Country。数据库中的StateId / CountryId字段是主键,并在EF中以这种方式指定。

如果我做这样的事情,它就可以工作(只要我在同一国家/地区没有两个地址):

foreach (var address in sponsor.Entity.Addresses)
{
   address.State = db.Attach(address.State);
   address.Country = db.Attach(address.Country);
}

如果我确实有两个具有相同州或国家/地区的地址,它将再次进行迭代,并再次调用该“附加”代码并引发异常。

因此,我建立了一个缓存机制:

Dictionary<string, State> _stateCache = new Dictionary<string, State>();

...

foreach (var address in sponsor.Entity.Addresses)
{
    if (!_stateCache.ContainsKey(address.State.StateCode))
        _stateCache.Add(address.State.StateCode, db.Attach(address.State));

    address.State = _stateCache[address.State.StateCode];

     // (... plus identical logic for countries ...)
}

对于这些表和许多其他表,这是很多(看似)不必要的管道。有没有更简单的方法?还是我在这里做的主要错误是导致这种现象的原因?

我只想简单地说:“州和国家(及其他十几个国家)-分配为子属性时,当EF保存您的父记录时隐式地附加您自己-不要创建一个新的记录,您是bi的儿子..枪!”

我猜,对于实体列表,基本上是只读模式...

如果阅读量不足

这是实际的映射

CreateMap<SponsorDto, Entity>()
    .ForMember(x => x.EntityId, y => y.MapFrom(z => z.EntityId))
    .ForMember(x => x.Addresses, y => y.MapFrom(z => z.Addresses))
    .ForMember(x => x.Name, y => y.MapFrom(z => z.Name));

CreateMap<SponsorDto, Sponsor>()
    .ForMember(x => x.SponsorId, y => y.MapFrom(z => z.EntityId))
    .ForMember(x => x.Entity, y => y.MapFrom(z => z))
    .ReverseMap();

CreateMap<Sponsor, SponsorDto>()
    .ForMember(x => x.EntityId, y => y.MapFrom(z => z.SponsorId))
    .ForMember(x => x.Addresses, y => y.MapFrom(z => z.Entity.Addresses))
    .ForMember(x => x.Name, y => y.MapFrom(z => z.Entity.Name))

CreateMap<Address, AddressDto>()
    .ForMember(x => x.CodeState, y => y.MapFrom(z => z.CodeState))
    .ForMember(x => x.StateId, y => y.MapFrom(z => z.CodeState.StateId))
    .ForMember(x => x.CodeCountry, y => y.MapFrom(z => z.CodeCountry))
    .ForMember(x => x.CountryId, y => y.MapFrom(z => z.CodeCountry.CountryId))
    .EqualityComparison((x, y) => x.AddressId == y.AddressId); // from automapper.collections

CreateMap<AddressDto, Address>()
    .ForMember(x => x.CodeState, y => y.MapFrom(z => z.CodeState))
    .ForMember(x => x.CodeCountry, y => y.MapFrom(z => z.CodeCountry))
    .EqualityComparison((x, y) => x.AddressId == y.AddressId);

地址的EF配置

public AddressConfiguration(string schema)
{
    ToTable("Address", schema);
    HasKey(x => x.AddressId);

    Property(x => x.AddressId).HasColumnName(@"AddressId").HasColumnType("int").IsRequired().HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
    Property(x => x.CountryId).HasColumnName(@"CountryId").HasColumnType("int").IsRequired();
    Property(x => x.StateId).HasColumnName(@"StateId").HasColumnType("int").IsOptional();

    // Foreign keys
    HasOptional(a => a.CodeState).WithMany().HasForeignKey(c => c.StateId).WillCascadeOnDelete(false); // FK_Address_State
    HasRequired(a => a.CodeCountry).WithMany().HasForeignKey(c => c.CountryId).WillCascadeOnDelete(false); // FK_Address_Country
}

谢谢

0 个答案:

没有答案