EF6:如何在 Select 中包含子属性,以便创建单个实例。避免“相同的主键”错误

Posted

技术标签:

【中文标题】EF6:如何在 Select 中包含子属性,以便创建单个实例。避免“相同的主键”错误【英文标题】:EF6:How to include subproperty with Select so that single instance is created. Avoid "same primary key" error 【发布时间】:2017-06-08 01:38:16 【问题描述】:

我正在尝试(以断开连接的方式)获取具有所有相关实体的实体,然后尝试更新该实体。但我收到以下错误:

附加“功能”类型的实体失败,因为同一类型的另一个实体已经具有相同的主键值。 公共类人 公共 int PersonId 获取;放; 公共字符串人名 得到;放 公共 ICollection 地址 获取;放;

public class Address

    public int AddressId  get; set; 
    public int PersonId  get; set; 
    public string Line1  get; set; 
    public string City  get; set; 
    public string State  get; set; 

    public Person Person  get; set; 
    public ICollection<Feature> Features  get; set;  


// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature

    public int FeatureId  get; set; 
    public string Featurename  get; set; 
    public ICollection<Address> Addresses  get; set;  // Many-To-Many with Addresses


public Person GetCandidate(int id)

    using (MyDbContext dbContext = new MyDbContext())
    
         var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
         person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
         return person.FirstOrDefault();
     


public void UpdateCandidate(Person newPerson)

    Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
    .....
    .....
    .....

错误: 附加信息:附加“功能”类型的实体失败,因为同一类型的另一个实体已经具有相同的主键值。

看起来(我可能错了)GetCandidate 正在为 Person.Addresses 中的每个功能分配一个新实例。那么,我如何修改 GetCandidate 以确保将相同的实例(对于相同的值)分配给 Person.Addresses --> Features。

请推荐。

【问题讨论】:

这是AsNoTracking 的副作用。取数据时最好设置dbContext.Configuration.ProxyCreationEnabled = false;,去掉AsNoTracking ProxyCreationEnabled 已经为假,但我确实想使用 AsNoTracking,因为我希望 EF 不跟踪实体。是否有可能需要实施其中一项设置?是否可以使用 AsNoTracking 并避免我遇到的错误? 跟踪意味着允许使用数据库上下文缓存。如果您不允许使用缓存,EF 将生成单独的实例。就这么简单。我看不出在您的 GetCandidate 方法中那样在短暂的数据库上下文中使用上下文跟踪服务有什么问题。只要 EF 不创建代理,返回的对象就不会引用用于获取它们的 db 上下文。 酷。谢谢伊万。这是很大的帮助。我怎么能接受你的回答? Ivan,您能否发帖作为答案,以便我接受您的回答。 【参考方案1】:

看起来(我可能错了)GetCandidate 正在为 Person.Addresses 中的每个功能分配一个新实例。那么,我如何修改 GetCandidate 以确保将相同的实例(对于相同的值)分配给 Person.Addresses --> Features。

由于您使用短暂的DbContext 来检索数据,因此您只需删除AsNoTracking(),从而允许EF 使用上下文缓存并合并Feature 实体。 EF 跟踪服务于不同的目的。一是允许将实体实例与您在这种情况下感兴趣的相同 PK 合并,二是在您修改实体并调用 SaveChanges() 时检测修改,显然您在使用上下文时不感兴趣只是为了检索数据。当您禁用查询的跟踪时,EF 无法使用缓存,从而生成单独的对象实例。

您真正不想要的是让 EF 创建代理,这些代理持有对用于获取它们的上下文的引用,并且在尝试附加到另一个上下文时会导致问题。我在您的模型中看不到 virtual 导航属性,因此 EF 很可能不会创建代理,但为了绝对确定,我会关闭 ProxyCreationEnabled

public Person GetCandidate(int id)

    using (MyDbContext dbContext = new MyDbContext())
    
         dbContext.Configuration.ProxyCreationEnabled = false;
         var person = dbContext.People.Where(x => x.PersonId == id);
         person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
         return person.FirstOrDefault();
     

【讨论】:

以上是关于EF6:如何在 Select 中包含子属性,以便创建单个实例。避免“相同的主键”错误的主要内容,如果未能解决你的问题,请参考以下文章

在 hive 的 select 语句中包含子查询结果

ERROR 1349 (HY000): View 的 SELECT 在 FROM 子句中包含子查询

错误 #1349:View 的 select cluse 在 from 子句中包含子查询

在 JPA 的 SQL 查询中的 FROM 语句中包含子查询

Linq包括在嵌套组中,通过查询

如何在我的文件转换脚本中包含子文件夹?