dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.79k stars 3.19k forks source link

EF 3.1 to EF 7 object creation changed behavior? #31111

Closed omatrot closed 1 year ago

omatrot commented 1 year ago

First of all, I'm in a Database First context, so I cannot provide a reproductible sample. I have been using the scaffolding tool to generate the entities and the DBContext. This is a migration from an existing data layer from EF 3.1 to EF 7. All I have done for EF 7, is to include all the existing code, and embedd it in a NET 7 library. I can't alter the database if this is needed to fix my bug.

In EF 3.1 I was doing the following to create ad persist a small graph:

Vehicule v = new Vehicule();
v.TrackingState = TrackableEntities.Common.Core.TrackingState.Added;
v.CompanyId = 1;
v.Name = "Test Unitaire";
v.PhoneNumber = "+33614407055";
v.GpsBoxType = 29;
v.Address = "testunitaire@safeprotect.fr";
v.AddressProtocol = 1;

var registration = new Registration();
registration.TrackingState = TrackableEntities.Common.Core.TrackingState.Added;
registration.LatestRegistrationStatusChangeDate = DateTime.Now;
registration.RegistrationStatus = RegistrationStatus.Authorized;
registration.CompanyId = 1;

v.Registration = registration;

// Data layer code not shown here.

Then the Vehicle is added to the context for saving. It is working fine in EF 3.1, but in EF 7 I have the following error:

System.AggregateException
  HResult=0x80131500
  Message=One or more errors occurred. (An error occurred while saving the entity changes. See the inner exception for details.)
  Source=System.Private.CoreLib
  StackTrace:
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at SafeProtect.Common.Data.DotNet.ConsoleTest.Program.Main(String[] args) in D:\AzureDevops\SafeProtect.Common\SafeProtect.Common.Data.DotNet.ConsoleTest\Program.cs:line 60

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.

Inner Exception 2:
SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_RegistrationNew_VehiculeID". The conflict occurred in database "RTEGeoloc", table "dbo.Vehicule", column 'VehiculeID'.

Indeed, there is a relationship from Registration to Vehicle.

If I do the reverse, first create the Registration and then associate a new Vehicle, inserting works.

 var registration = new Registration();
registration.Vehicule = new Vehicule();
registration.LatestRegistrationStatusChangeDate = DateTime.Now;
registration.RegistrationStatus = RegistrationStatus.Authorized;
registration.CompanyId = 1;

var v = registration.Vehicule;
v.CompanyId = 1;
v.Name = "Test Unitaire";
v.PhoneNumber = "+33614407055";
v.GpsBoxType = 29;
v.Address = "testunitaire@safeprotect.fr";
v.AddressProtocol = 1;

