ASP.NET EF CORE: Composite key composed of two foreign keys not evaluating properly

asp.net-core c# entity-framework-core sql

Question

EDIT After some editing I am now able to post the data but have issues scaffolding the structure. My model builder feels like it's better now but something is still amiss

modelBuilder.Entity<Client>().HasMany(x => x.Machines).WithOne(x=>x.Client).HasForeignKey(x=>x.ClientID);
modelBuilder.Entity<Machine>().HasKey(x => new { x.ID, x.ClientID });
modelBuilder.Entity<MachineBob>().HasOne(x => x.Machine).WithMany(x => x.MachineBobData).HasForeignKey(x => new { x.MachineID, x.ClientID }).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MachineMixer>().HasOne(x => x.Machine).WithMany(x => x.MachineMixerData).HasForeignKey(x => new { x.MachineID, x.ClientID }).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MachineBob>().HasKey(c => new { c.TimeStamp, c.ClientID, c.MachineID });
modelBuilder.Entity<MachineMixer>().HasKey(x => new { x.TimeStamp, x.MachineID, x.ClientID });

END - EDIT

I am attempting to build a web app using EF Core that will allow a Client to own any number of machines and log their data. Each machine can be of a different type and every type of machine has a unique table as they log different things. I am able to create a Client and Machine object without any trouble but as soon as I try to log data into my Machinexxx data tables I run into issues concerning my use of composite foreign keys.

CloudApp.Models

public class Client 
{
    [Required]
    [MaxLength(100)]
    public string ClientName { get; set; }
    [MaxLength(100)]
    [Key]
    public int ID { get; set; }
    [MaxLength(100)]
    // TODO: Automatically assign these email addresses as CLAIMS to allow the USER to see the CLIENT
    public List<String> Emails;
    public ICollection<Machine>? Machines { get; set; }
}

Which has a one to many relationship with the Machine class here

public class Machine
{
    [MaxLength(50)]
    [Key]
    public int ID { get; set; }

    [MaxLength(100)]
    public string Factory { get; set; }
    [MaxLength(50)]
    public string Line { get; set; }

    [MaxLength(50)]
    public string MachineType { get; set; }
    [MaxLength(50)]
    public string MachineName { get; set; }
    [MaxLength(50)]
    public string Country { get; set; }
    [MaxLength(50)]
    public string City { get; set; }
    [MaxLength(25)]
    public int ZipCode { get; set; }
    [MaxLength(50)]
    public string Address { get; set; }

    [MaxLength(50)]
    public int ClientID { get; set; }
    public Client Client { get; set; } // Navivation property
    public List<MachineBob>? MachineBobData { get; set; } // Navivation property
    public List<MachineMixer>? MachineMixerData { get; set; }// Navivation property

Which has a one to many relationship with the (currently) two possible machine types

public class MachineBob 
{
    [MaxLength(50)]
    [XmlElement("TimeStamp")]
    public DateTime TimeStamp { get; set; }
    [XmlElement("Temperature")]
    [MaxLength(50)]
    public int Temperature { get; set; }
    [MaxLength(50)]
    [XmlElement("Heartbeat")]
    public Boolean Heartbeat { get; set; }

    [MaxLength(50)]
    [ForeignKey("MachineID")]
    public int MachineID { get; set; }
    public Machine Machine { get; set; }
    [MaxLength(50)]
    [ForeignKey("ClientID")]
    public int ClientID { get; set; }
    public Client Client { get; set; }
}

[XmlRoot("Root"),Serializable]
public class MachineMixer 
{
    [MaxLength(50)]
    [XmlElement("TimeStamp")]
    public DateTime TimeStamp { get; set; }
    [XmlElement("Temperature")]
    [MaxLength(50)]
    public int MixingRatio { get; set; }
    [MaxLength(50)]
    [XmlElement("Heartbeat")]
    public Boolean Heartbeat { get; set; }
    [XmlIgnore]

    [MaxLength(50)]
    public int MachineID { get; set; }
    public Machine Machine { get; set; }
    [MaxLength(50)]
    public int ClientID { get; set; }
    public Client Client { get; set; }
}

Lastly my model builder looks like this (all of my relationships are made here I try not to use data annotations due to their limitation)

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
        base.OnModelCreating(modelBuilder);

