首页 > 解决方案 > 阻止 EF 添加或更新某些子实体

问题描述

tl;博士;

在实体框架 6 中是否可以指示 EF 不添加或更改某些实体,而是Attach在保存时将它们分配给父级时隐含它们?

背景

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

赞助

实体

地址

国家

状态

我将以下 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() 时,它会添加重复的州和国家/地区。数据库中的 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 保存您的父记录时隐式附加您自己-不要创建新记录,您是双亲的儿子。 !”

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

万一这还不够阅读

这是实际的映射

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
}

谢谢

标签: c#entity-frameworkautomapper

解决方案


推荐阅读