2

I have read a lot of related questions about this topic but none of them seemed to address my problem, so please bear with me. I am new to EF and trying to establish the following relationship, in ASP .NET MVC, using EF6:

I need to have two permanent tables, Drivers and Cars. I now need to create a relationship between these tables when a Driver is associated to a Car. But one Driver can only be assigned to one Car.

A Driver may not always be associated to a Car and vice-versa and I want to maintain both tables even if there isn't always an association between them, so that is why I believe I need to have an additional table exclusively to make this connection. Which I think will create a 1:1:1 relationship between these classes.

Below is the model for my POCO classes.

Models

public class Driver
{
    public int DriverID { get; set; }
    public string Name { get; set; }
    //other additional fields

    public DriverCar DriverCar { get; set; }
}

public class Car
{
    public int CarID { get; set; }
    public string Brand { get; set; }
    //other additional fields

    public DriverCar DriverCar { get; set; }
}

public class DriverCar
{
    public int DriverCarID { get; set; }

    public int DriverID { get; set; }
    public Driver Driver { get; set; }

    public int CarID { get; set; }
    public Car Car { get; set; }
 }

I have tried configuration the relationships using Fluent API but I believe I am doing it completly wrong since I have got errors such as:

Introducing FOREIGN KEY constraint 'FK_dbo.DriverCar_dbo.Car_CarId' on table 'DriverCar' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint or index. See previous errors.

Fluent Api

modelBuilder.Entity<DriverCar>()
                        .HasRequired(a => a.Driver)
                        .WithOptional(s => s.DriverCar)
                        .WillCascadeOnDelete(false);

modelBuilder.Entity<DriverCar>()
                        .HasRequired(a => a.Car)
                        .WithOptional(s => s.DriverCar)
                        .WillCascadeOnDelete(false);

I am really not sure if I am missing something or if there is some better approach to handle this situation and I would appreciate so much if someone can give me some feedback on how to solve this.


Update

Just found an interesting answer here: Is it possible to capture a 0..1 to 0..1 relationship in Entity Framework? Which I believe is exactly what I want: a 0..1 to 0..1 relationship. But all the mentioned options seem too complex and I'm not quite sure which one is the best or how to even correctly implement them.

Are these type of relationships supposed to be so hard to implement in EF? For example, I tried Option 1 but it created a 0..1 to many relationship from both tables - Driver to Car and Car to Driver. How am I suppose to create an unique association between them then?

Sfmar
  • 159
  • 1
  • 13
  • I think you have over-complicated this. The Car ID should be on the Driver table then. Adding another entity in between would be useful for many to many relationship but in this case simply adds overhead that is not needed. The CarID only has to be null on the driver table since you won't always have a car. – Mark Fitzpatrick Oct 26 '18 at 17:31
  • It ultimately becomes a many to many relationship. A driver could have driven two or more cars. The driver and the Car ID would be repeated in the Driver table. A third table is necessary for this scenario. – r lo Oct 26 '18 at 17:58
  • I agree the DriverCar table is not needed. This is a one-to-zero relationship per the questions. https://stackoverflow.com/questions/14701378/implementing-zero-or-one-to-zero-or-one-relationship-in-ef-code-first-by-fluent – Neil.Work Oct 26 '18 at 18:13
  • @MarkFitzpatrick the problem if there is no third table, to my understanding, according to the definition of 1:0..1 relationship, if there is no association between a Car and Driver, the dependent table (because you have to set a dependent end) row will disappear. For example, if you decide the Car is the dependent table if a row has no association to a Driver it will simply not appear and that is what I don't want. I need to have permanent tables of Driver and Car. – Sfmar Oct 29 '18 at 09:37
  • @Neil.Work I have tried following the answers provided in the link you shared but I keep getting this error: **The navigation property 'Driver' declared on type 'MyApp.Models.Car' has been configured with conflicting foreign keys.** Do you know why I might be getting this error? – Sfmar Oct 29 '18 at 11:08
  • Just found an interesting answer related to this question: https://stackoverflow.com/questions/21889367/is-it-possible-to-capture-a-0-1-to-0-1-relationship-in-entity-framework?noredirect=1&lq=1 . But I still can't figure out what is the best solution to implement, they all seem too complex. – Sfmar Oct 29 '18 at 11:24
  • Interesting to note that a Driver may or may not have a car (1:0 or 1:1), but Car also may or may not have a driver assigned (1:0 or 1:1). There isn't a way in EF that I know of to specify that both FKs are optional AND must both exist or be null without a third table. – John White Oct 30 '18 at 18:01
  • @JohnWhite Exactly, that is why I created a third table as you can see in the POCO classes. But, as I mentioned and which is my question, I tried configuring the relationship between these three tables and it doesn't work. It is basically the same problem another user refered to (check the link in the update) and the answer all involve workarounds that all seem to me too complex to solve this. – Sfmar Oct 31 '18 at 08:59

2 Answers2

2

Try this for your models. Virtual enables lazy loading and is advised for navigation properties. DataAnnotations showing the Foreign Keys (or use fluent) to be sure each relationship is using the correct key.

public class Driver
{
    public int DriverID { get; set; }
    public string Name { get; set; }
    //other additional fields

