簡體   English   中英

使用帶有現有子實體的實體框架在數據庫中創建實體時出現問題

[英]Problem creating an entity in the database using Entity Framework with existing child entities

我正在嘗試使用 C# 中的實體框架在數據庫中插入一個實體。 這是在數據庫中創建場地的 function。

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 的數據已經存在,我只添加它們的關系(可用日期和工作日)。 在實體框架中完成這項任務非常困難。

我無法運行您的代碼,因為我沒有您的數據庫或您的 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
        });
});

to Calendar對象from db分離。 在該通話期間,您還特別包括DayOfWeek (即Include(w => w.DayOfWeek) ) - 這將為您的上下文現在知道的DayOfWeek object 提供水分(即使您使用AsNoTracking() )。 看起來您還分離了與fromto上的子對象匹配的DayOfWeek對象。

問題在於這個電話:

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

您正在創建一個新的AvailabilityDate並分配 detached fromto它。 您的上下文將這些分離的對象視為分離的,並假定您正在嘗試附加它們。 fromto對象中是您的DayOfWeek子 object - 這聽起來與您發布的評論的fromto相同。 然后,您的上下文將嘗試附加這兩個DayOfWeek子對象,但在附加WeekDay object for to時將失敗,因為具有相同 IdWeekDay object 已經附加到from 上下文不能有兩個具有相同 ID 的對象,它不能識別為相同的 object - 並且它不能將它們識別為相同的 object,因為它們是分離的。

一個可能的解決方案

為什么要擔心分離呢? Entity Framework 足夠聰明,可以知道您正在重用 object 而無需重新創建它。 只需添加 object,並允許您的上下文識別它已經存在於數據庫中,只需將其作為屬性添加到您的新 object。 這一切都假定您沒有某種唯一索引來強制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 怎么會知道它可以重用對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM