【问题标题】:Problem creating an entity in the database using Entity Framework with existing child entities使用带有现有子实体的实体框架在数据库中创建实体时出现问题
【发布时间】:2020-12-31 14:52:06
【问题描述】:

我正在尝试使用 C# 中的实体框架在数据库中插入一个实体。这是在数据库中创建场地的函数。

public async Task<Guid> CreateVenue(CurrentUser currentUser, CreateVenueDTO data, IAuthenticator authenticator)
{
    using (var db = contextFactory.GetContext())
    {
        var uid = Guid.NewGuid();

        var venue = new Venue
            {
                Uid = uid,
                Name = data.Name,
                Address = data.Address,
                Description = data.Description,
                MaxCapacitySitting = data.MaxCapacitySitting,
                MaxCapacityStanding = data.MaxCapacityStanding,
                Lat = data.Lat,
                Lon = data.Lon
            };

        db.Venues.Add(venue);
        await db.SaveChangesAsync();

        venue.VenueFacilities = new List<VenueFacility>();
        var venueFacilities = await db.Facilities.Where(x => data.FacilityUids.Contains(x.Uid)).ToListAsync();
        venueFacilities.ForEach(facility => venue.VenueFacilities.Add(new VenueFacility { Facility = facility, FacilityId = facility.Id, Venue = venue, VenueId = venue.Id, Rank = 0 }));

        db.Venues.Update(venue);
        await db.SaveChangesAsync();

        if (data.Organization != null)
        {
            var orgUid = data.Organization.Uid;

            if (orgUid == Guid.Empty)
            {
                orgUid = await organizationService.CreateOrganization(currentUser, data.Organization);
            }

            venue.Organisation = db.Organizations.Where(x => x.Uid == orgUid).FirstOrDefault();
            db.Venues.Update(venue);

            await db.SaveChangesAsync();
       }

       var venueTypes = await db.Types.Where(x => data.TypeUids.Contains(x.Uid)).ToListAsync();
       venue.VenueTypes = new List<VenueType>();

       venueTypes.ForEach(type => venue.VenueTypes.Add(new VenueType { VenueId = venue.Id, Venue = venue, TypeId = type.Id, Type = type }));

       db.Venues.Update(venue);
       await db.SaveChangesAsync();

       venue.Pricing = new Pricing { Uid = Guid.NewGuid(), Package = data.Pricing.Package, MinimumHourCount = data.Pricing.MinimumHourCount ?? 0, MinimumPeopleCount = data.Pricing.MinimumPeopleCount ?? 0, WeekDays = data.Pricing.WeekDays ?? 0, WeekEnds = data.Pricing.WeekEnds ?? 0 };

       db.Venues.Update(venue);
       await db.SaveChangesAsync();

       venue.VenueAvailabilityTimings = new List<AvailabilityTiming>();
            
       data.AvailabilityTimings.ToList().ForEach(availability => {
           DayTime from = null;
           DayTime to = null;
           WeekDay weekDay = db.WeekDays.Where(w => w.Id == availability.DayOfWeek).AsNoTracking().FirstOrDefault();

           if (availability.From != null)
           {
               from = db.DayTimes.Where(f => f.Id == availability.From).AsNoTracking().FirstOrDefault();
               db.Detatch<DayTime>(from);
           }

           if (availability.To != null)
           {
               to = db.DayTimes.Where(t => t.Id == availability.To).AsNoTracking().FirstOrDefault();
               db.Detatch<DayTime>(to);
           }

           db.Detatch<WeekDay>(weekDay);

           var newAvailability = new AvailabilityTiming
                {
                    Uid = Guid.NewGuid(),
                    From = from,
                    To = to,
                    DayOfWeek = weekDay,
                    AvailableAllDay = availability.AvailableAllDay
                };

           venue.VenueAvailabilityTimings.Add(newAvailability);                
       });

       db.Venues.Update(venue);
       await db.SaveChangesAsync();

       venue.HowFarAdvanced = data.HowFarAdvanced;
       venue.VenueAvailabilityDates = new List<AvailabilityDate>();

       data.AvailabilityDates.ToList().ForEach(availability =>
            {
                Scalable_Web_Data.Models.Calendar from = db.Calendars.Include(w => w.DayOfWeek).AsNoTracking().Where(f => f.Id == availability.From.Id).AsNoTracking().FirstOrDefault();
                Scalable_Web_Data.Models.Calendar to = db.Calendars.Include(w => w.DayOfWeek).AsNoTracking().Where(t => t.Id == availability.To.Id).AsNoTracking().FirstOrDefault();

                db.Detatch<Scalable_Web_Data.Models.Calendar>(from);
                db.Detatch<WeekDay>(db.WeekDays.Where(w => w.Id == from.DayOfWeek.Id).AsNoTracking().FirstOrDefault());

                db.Detatch<Scalable_Web_Data.Models.Calendar>(to);
                db.Detatch<WeekDay>(db.WeekDays.Where(w => w.Id == to.DayOfWeek.Id).AsNoTracking().FirstOrDefault());

                venue.VenueAvailabilityDates.Add(new AvailabilityDate 
                {
                    From = from,
                    To = to
                });
        });
            
        db.Venues.Update(venue);
        await db.SaveChangesAsync();
            
        return uid;
    }
}