    public DriverCar? DriverCar { get; set; }
}

public class Car
{
    public int CarID { get; set; }
    public string Brand { get; set; }
    //other additional fields

    public DriverCar? DriverCar { get; set; }
}

public class DriverCar
{
    public int DriverCarID { get; set; }

    [ForeignKey("Driver")]
    public int DriverID { get; set; }
    public Driver Driver { get; set; }

    [ForeignKey("Car")]
    public int CarID { get; set; }
    public Car Car { get; set; }
 }

modelBuilder.Entity<Driver>()
                        .HasOptional(a => a.DriverCar)
                        .WithRequired(s => s.Driver)
                        .WillCascadeOnDelete(false);

modelBuilder.Entity<Car>()
                        .HasOptional(a => a.DriverCar)
                        .WithRequired(s => s.Car)
                        .WillCascadeOnDelete(false);

Note: Changed to Data Annotations for Foreign Keys. Inverted fluent statements. Fixed Driver to Car in second relationship.

John White
  • 705
  • 4
  • 12
  • I tried this but it doesn't recognize the .HasForeignKey. I get this error: 'EntityTypeConfiguration' does not contain a definitio for 'HasForeignKey' and no extension method accepting a first argument of type 'EntityTypeConfiguration could be found'. – Sfmar Oct 29 '18 at 09:40
  • You do have all three classes in your DbContext, right? – John White Oct 29 '18 at 18:32
  • Yes, I do have them. – Sfmar Oct 30 '18 at 10:01
0

Here is a simple way to create a one to zero. Note that I'm a fan of keeping the Id of all tables as just Id, not CarId etc, just my style. This is just a console app so once you add the EF nuget you could just copy/paste.

But the below code works with .net framework 4.6 and EF6.2 It creates the following tables

Car

  • Id (PK, int, not null)
  • Driver_Id (FK, int, null)

Driver

  • Id (PK, int, not null)

Under this schema a Car can have only one driver. A driver may still drive multiple cars though. I'm not sure if that's an issue for you or not.

    using System.Data.Entity;

    namespace EFTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                var connectionString = "<your connection string>";
                var context = new DatabaseContext(connectionString);

                var car = new Car();
                var driver = new Driver();

                context.Cars.Add(car);
                context.Drivers.Add(driver);
                car.Driver = driver;

                context.SaveChanges();

            }
        }

        public class Car
        {
            public int Id { get; set; }
            public virtual Driver Driver { get; set; }
        }
        public class Driver
        {
            public int Id { get; set; }
        }

        public class DatabaseContext : DbContext, IDatabaseContext
        {
            public DbSet<Car> Cars { get; set; }
            public DbSet<Driver> Drivers { get; set; }

            public DatabaseContext(string connectionString) : base(connectionString){ }

            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Car>()
                    .HasKey(n => n.Id)
                    .HasOptional(n => n.Driver);

                modelBuilder.Entity<Driver>()
                    .HasKey(n => n.Id);
            }

        }
    }

But if you REALLY wanted to enforce the constraint of only one mapping per car and driver, you could do it with the code below. Note that when you have the joining entity, you don't put it's Id anywhere on the joined entities.

using System.Data.Entity;

namespace EFTest
{
class Program
{
    static void Main(string[] args)
    {
        var connectionString = "your connection string";
        var context = new DatabaseContext(connectionString);

        //Create a car, a driver, and assign them
        var car = new Car();
        var driver = new Driver();
        context.Cars.Add(car);
        context.Drivers.Add(driver);
        context.SaveChanges();
        var assignment = new DriverAssignment() { Car_id = car.Id, Driver_Id = driver.Id };
        context.DriverAssignments.Add(assignment);
        context.SaveChanges();

        //Create a new car and a new assignment
        var dupCar = new Car();
        context.Cars.Add(dupCar);
        context.SaveChanges();
        var dupAssignment = new DriverAssignment() { Car_id = dupCar.Id, Driver_Id = driver.Id };
        context.DriverAssignments.Add(dupAssignment);

        //This will throw an exception because it will violate the unique index for driver.  It would work the same for car.
        context.SaveChanges();

    }
}

public class Car
{
    public int Id { get; set; }
}
public class Driver
{
    public int Id { get; set; }
}

public class DriverAssignment
{
    public int Car_id { get; set; }

    public int Driver_Id { get; set; }
}


public class DatabaseContext : DbContext, IDatabaseContext
{
    public DbSet<Car> Cars { get; set; }
    public DbSet<Driver> Drivers { get; set; }

    public DbSet<DriverAssignment> DriverAssignments { get; set; }

    public DatabaseContext(string connectionString) : base(connectionString) { }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Car>().HasKey(n => n.Id);
        modelBuilder.Entity<Driver>().HasKey(n => n.Id);
        modelBuilder.Entity<DriverAssignment>().HasKey(n => new { n.Car_id, n.Driver_Id });
        modelBuilder.Entity<DriverAssignment>().HasIndex(n => n.Car_id).IsUnique();
        modelBuilder.Entity<DriverAssignment>().HasIndex(n => n.Driver_Id).IsUnique();
    }

}

}

Neil.Work
  • 985
  • 7
  • 9