Following are the entities and the configuration:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations.Schema;
    using Newtonsoft.Json;
    using MyNameSpace.Common.Data.BusinessObjects.CustomAttributes;

    namespace MyNameSpace.Common.Data.BusinessObjects.Models
    {
        public partial class Vehicule
        {
            public Vehicule()
            {
                ContextDetail = new HashSet<ContextDetail>();
                ContextDetailData = new HashSet<ContextDetailData>();
                FleetDetail = new HashSet<FleetDetail>();
                GpsPosition = new HashSet<GpsPosition>();
                VehiculeContext = new HashSet<VehiculeContext>();
                VehiculeTemperatureContext = new HashSet<VehiculeTemperatureContext>();
                ActivityHistory = new HashSet<ActivityHistory>();
                LevelOneFleets = new HashSet<Fleet>();
                ReportSubscriptions = new HashSet<ReportSubscription>();

                AdditionalInit();
            }
            public long VehiculeId { get; set; }

            public int CompanyId { get; set; }

            public string PhoneNumber { get; set; }

            public long? HardwareId { get; set; }

            public int? VehiculeUserId { get; set; }

            public int GpsBoxType { get; set; }

            public string Category { get; set; }

            public string Name { get; set; }

            public string Description { get; set; }

            public int IconId { get; set; }

            public int GpsBoxTrackingDelay { get; set; }

            public string Address { get; set; }

            public int? AddressProtocol { get; set; }

            public string BoardId { get; set; }

            public ulong RowVersion { get; set; }

            public bool RowEnabled { get; set; }

            public DateTime? DbInsertTime { get; set; }

            public string CustomId { get; set; }

            public bool? HasGeoWorker { get; set; }

            public int? GpsBoxSubType { get; set; }

            public virtual Company Company { get; set; }

            public virtual GpsBoxSubType GpsBox { get; set; }

            public virtual GpsBoxType GpsBoxTypeNavigation { get; set; }

            public virtual VehiculeGpsBoxInfo VehiculeGpsBoxInfo { get; set; }

            public virtual Registration Registration { get; set; }

            public virtual Device Device { get; set; }

            public virtual ICollection<FleetDetail> FleetDetail { get; set; }

            public virtual ICollection<ContextDetail> ContextDetail { get; set; }

            public virtual ICollection<VehiculeContext> VehiculeContext { get; set; }

            public virtual ICollection<VehiculeTemperatureContext> VehiculeTemperatureContext { get; set; }

            public virtual ICollection<GpsPosition> GpsPosition { get; set; }

            public virtual ICollection<AlertInstructions> AlertInstructions { get; set; }

            public virtual ICollection<ActivityHistory> ActivityHistory { get; set; }

            public virtual ICollection<ContextDetailData> ContextDetailData { get; set; }

            [NotMapped]
            public virtual ICollection<Fleet> LevelOneFleets { get; set; }

            public bool NotifyUser { get; set; }

            public virtual VehiculeSettings VehiculeSettings { get; set; }

            public string SecuritasContacts { get; set; }

            public virtual ICollection<ReportSubscription> ReportSubscriptions { get; set; }

            public TrackingState TrackingState { get; set; }

            public ICollection<string> ModifiedProperties { get; set; }

            public Guid EntityIdentifier { get; set; }

            partial void AdditionalInit();
        }
    }

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations.Schema;
    using MyNameSpace.Common.Data.BusinessObjects.CustomAttributes;
    using TrackableEntities.Common.Core;

    namespace MyNameSpace.Common.Data.BusinessObjects.Models
    {
        public partial class Registration : ITrackable, IMergeable
        {
            public long VehiculeId { get; set; }

            public int CompanyId { get; set; }

            public DateTime LatestRegistrationStatusChangeDate { get; set; }
            //public int RegistrationStatusId { get; set; }

            public bool RegistrationMailSent { get; set; }

            public virtual RegistrationStatus RegistrationStatus { get; set; }

            public virtual Company Company { get; set; }

            public virtual Vehicule Vehicule { get; set; }

            public bool NotAnyFreeLicenceMailSent { get; set; }

            [NotMapped]
            public virtual bool AllowSendNotificationToDevice { get; set; }

            [NotMapped]
            public TrackingState TrackingState { get; set; }

            [NotMapped]
            public ICollection<string> ModifiedProperties { get; set; }

            [NotMapped]
            public Guid EntityIdentifier { get; set; }

            partial void AdditionalInit();
        }
    }

    internal static void ConfigureVehicleEntity(ModelBuilder builder)
    {
        builder.Entity<Vehicle>(entity =>
        {
            entity.HasKey(v => v.VehiculeId);
            entity.HasIndex(e => e.HardwareId)
                .HasName("UIX_HardwareID")
                .IsUnique()
                .HasFilter("([HardwareID] IS NOT NULL)");

            entity.HasIndex(e => e.RowVersion)
                .HasName("IX_RowVersion");

            entity.HasIndex(e => e.VehiculeUserId)
                .HasName("UIX_VehiculeUserID")
                .IsUnique()
                .HasFilter("([VehiculeUserID] IS NOT NULL)");

            entity.HasIndex(e => new { e.BoardId, e.CompanyId })
                .HasName("UIX_CompanyIdVehiculeBoardId")
                .IsUnique()
                .HasFilter("([BoardId] IS NOT NULL)");

            entity.HasIndex(e => new { e.GpsBoxType, e.RowEnabled })
                .HasName("IX_RowEnabled");

            entity.HasIndex(e => new { e.VehiculeId, e.Name, e.CompanyId, e.RowEnabled })
                .HasName("IX_CompanyID_RowEnabled");

            entity.Property(e => e.VehiculeId).HasColumnName("VehiculeID").ValueGeneratedOnAdd();

            entity.Property(e => e.Address).HasMaxLength(250);

            entity.Property(e => e.BoardId)
                .HasColumnName("BoardID")
                .HasMaxLength(50)
                .IsUnicode(false);

            entity.Property(e => e.Category).HasMaxLength(128);

            entity.Property(e => e.CompanyId).HasColumnName("CompanyID");

            entity.Property(e => e.CustomId).HasMaxLength(128);

            entity.Property(e => e.DbInsertTime)
                .HasColumnType("datetime")
                .HasDefaultValueSql("(getutcdate())");

            entity.Property(e => e.Description).HasMaxLength(1024);

            entity.Property(e => e.GpsBoxTrackingDelay).HasDefaultValueSql("((1))");

            entity.Property(e => e.HardwareId).HasColumnName("HardwareID");

            entity.Property(e => e.IconId)
                .HasColumnName("IconID")
                .HasDefaultValueSql("((33554432))");

            entity.Property(e => e.Name).HasMaxLength(128);

            entity.Property(e => e.PhoneNumber)
                .HasMaxLength(30)
                .IsUnicode(false);

            entity.Property(e => e.RowEnabled)
                .IsRequired()
                .HasDefaultValueSql("((1))");

            entity.Property(e => e.RowVersion)
                .IsRequired()
                .HasConversion(new NumberToBytesConverter<ulong>())
                .IsRowVersion();

            entity.Property(e => e.VehicleUserId).HasColumnName("VehiculeUserID");

            entity.Property(e => e.GpsBoxType).HasColumnName("GpsBoxType");

            entity.Property(e => e.GpsBoxSubType).HasColumnName("GpsBoxSubType");

            entity.HasOne(d => d.GpsBoxTypeNavigation)
                .WithMany(p => p.Vehicule)
                .HasForeignKey(d => d.GpsBoxType)
                .OnDelete(DeleteBehavior.ClientSetNull)
                .HasConstraintName("FK_Vehicule_GpsBoxType");

            entity.HasOne(d => d.GpsBox)
                .WithMany(p => p.Vehicule)
                .HasForeignKey(d => new { d.GpsBoxType, d.GpsBoxSubType })
                .HasConstraintName("FK_Vehicule_GpsBoxSubType");
        });
    }

    internal static void ConfigureRegistrationEntity(ModelBuilder builder)
    {
        builder.Entity<Registration>(entity =>
        {
            entity.HasKey(e => e.VehiculeId)
                .HasName("PK_RegistrationNew");

            entity.ToTable("Registration", "SafeProtect");

            entity.Property(e => e.VehiculeId)
                .HasColumnName("VehiculeID")
                .ValueGeneratedNever();
            entity.Property(e => e.RegistrationStatus)
                .HasColumnName("RegistrationStatusId")
                .HasDefaultValueSql("((3))")
                .HasConversion(v => (int)v, v => (RegistrationStatus)v);
            entity.Property(e => e.LatestRegistrationStatusChangeDate).HasColumnType("smalldatetime").HasConversion(DateTimeKindUtcConverter);

            entity.HasOne(d => d.Company)
                .WithMany(p => p.Registration)
                .HasForeignKey(d => d.CompanyId)
                .OnDelete(DeleteBehavior.ClientSetNull)
                .HasConstraintName("FK_RegistrationNew_Company");

            entity.HasOne(d => d.Vehicule)
                .WithOne(p => p.Registration)
                .HasForeignKey<Registration>(d => d.VehiculeId)
                .OnDelete(DeleteBehavior.ClientSetNull)
                .HasConstraintName("FK_RegistrationNew_VehiculeID");
        });
    }
ajcvickers commented 1 year ago

This issue is lacking enough information for us to be able to fully understand what is happening. Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate. In particular, the, "// Data layer code not shown here." is likely to be relevant.

omatrot commented 1 year ago

I will close this issue because running EF Core 3.1 on NET 7 is possible.

ajcvickers commented 1 year ago

@omatrot Note that EF Core 3.1 is out-of-support.