在插入可用日期时

 venue.VenueAvailabilityDates.Add(new AvailabilityDate 
                {
                    From = from,
                    To = to
                });

我收到此错误(即使我可能已经分离了所有子实体)

无法跟踪实体类型“WeekDay”的实例,因为已经在跟踪具有相同键值 {'Id'} 的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”来查看冲突的键值。

这里是重要的类

public class Calendar
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [DateColumn]
    public DateTime Date { get; set; }
    public WeekDay DayOfWeek { get; set; }
}

public class WeekDay
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string En { get; set; }
    public string No { get; set; }
}

public class AvailabilityDate
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public virtual Calendar From { get; set; }
    public virtual Calendar To { get; set; }
}

public class AvailabilityTiming
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public Guid Uid { get; set; }
    public WeekDay DayOfWeek { get; set; }
    public DayTime From { get; set; }
    public DayTime To { get; set; }
    public Boolean AvailableAllDay { get; set; }
}

分离第一个直系子节点就足够了。由于 Calendar 和 WeekDay 的数据已经存在,我只添加它们的关系(可用日期和工作日)。在 Entity Framework 中完成这项任务非常困难。

【问题讨论】:

  • 你能添加你的其他类定义吗?我没有看到 AvailabilityDateAvailabilityTiming 或您的 DbContext 实现(定义 db 是什么的类)的类定义。我怀疑其中一些类和WeekDay 之间存在一些关系,这在您发布的代码中并不明显,这会导致分离实体出现问题。
  • 我已经在上面的代码中添加了。
  • 在您正在运行的测试用例中,WeekDay 对象的 ID 是否与此代码中的 fromto Calendar 对象相同:db.Detatch(来自); db.Detatch(db.WeekDays.Where(w => w.Id == from.DayOfWeek.Id).AsNoTracking().FirstOrDefault()); db.Detatch(to); db.Detatch(db.WeekDays.Where(w => w.Id == to.DayOfWeek.Id).AsNoTracking().FirstOrDefault());
  • 此外,该错误似乎提示您启用DbContextOptionsBuilder.EnableSensitiveDataLogging。您可以在此处找到有关如何使用它的详细信息:docs.microsoft.com/en-us/dotnet/api/…。如果您能找到违规WeekDay 的 ID,它将帮助您找到它。
  • 是的,ID 与您要求的相同。实际上,我已经在数据库中保存了工作日,同样,我已经保存了 30 年的日历日期。当我必须创建可用性时,我只是参考现有记录中的日期 ID。 EntityFramework 尝试再次添加这些工作日和日历日期。如果嵌套实体已经存在,应该可以忽略它们的添加。

标签: c# entity-framework ef-code-first inner-classes


【解决方案1】:

我无法运行你的代码,因为我没有你的数据库或你的 DbContext 实现,但我会尝试一下。

问题

问题在于这段代码:

data.AvailabilityDates.ToList().ForEach(availability =>
    {
        Scalable_Web_Data.Models.Calendar from = db.Calendars.Include(w => w.DayOfWeek).AsNoTracking().Where(f => f.Id == availability.From.Id).AsNoTracking().FirstOrDefault();
        Scalable_Web_Data.Models.Calendar to = db.Calendars.Include(w => w.DayOfWeek).AsNoTracking().Where(t => t.Id == availability.To.Id).AsNoTracking().FirstOrDefault();

        db.Detatch<Scalable_Web_Data.Models.Calendar>(from);
        db.Detatch<WeekDay>(db.WeekDays.Where(w => w.Id == from.DayOfWeek.Id).AsNoTracking().FirstOrDefault());

        db.Detatch<Scalable_Web_Data.Models.Calendar>(to);
        db.Detatch<WeekDay>(db.WeekDays.Where(w => w.Id == to.DayOfWeek.Id).AsNoTracking().FirstOrDefault());

        venue.VenueAvailabilityDates.Add(new AvailabilityDate 
        {
            From = from,
            To = to
        });
});

您将Calendar 对象fromtodb 中分离出来。在该调用期间,您还特别包含DayOfWeek(即Include(w =&gt; w.DayOfWeek)) - 这将水合您的上下文现在知道的DayOfWeek 对象(即使您使用AsNoTracking())。看起来您还分离了与 fromto 上的子对象匹配的 DayOfWeek 对象。

问题在于这个调用:

venue.VenueAvailabilityDates.Add(new AvailabilityDate 
{
    From = from,
    To = to
});

您正在创建一个新的AvailabilityDate 并为其分配分离的fromto。您的上下文将这些分离的对象视为分离的,并假定您正在尝试附加它们。在 fromto 对象中是您的 DayOfWeek 子对象 - 这听起来与您发布的评论中的 fromto 相同。然后,您的上下文将尝试附加这两个子 DayOfWeek 对象,但在为 to 附加 WeekDay 对象时将失败,因为具有 相同 IdWeekDay 对象已经存在附于from。上下文不能有两个具有相同 Id 的对象,它不能识别为同一个对象 - 并且它不能将它们识别为同一个对象,因为它们是分离的。

可能的解决方案

为什么要担心分离呢? Entity Framework 足够聪明,可以知道您正在重用一个对象,而无需重新创建它。只需添加该对象,并允许您的上下文识别它已存在于数据库中,只需将其作为属性添加到您的新对象。这一切都假设您没有某种唯一索引来强制 WeekDay 记录不能链接到多个 Calendar 记录,或者 Calendar 记录不能链接到多个 AvailabilityDate 记录。

data.AvailabilityDates.ToList().ForEach(availability =>
{
    Scalable_Web_Data.Models.Calendar from = db.Calendars.Include(w => w.DayOfWeek).AsNoTracking().Where(f => f.Id == availability.From.Id).AsNoTracking().FirstOrDefault();
    Scalable_Web_Data.Models.Calendar to = db.Calendars.Include(w => w.DayOfWeek).AsNoTracking().Where(t => t.Id == availability.To.Id).AsNoTracking().FirstOrDefault();

    venue.VenueAvailabilityDates.Add(new AvailabilityDate 
    {
        From = from,
        To = to
    });
});

这应该采用现有的fromto(以及它们已经存在的DayOfWeek 对象),并使用对您提供的现有对象的引用创建一个新的AvailabilityDate

如果这会创建新的CalendarWeekDay 记录,那么您可能需要重新访问您的DbContext 实现,特别是对象之间的关系以及键的使用方式。我没有看到您在提供的对象上指定任何外键 - 我通常明确定义它们。例如:

public class AvailabilityDate
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int FromId { get; set; }
    public int ToId { get; set; }

    [ForeignKey(nameof(FromId))]
    public virtual Calendar From { get; set; }

    [ForeignKey(nameof(ToId))]
    public virtual Calendar To { get; set; }
}

这会将FromTo 的ID 持久化为数据库中的字段,然后使用这些外键来合并和填充对象。如果不指定它们,我不确定 EF 怎么会知道它可以重用对象。

【讨论】:

  • ِ这一切我都试过了。分离逻辑是最后一次尝试。但没有任何效果。我不得不改变设计。
猜你喜欢
  • 2016-09-07
  • 2019-04-13
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 2010-09-29
  • 1970-01-01
  • 2017-02-04
  • 2020-05-14
相关资源
最近更新 更多