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

时间:2013-11-11 21:06:22

标签: c# sql-server entity-framework ef-code-first

我有3张桌子, 1. AttributeTypes(列:AttributeId(PK),AttributeName,..) 2.位置(列:locationId(PK),LocationName,...) 3. LocationAttributeType(列:locationId(FK),AttributeId(FK))

每当我尝试从GUI插入新的位置记录及其属性类型时,它应该为Table- 位置 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();
    }

我在这里做错了什么?

3 个答案:

答案 0 :(得分:1)

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。如果你真的想这样做,你需要明确地控制实体状态。 (当你想使用通用存储库时,这样做并不是那么简单,这就是为什么我专注于这种方法。)

答案 1 :(得分:1)

谢谢大家的建议。 我必须在这里摆脱我的通用存储库以保存我的上下文更改并手动执行,如下所示,

private IEnumerable<int> AddLocationEntities(IEnumerable<LocationDataModel> locations)
    {
        var results = new List<int>();
        foreach (LocationDataModel l in locations)
        {
            var entity = _mapper.Map<LocationDataModel, Location>(l);//you can map manually also
            var AttributeCode = l.AssignedAttributes.FirstOrDefault().AttributeTypeId;
            using (MyContext c = new MyContext())
            {
                var attr = c.AttributeTypes.Where(a => a.Id == AttributeTypeId ).ToList();
                entity.AttributeTypes = attr;
                c.Locations.Add(entity);
                c.SaveChanges();
                var locid = entity.Id;
                results.Add(locid);
            }

        }
      return results;
    }

答案 2 :(得分:0)

在您else的{​​{1}}声明中,您应该添加

Upsert