C# 使用实体框架和现有子实体在数据库中创建实体时出现问题

C# 使用实体框架和现有子实体在数据库中创建实体时出现问题,c#,entity-framework,ef-code-first,inner-classes,C#,Entity Framework,Ef Code First,Inner Classes,我试图使用C#中的实体框架在数据库中插入实体。下面是在数据库中创建场地的函数 public async Task<Guid> CreateVenue(CurrentUser currentUser, CreateVenueDTO data, IAuthenticator authenticator) { using (var db = contextFactory.GetContext()) { var uid = Guid.NewGuid();

我试图使用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;
    }
}
我得到了这个错误(尽管我可能已经分离了所有的子实体)

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

以下是重要的课程

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

分离第一个直接子对象就足够了。因为日历和工作日的数据已经存在,我只添加它们的关系(可用日期和工作日)。在实体框架中执行此任务非常困难。

我无法运行您的代码,因为我没有您的数据库或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
        });
});
您正在创建一个新的
AvailabilityDate
,并将分离的
分配给它。您的上下文将这些分离的对象视为已分离,并假定您正在尝试附加它们。在
from
to
对象中,是您的
DayOfWeek
子对象-从您发布的评论中,这两个
from和
to
听起来是相同的。然后,您的上下文将尝试附加这两个子
DayOfWeek
对象,但在将
WeekDay
对象附加到
时将失败,因为具有相同Id的
WeekDay
对象已从
附加到
。上下文不能有两个ID相同但不能识别为同一对象的对象,也不能识别为同一对象,因为它们是分离的

可能的解决方案

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

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
    });
});
这应该将现有的
带到
(以及它们已经存在的
DayOfWeek
对象),并创建一个新的
AvailabilityDate
,其中引用了您提供的现有对象

如果这会创建新的
日历
工作日
记录,则可能需要重新访问
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; }
}

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

我无法运行您的代码,因为我没有您的数据库或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
        });
});
您正在创建一个新的
AvailabilityDate
,并将分离的
分配给它。您的上下文将这些分离的对象视为已分离,并假定您正在尝试附加它们。在
from
to
对象中,是您的
DayOfWeek
子对象-从您发布的评论中,这两个
from和
to
听起来是相同的。然后,您的上下文将尝试附加这两个子
DayOfWeek
对象,但在将
WeekDay
对象附加到
时将失败,因为具有相同Id的
WeekDay
对象已从
附加到
。上下文不能有两个ID相同但不能识别为同一对象的对象,也不能识别为同一对象,因为它们是分离的

可能的解决方案

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

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
    });
});
这应该将现有的
带到
(以及它们已经存在的
DayOfWeek
对象),并创建一个新的
AvailabilityDate
,其中引用了您提供的现有对象

如果这会创建新的
日历
工作日
记录,则可能需要重新访问
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; }
}
这会将
的ID持久化为数据库中的字段,然后使用