实体框架M:1关系导致主要密钥重复

时间:2016-10-18 06:50:37

标签: c# entity-framework

我对EF 6.0有些新意,所以我很确定我在这里做错了什么。 有两个与问题相关的问题

  1. 我在这里做错了什么
  2. 实现这一目标的最佳做法是什么
  3. 我正在使用代码优先模型,并使用edmx设计器来设计模型和关系,系统需要定期从Web服务中提取信息并将其保存到桌面应用程序中的本地数据库(SQL Lite)< / p>

    enter image description here

    所以我从API获得了一个订单列表,当我填充并尝试保存Ticket时,我在尝试插入TicketSeatType时遇到重复的键异常 -

    如何将票证插入dbContext,以便它不会尝试重新插入插入TicketSeatType和TicketPriceType,我已经尝试将子对象状态设置为未更改但它似乎正在插入

    其次,使用EF实现这一目标的最佳做法是什么?将每个对象加载到内存中并比较是否存在

    只是效率很低

    因为我需要定期更新列表,所以我必须检查数据库中的每个对象(如果存在),然后更新,否则插入

    代码:

    //read session from db
                    if (logger.IsDebugEnabled) logger.Debug("reading session from db");
                    dbSession = dbContext.SessionSet.Where(x => x.Id == sessionId).FirstOrDefault();
    
                    //populate orders
                    List<Order> orders = (from e in ordersList
                                          select new Order {
                                              Id = e.OrderId,
                                              CallCentreNotes = e.CallCentreNotes,
                                              DoorEntryCount = e.DoorEntryCount,
                                              DoorEntryTime = e.DoorEntryTime,
                                              OrderDate = e.OrderDate,
                                              SpecialInstructions = e.SpecialInstructions,
                                              TotalValue = e.TotalValue,
                                              //populate parent refernece
                                              Session = dbSession
                                          }).ToList();
    
                    //check and save order
                    foreach (var o in orders) {
                        dbOrder = dbContext.OrderSet.Where(x => x.Id == o.Id).FirstOrDefault();
                        if (dbOrder != null) {
                            dbContext.Entry(dbOrder).CurrentValues.SetValues(o);
                            dbContext.Entry(dbOrder).State = EntityState.Modified;
                        }
                        else {
                            dbContext.OrderSet.Add(o);
                            dbContext.Entry(o.Session).State = EntityState.Unchanged;
                        }
                    }
                    dbContext.SaveChanges();
    
                    //check and add ticket seat type
                    foreach (var o in ordersList) {
                        foreach (var t in o.Tickets) {
                            var ticketSeatType = new TicketSeatType {
                                Id = t.TicketSeatType.TicketSeatTypeId,
                                Description = t.TicketSeatType.Description
                            };
                            dbTicketSeatType = dbContext.TicketSeatTypeSet.Where(x => x.Id == ticketSeatType.Id).FirstOrDefault();
                            if (dbTicketSeatType != null) {
                                dbContext.Entry(dbTicketSeatType).CurrentValues.SetValues(ticketSeatType);
                                dbContext.Entry(dbTicketSeatType).State = EntityState.Modified;
                            }
                            else {
                                if (!dbContext.ChangeTracker.Entries<TicketSeatType>().Any(x => x.Entity.Id == ticketSeatType.Id)) {
                                    dbContext.TicketSeatTypeSet.Add(ticketSeatType);
                                }
                            }
                        }
                    }
                    dbContext.SaveChanges();
    
                    //check and add ticket price type
                    foreach (var o in ordersList) {
                        foreach (var t in o.Tickets) {
                            var ticketPriceType = new TicketPriceType {
                                Id = t.TicketPriceType.TicketPriceTypeId,
                                SeatCount = t.TicketPriceType.SeatCount,
                                Description = t.TicketPriceType.Description
                            };
                            dbTicketPriceType = dbContext.TicketPriceTypeSet.Where(x => x.Id == ticketPriceType.Id).FirstOrDefault();
                            if (dbTicketPriceType != null) {
                                dbContext.Entry(dbTicketPriceType).CurrentValues.SetValues(ticketPriceType);
                                dbContext.Entry(dbTicketPriceType).State = EntityState.Modified;
                            }
                            else {
                                if (!dbContext.ChangeTracker.Entries<TicketPriceType>().Any(x => x.Entity.Id == ticketPriceType.Id)) {
                                    dbContext.TicketPriceTypeSet.Add(ticketPriceType);
                                }
                            }
                        }
                    }
                    dbContext.SaveChanges();
    
                    //check and add tickets
                    foreach (var o in ordersList) {
                        dbOrder = dbContext.OrderSet.Where(x => x.Id == o.OrderId).FirstOrDefault();
                        foreach (var t in o.Tickets) {
                            var ticket = new Ticket {
                                Id = t.TicketId,
                                Quantity = t.Quantity,
                                TicketPrice = t.TicketPrice,
                                TicketPriceType = new TicketPriceType {
                                    Id = t.TicketPriceType.TicketPriceTypeId,
                                    Description = t.TicketPriceType.Description,
                                    SeatCount = t.TicketPriceType.SeatCount,
                                },
                                TicketSeatType = new TicketSeatType {
                                    Id = t.TicketSeatType.TicketSeatTypeId,
                                    Description = t.TicketSeatType.Description
                                },
                                Order = dbOrder
                            };
                            //check from db 
                            dbTicket = dbContext.TicketSet.Where(x => x.Id == t.TicketId).FirstOrDefault();
                            dbTicketSeatType = dbContext.TicketSeatTypeSet.Where(x => x.Id == t.TicketSeatType.TicketSeatTypeId).FirstOrDefault();
                            dbTicketPriceType = dbContext.TicketPriceTypeSet.Where(x => x.Id == t.TicketPriceType.TicketPriceTypeId).FirstOrDefault();                            
    
                            if (dbTicket != null) {
                                dbContext.Entry(dbTicket).CurrentValues.SetValues(t);
                                dbContext.Entry(dbTicket).State = EntityState.Modified;
                                dbContext.Entry(dbTicket.Order).State = EntityState.Unchanged;
                                dbContext.Entry(dbTicketSeatType).State = EntityState.Unchanged;
                                dbContext.Entry(dbTicketPriceType).State = EntityState.Unchanged;
    
                            }
                            else {
                                dbContext.TicketSet.Add(ticket);
                                dbContext.Entry(ticket.Order).State = EntityState.Unchanged;
                                dbContext.Entry(ticket.TicketSeatType).State = EntityState.Unchanged;
                                dbContext.Entry(ticket.TicketPriceType).State = EntityState.Unchanged;
                            }
                        }
                    }
                    dbContext.SaveChanges();
    

    更新:

    找到答案,它与EF如何跟踪对象的引用有关,在上面的代码中,我是从TicketPriceType和TicketSeatType列表中创建新的实体类型:

     foreach (var o in ordersList) {
                        dbOrder = dbContext.OrderSet.Where(x => x.Id == o.OrderId).FirstOrDefault();
                        foreach (var t in o.Tickets) {
                            var ticket = new Ticket {
                                Id = t.TicketId,
                                Quantity = t.Quantity,
                                TicketPrice = t.TicketPrice,
                                TicketPriceType = new TicketPriceType {
                                    Id = t.TicketPriceType.TicketPriceTypeId,
                                    Description = t.TicketPriceType.Description,
                                    SeatCount = t.TicketPriceType.SeatCount,
                                },
                                TicketSeatType = new TicketSeatType {
                                    Id = t.TicketSeatType.TicketSeatTypeId,
                                    Description = t.TicketSeatType.Description
                                },
                                Order = dbOrder
                            };
    ....
    

    在这种情况下,EF不会知道它们是哪些对象并尝试插入它们。

    解决方案是从数据库中读取实体并分配这些实体,因此它引用相同的实体并且不添加新实体

    foreach (var t in o.Tickets) {
    
                                //check from db 
                                dbTicket = dbContext.TicketSet.Where(x => x.Id == t.TicketId).FirstOrDefault();
                                dbTicketSeatType = dbContext.TicketSeatTypeSet.Where(x => x.Id == t.TicketSeatType.TicketSeatTypeId).FirstOrDefault();
                                dbTicketPriceType = dbContext.TicketPriceTypeSet.Where(x => x.Id == t.TicketPriceType.TicketPriceTypeId).FirstOrDefault();
    
                                var ticket = new Ticket {
                                    Id = t.TicketId,
                                    Quantity = t.Quantity,
                                    TicketPrice = t.TicketPrice,
                                    TicketPriceType = dbTicketPriceType,
                                    TicketSeatType = dbTicketSeatType,
                                    Order = dbOrder
                                };
    ...}
    

1 个答案:

答案 0 :(得分:1)

您是否认为您正在尝试编写非常相似的代码来定义每个实体的状态?  我们可以使用一个命令处理所有这些操作。

您可以使用新发布的EntityGraphOperations实体框架代码优先轻松实现此目的。我是这个产品的作者。我已将其发布在githubcode-project包括逐步演示,示例项目已准备好下载)nuget。借助InsertOrUpdateGraph方法,它会自动将您的实体设置为AddedModified。在DeleteMissingEntities方法的帮助下,您可以删除数据库中存在的实体,但不能删除当前集合中的实体。

// This will set the state of the main entity and all of it's navigational 
// properties as `Added` or `Modified`.
context.InsertOrUpdateGraph(ticket); 

顺便说一下,我觉得有必要提一下,这当然不是最有效的方法。一般的想法是从数据库中获取所需的实体并定义实体的状态。它会尽可能高效。