【问题标题】:EF adding duplicate records into lookup/reference tableEF 将重复记录添加到查找/引用表中
【发布时间】:2013-11-11 21:06:21
【问题描述】:

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

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

我在这里做错了什么?

【问题讨论】:

  • 不想检查您的代码,因为它非常广泛,但您可能在某处遗漏了Attach
  • 您能在我的代码中查看“Upsert”方法并建议我使用“附加”的正确位置吗?

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


【解决方案1】:

DbSet&lt;T&gt;.Add() 方法在添加时附加整个对象图。您需要向 EF 表明“参考”实体实际上已经存在。有两种简单的方法可以做到这一点:

  • 不要将导航属性设置为对象。相反,只需将相应的外键属性设置为正确的值即可。

  • 您需要确保不会将同一实体的多个实例加载到对象上下文中。创建上下文后,将AttributeType 实体的完整列表加载到上下文中,并创建一个Dictionary&lt;&gt; 来存储它们。当您想将属性添加到 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。如果你真的想这样做,你需要明确地控制实体状态。 (当您想使用通用存储库时,这样做并不那么简单,这就是我专注于这种方法的原因。)

【讨论】:

  • 那么,我需要删除我的“位置”实体类中的导航属性吗?您能建议如何在这里设置相应的外键属性吗?
  • "不要将导航属性设置为对象。相反,只需将相应的外键属性设置为正确的值。" - 那是为我做的。一旦我取消了查找表的导航属性,宾果游戏,查找中不再有新记录。 :)
【解决方案2】:

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

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;
    }

【讨论】:

    【解决方案3】:

    在 yourUpsertelse 语句中,您应该添加

    context.TEntity.Attach(entity);
    

    【讨论】:

    • 我尝试附加,当我有多个位置并且所有位置的属性类型相同时,它会给出以下错误。 "ObjectStateManager 中已存在具有相同键的对象。"
    • 这将是有问题的,因为您最终可能会在上下文中出现多个相同的AttributeType 实体。有关处理此问题的一种方法,请参见我的示例。
    猜你喜欢
    • 2017-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-24
    • 1970-01-01
    • 2019-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多