        foreach (var foreignKey in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
        {
            foreignKey.DeleteBehavior = DeleteBehavior.Cascade;
        }

        modelBuilder.Entity<Client>().HasMany(x => x.Machines);

        modelBuilder.Entity<Machine>().HasOne(x => x.Client).WithMany(x => x.Machines).HasForeignKey(x => x.ClientID);
        modelBuilder.Entity<Machine>().HasKey(x => new { x.ClientID, x.ID });

        modelBuilder.Entity<Machine>().HasMany(x => x.MachineBobData).WithOne(x => x.Machine).HasForeignKey(x => new { x.MachineID, x.ClientID })OnDelete(DeleteBehavior.NoAction); 
        modelBuilder.Entity<Machine>().HasMany(x => x.MachineMixerData).WithOne(x => x.Machine).HasForeignKey(x => new { x.MachineID, x.ClientID }).OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<MachineBob>().HasKey(c => new { c.TimeStamp, c.ClientID, c.MachineID });

        modelBuilder.Entity<MachineMixer>().HasKey(x => new { x.TimeStamp, x.MachineID, x.ClientID });
}

What I would like to do is have every row be one data entry with a unique composite key composed of MachineID, ClientID, Timestamp. But as I explain below that is not currently possible...

As I stated before all CRUD operations for bot the Client and Machine class work fine. But MachineBob is incredibly problematic. Even though the model design sees that there is a composite key of three elements I get a SQL error whenever I attempt to write to the table. What is very strange is that I can write as expected so long as I used ClientID = 1, MachineID = 1, and a unique Timestamp which is what I want. But as soon as I attempt to add another row or another instance of this Machine BOB by writing Client ID=X issues start to arise.

Here is the SQL exception which seems to only have a problem with ClientId not being unique. But it is part of a composite key so the problem shouldn't be occurring anyway:

SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_MachineBobs_Clients_ClientID". The conflict occurred in database "aspnet-ProcessCloudApp-B3DBF3A9-F26C-41F4-B7D6-CC2D2782C266", table "dbo.Clients", column 'ID'. The statement has been terminated.

I would like some assistance concerning my modelbuilding as I feel most lost with this.

Part of me feels that the problem may also lay with how I'm thinking about this "MachineBob" data model. Perhaps it would be better to instead have a one to one relationship between "Machine" and "MachineBob" and have all properties of type ICollection<T> or List<T>

Thank you for your assistance.

1
0
2/21/2020 3:18:32 PM

Popular Answer

I was able to solve the issue (only took all week). I modified my modelbuilders accordingly but also got rid of the "public Client Client" in the MachineBOB. Even though I need the ClientID the objects are not directly connected to each other. Here's the code for reference to anyone else who finds it worth critiquing further or for their own use on this semi-complex relationship.

        modelBuilder.Entity<Client>().HasMany(x => x.Machines).WithOne(x=>x.Client).HasForeignKey(x=>x.ClientID);
        modelBuilder.Entity<Client>().HasKey(x => x.ID);
        modelBuilder.Entity<Machine>().HasKey(x => new { x.ID, x.ClientID });
        modelBuilder.Entity<MachineBob>().HasOne(x => x.Machine).WithMany(x => x.MachineBobData).HasForeignKey(x => new { x.MachineID, x.ClientID}).OnDelete(DeleteBehavior.NoAction);
        modelBuilder.Entity<MachineMixer>().HasOne(x => x.Machine).WithMany(x => x.MachineMixerData).HasForeignKey(x => new { x.MachineID, x.ClientID }).OnDelete(DeleteBehavior.NoAction);
        modelBuilder.Entity<MachineBob>().HasKey(c => new { c.TimeStamp, c.ClientID, c.MachineID });
        modelBuilder.Entity<MachineMixer>().HasKey(x => new { x.TimeStamp, x.MachineID, x.ClientID });

and the class

[XmlRoot("Root"),Serializable]
public class MachineBob 
{
    [MaxLength(50)]
    [XmlElement("TimeStamp")]
    public DateTime TimeStamp { get; set; }
    [XmlElement("Temperature")]
    [MaxLength(50)]
    public int Temperature { get; set; }
    [MaxLength(50)]
    [XmlElement("Heartbeat")]
    public Boolean Heartbeat { get; set; }

    [MaxLength(50)]
    [ForeignKey("MachineID")]
    public int MachineID { get; set; }
    public Machine Machine { get; set; }
    [MaxLength(50)]
    [ForeignKey("ClientID")]
    public int ClientID { get; set; }
0
2/21/2020 3:38:06 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow