Search code examples
c#asp.net-mvcentity-frameworkef-code-firstmany-to-many

Insert Fails in Many-to-Many Relationship


I am working on a Entity Framework Code First project where I have many-to-many relationships: ServiceProvider and ServiceType

I am getting the following error when adding a new ServiceProvider into the repository: Cannot insert the value NULL into column 'ID', table 'ABCDE.dbo.ServiceTypes'; column does not allow nulls. INSERT fails.

The entity and controller classes are as follows:

ServiceProvider.cs:

public partial class ServiceProvider
{
    public ServiceProvider()
    {
        ServiceTypes = new HashSet<ServiceType>();
    }

    [Key]
    public int ServiceProviderID { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    public virtual ICollection<ServiceType> ServiceTypes { get; set; }

    public virtual IEnumerable<Service> Services { get; set; }

}

ServiceType.cs:

public partial class ServiceType
{
    public ServiceType(ServiceTypeEnum @enum)
    {
        ID = (int) @enum;
        Name = @enum.ToString();
    }

    protected ServiceType() { } // For EF

    //[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int ID { get; set; }

    [Required, MaxLength(100)]
    public string Name { get; set; }

    public virtual ICollection<ServiceProvider> ServiceProviders { get; set; }

    public static implicit operator ServiceType(ServiceTypeEnum @enum) => new ServiceType(@enum);

    public static implicit operator ServiceTypeEnum(ServiceType serviceType) => (ServiceTypeEnum) serviceType.ID;
}


public enum ServiceTypeEnum
{
    Ambulance = 1,
    [Display(Name = "Cash Advance")]
    CashAdvance = 2,
    Hospitalization = 3,
    Hotel = 4,
    [Display(Name = "House Call")]
    HouseCall = 5,
    [Display(Name = "Medical Escort")]
    MedicalEscort = 6,
    Transfer = 7,
    Repatriation = 8
}

and the ServiceProviderController.cs:

public ActionResult Create()
    {
        var _allServiceTypes =  Enum.GetValues(typeof(ServiceTypeEnum))
           .Cast<ServiceTypeEnum>()
           .Select(t => new KeyValuePair<string, int>(t.ToString(), (int) t));

        var viewModel = new ServiceProviderViewModel.CreateModel()
        {
            AllServiceTypes = _allServiceTypes
        };

        return View(viewModel);
    }

    [HttpPost]
    public ActionResult Create(ServiceProviderViewModel.CreateModel viewModel)
    {
        if (ModelState.IsValid)
        {

            var serviceProvider = new ServiceProvider();
            serviceProvider.Title = viewModel.Title;            

            repository.InsertServiceProvider(serviceProvider);

            for (int i = 0; i < viewModel.SelectedServiceTypes.Length; i++)
            {
                var _serviceType = new ServiceType((ServiceTypeEnum)Enum.Parse(typeof(ServiceTypeEnum), viewModel.SelectedServiceTypes[i].ToString()));
                serviceProvider.ServiceTypes.Add(_serviceType);
            }

            repository.Save();

            return RedirectToAction("Index");
        }
        return View(viewModel);
    }

I am not sure why the database table ServiceTypeServiceProviders (automatically generated by EF) is not populated with the correct data. Nothing gets stored into it. By looking at the serviceProvider object while debugging, I can see that everything seems to be fine (see the image below)

enter image description here

I filled the ServiceType table manually with the following data (matching the enum)

enter image description here


Solution

  • I'm assuming the "new" ServiceType object you are creating and adding to the associations for the ServiceProvider is a reference to the data you manually inserted into the ServiceType table and not a new object... If so you'll either want to retrieve the object from the DbContext rather than creating a new object or you'll have to attach the "new" ServiceType object to the current DbContext using either context.Set<ServiceType>.Attach(serviceType); or context.Entry(serviceType).State = EntityState.Attached;. These may require the assignment of the ID property for the ServiceType object to ensure it maps to the existing entities correctly. This will ensure you are not getting a null ID or duplicate entries in the ServiceType table on insert.

    If this does not fix the issue, we would have to see the code for InsertServiceProvider to get a better idea of how you are adding the ServiceProvider entity to the DbContext