c# – EF将重复记录添加到查找/引用表中

我有3张桌子,

1. AttributeTypes(列:AttributeId(PK),AttributeName,..)

2.位置(列:locationId(PK),LocationName,…)

3. LocationAttributeType(列:locationId(FK),AttributeId(FK))

每当我尝试从GUI插入新的位置记录及其属性类型时,它应该为Table-Location和LocationAttributeType创建新记录.但EF试图在Table- AttributeTypes中添加新记录,它只是用作参考表,不应在其中添加新的/重复的记录.我怎么能防止这种情况?

这是我的代码,

GUI发送的模型是,

public class LocationDataModel
{
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public string Code { get; set; }

    [DataMember]
    public List<AttributeTypeDataModel> AssignedAttributes = new List<AttributeTypeDataModel>();
}
public class AttributeTypeDataModel
{
    protected AttributeTypeDataModel() {}

    public AttributeTypeDataModel(int id) { this.Id = id; }

    public AttributeTypeDataModel(int id, string name)
        : this(id)
    {
        this.Name = name;
    }

    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public virtual ICollection<LocationDataModel> Locations { get; set; }
  }

由EF创建的实体是,

public partial class Location
{
    public Location()
    {
        this.AttributeTypes = new List<AttributeType>();
    }

    public Location(int campusId, string code)
        : this()
    {
        CampusId = campusId; Code = code;
    }


    public int Id { get; set; }
    public int CampusId { get; set; }
    public string Code { get; set; }
    public virtual ICollection<AttributeType> AttributeTypes { get; set; }

}

public partial class AttributeType
{
    public AttributeType()
    {
        this.Locations = new List<Location>();
    }

    public int AttributeTypeId { get; set; }
    public string AttributeTypeName { get; set; }
    public virtual ICollection<Location> Locations { get; set; }
}

我有以下代码将这些新位置添加到数据库,

     private IEnumerable<TEntity> AddEntities<TModel, TEntity, TIdentityType>
     (IEnumerable<TModel> models, Func<TModel, TIdentityType> primaryKey, 
        IGenericRepository<TEntity, TIdentityType> repository)
        {
        var results = new List<TEntity>();

        foreach (var model in models)
        {
            var merged = _mapper.Map<TModel, TEntity>(model);
            var entity = repository.Upsert(merged);
            results.Add(entity);
        }
        repository.Save();
        return results.AsEnumerable();
    }

我正在使用以下通用存储库来执行与实体相关的操作

public TEntity Upsert(TEntity entity)
    {
        if (Equals(PrimaryKey.Invoke(entity), default(TId)))
        {
            // New entity
            return Context.Set<TEntity>().Add(entity);
        }
        else
        {
            // Existing entity
            Context.Entry(entity).State = EntityState.Modified;
            return entity;
        }
    }

   public void Save()
    {
        Context.SaveChanges();
    }

我在做什么错了?

最佳答案 DbSet< T> .Add()方法附加了整个对象图.您需要向EF指出“参考”实体实际上已存在.有两种简单的方法可以做到这一点:

>不要将导航属性设置为对象.相反,只需将相应的外键属性设置为正确的值即可.
>您需要确保不将同一实体的多个实例加载到对象上下文中.创建上下文后,将完整的AttributeType实体列表加载到上下文中,并创建一个Dictionary<>存储它们.如果要向Location添加属性,请从字典中检索相应的属性.在调用SaveChanges()之前,遍历字典并将每个AttributeType标记为未更改.像这样的东西:

    using (MyContext c = new MyContext())
    {
        c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Fish", AttributeTypeId = 1 });
        c.AttributeTypes.Add(new AttributeType { AttributeTypeName = "Face", AttributeTypeId = 2 });
        c.SaveChanges();
    }

    using (MyContext c = new MyContext())
    {
        Dictionary<int, AttributeType> dictionary = new Dictionary<int, AttributeType>();

        foreach (var t in c.AttributeTypes)
        {
            dictionary[t.AttributeTypeId] = t;
        }

        Location l1 = new Location(1, "Location1") { AttributeTypes = { dictionary[1], dictionary[2] } };
        Location l2 = new Location(2, "Location2") { AttributeTypes = { dictionary[1] } };

        // Because the LocationType is already attached to the context, it doesn't get re-added.
        c.Locations.Add(l1);
        c.Locations.Add(l2);

        c.SaveChanges();
    }

在这种特定情况下,您使用多对多关系,EF自动处理中间表.这意味着您实际上没有在模型中公开FK属性,我上面的第一个建议将不起作用.

因此,您需要使用仍然应该工作的第二个建议,或者您需要放弃中间表的自动处理,而是为它创建一个实体.这将允许您应用第一个建议.你会有以下型号:

public partial class Location
{
    public Location()
    {
        this.AttributeTypes = new List<LocationAttribute>();
    }

    public Location(int campusId, string code)
        : this()
    {
        CampusId = campusId; Code = code;
    }

    public int Id { get; set; }
    public int CampusId { get; set; }
    public string Code { get; set; }
    public virtual ICollection<LocationAttribute> AttributeTypes { get; set; }
}

public partial class LocationAttribute
{
    [ForeignKey("LocationId")]
    public Location Location { get; set; }
    public int LocationId { get; set; }

    public int AttributeTypeId { get; set; }
}

public partial class AttributeType
{
    public int AttributeTypeId { get; set; }
    public string AttributeTypeName { get; set; }
}

使用此方法会导致功能丢失,因为您无法在不进行中间查找的情况下从Location导航到AttributeType.如果你真的想这样做,你需要明确地控制实体状态. (当你想使用通用存储库时,这样做并不是那么简单,这就是我专注于这种方法的原因.)

点赞