使用带有现有子实体的实体框架在数据库中创建实体的问题

问题描述

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

public class Test {
    
    public static void main(String[] args) {
        
        Test example1 = new Test();
        Test example2 = example1;
        Test example3=new Test();
        
        if (example1==example2){
            System.out.println("1 and 2 are same");
        }
        
        if (example1==example3){
            System.out.println("1 and 3 are same");
        }
        
        if (example2==example3){
            System.out.println("2 and 3 are same");
        }
        
        if (example1.equals(example2)){
            System.out.println("1 and 2 are equal");
        }
        
        if (example1.equals(example3)){
            System.out.println("1 and 3 are equal");
        }
        
        if (example2.equals(example3)){
            System.out.println("2 and 3 are equal");
        }
        
    }
}

同时插入可用性日期

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

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

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

这里是重要的类

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

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

解决方法

我无法运行你的代码,因为我没有你的数据库或你的 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 => w.DayOfWeek)) - 这将水合 DayOfWeek 对象,您的上下文现在知道该对象(即使您使用 AsNoTracking()) .看起来您还分离了与 DayOfWeekfrom 上的子对象匹配的 to 对象。

问题出在这个调用上:

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

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

可能的解决方案

为什么要担心分离?实体框架足够聪明,可以知道您正在重用一个对象,而无需重新创建它。只需添加该对象,并允许您的上下文识别它已存在于数据库中,只需将其作为属性添加到新对象中即可。这一切都假设您没有某种唯一索引强制 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 怎么会知道它可以重用对象。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...