Multiple one-to-one relationship with two independent tables and one dependent - c#

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?

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.

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();
}
}
}

Related

How can I create working lists and save them using the Entity Framework Core and ASP.NET Web API?

I am making a web app similar to google classroom in that you can join classes.
I have a class "Account" and inside that account I have a list that should hold the IDs of all the classes the account has joined. I tried to make the list a list of longs, but I couldn't do that because I got the error:
System.InvalidOperationException: 'The property
'Account._classesJoined' could not be mapped, because it is of type
'List' which is not a supported primitive type or a valid entity
type. Either explicitly map this property, or ignore it using the
'[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in
'OnModelCreating'.
The way I solved this problem is to create a class "JoinedClassId" to make a list of instead, with a property "classIdNumber". However, during testing, I noticed that the JoinedClassIds that I added to the the Account object were not saving. I think this is because I am not saving the database table for the JoinedClassId class.
Do I have to create a database context and controller for the JoinedClassId class? I don't want to be able to manipulate the JoinedClassId class from the API, I'm only using it as a data container. Is there a way I could either create a long list and save it or save the JoinedClassIds?
In EF Core "Many-to-many relationships without an entity class to represent the join table are not yet supported".
Book -> Category has many-to-may rel so this should create the 3 tables in DB :
Books, Category and BookCategory
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
//public ICollection<Category> Categories { get; set; } // cannot appear
// For the many-to-many rel
public List<BookCategory> BookCategories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
//public ICollection<Book> Books { get; set; } // cannot appear
// For the many-to-many rel
public List<BookCategory> BookCategories { get; set; }
}
// Class because of the many-to-many rel
public class BookCategory
{
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class MyContextDbContext : DbContext
{
public MyContextDbContext(DbContextOptions<MyContextDbContext> dbContextOptions)
: base(dbContextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookCategory>()
.HasKey(t => new { t.BookId, t.CategoryId });
modelBuilder.Entity<BookCategory>()
.HasOne(bctg => bctg.Book)
.WithMany(ctg => ctg.BookCategories)
.HasForeignKey(book => book.CategoryId);
modelBuilder.Entity<BookCategory>()
.HasOne(bctg => bctg.Category)
.WithMany(ctg => ctg.BookCategories)
.HasForeignKey(ctg => ctg.BookId);
}
public DbSet<Book> Book { get; set; }
public DbSet<Category> Category { get; set; }
}

Entity Framework 1 to Many creates new objects

I'm using EntityFramework for my Microsoft Sql Data Base.
First entity is Product:
public class Product
{
public Product()
{
ProductStories = new HashSet<ProductStory>();
}
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Deleted { get; set; }
public HashSet<ProductStory> ProductStories { get; set; }
}
And another entity is ProductStory, which stores story about income or outcome of Products.
public class ProductStory
{
public int ProductStoryId { get; set; }
public virtual Product.Product Product { get; set; }
public int Count { get; set; }
public DateTime DateTime { get; set; }
}
So one Product could be in mane ProductStories, or in none.
I will not show all code(too big), so when I firstly create a single Product instance and save it in DB. Then I create a single ProductStory and reference to property Product to that instance of Product.
Then I save this ProductStory, there becomes 2 instances of ProductStory.
As I read, and I made this as virtual property:
public virtual Product.Product Product { get; set; }
How this problem could be solved?
I'm using EntityTypeConfiguration for tables configuration.
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
ToTable("Products").HasKey(x => x.ProductId);
Property(x => x.ProductId).IsRequired();
Property(x => x.Name).IsRequired().HasMaxLength(255).HasColumnName("Name");
//.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Name") { IsUnique = true }));
Property(x => x.Description).IsOptional().HasColumnName("Description");
Property(x => x.Deleted).HasColumnName("Deleted");
}
}
And for ProductStory:
class ProductStoryMap: EntityTypeConfiguration<ProductStory>
{
public ProductStoryMap()
{
ToTable("ProductStories").HasKey(ps => ps.ProductStoryId);
Property(ps => ps.ProductStoryId).IsRequired();
//Property(ps => ps.ProductId).IsRequired().HasColumnName("ProductId");
Property(ps => ps.Count).HasColumnName("Count");
Property(ps => ps.DateTime).HasColumnName("DateTime");
}
}
You have some errors in your code:
//Change this:
public HashSet<ProductStory> ProductStories { get; set; }
//For this (virtual is needed here, also use ICollection rather than any specific implementation)
public virtual ICollection<ProductStory> ProductStories { get; set; }
//Change this:
public virtual Product.Product Product { get; set; }
//For this (virtual makes no sense here)
public Product.Product Product { get; set; }
And lastly, ProductStory needs a way to keep the reference to its parent Product. This is what creates the Foreign Key relationship in your database and allows Entity Framework to link the tables. So add this to ProductStory:
public int ProductId { get; set; }
If you are still getting a duplicated object (which may happen), ensure you are setting the ProductId to the ProductStory you are saving.
The solution was about Entity Framework "bug/feature".
As I add new ProductStory into DataBase, it attaches the whole graph(including all other entities references and recreates them).
So before commiting new ProductStory, I have to set to null all it's navigation properties to avoid recreating.

Entity Framework code-first approach one-to-one fluent api mapping

I'm trying to create a one-to-one mapping with Entity Framework code-first (including fluent API mapping) approach. This is the first time I'm using code first approach.
When I run the UpdateTaskCompleted() method, it throws the following exception:
Operand type clash: uniqueidentifier is incompatible with int
I suspect that I'm doing something wrong in fluent API mapping.
[Table("tblSession")]
public partial class tblSession
{
[Key]
public Guid SessionId { get; set; }
[Required]
public bool IsActive { get; set; }
public tblTaskDetail tblTaskDetail { get; set; }
}
[Table("tblTaskDetail")]
public partial class tblTaskDetail
{
[Key]
public int TaskDetailID { get; set; }
public Guid? SessionID { get; set; }
[Required]
[StringLength(50)]
public string TaskStatus { get; set; }
[ForeignKey("SessionID")]
public tblSession tblSession { get; set; }
}
public class RequestSession
{
[Key]
public Guid SessionId { get; set; }
public bool IsActive { get; set; }
public TaskDetail TaskDetail { get; set; }
}
public class TaskDetail
{
[Key]
public int TaskDetailID { get; set; }
public Guid? SessionID { get; set; }
public string TaskStatus { get; set; }
public RequestSession RequestSession { get; set; }
}
public class TaskDetailMapper:EntityTypeConfiguration<TaskDetail>
{
public TaskDetailMapper()
{
this.ToTable("tblTaskDetail");
this.HasKey(hk => hk.TaskDetailID);
HasRequired<RequestSession>(a => a.RequestSession)
.WithRequiredPrincipal(o => o.TaskDetail).Map(m => m.MapKey("SessionID"));
this.Property(o => o.TaskStatus).HasColumnName("TaskStatus");
}
}
public class RequestSessionMapper : EntityTypeConfiguration<RequestSession>
{
public RequestSessionMapper()
{
// Table & Column Mappings
this.ToTable("tblSession");
//Primary key
this.HasKey<Guid>(hk => hk.SessionId);
this.Property(t => t.SessionId).HasColumnName("SessionId");
this.Property(t => t.IsActive).HasColumnName("IsActive");
}
}
public partial class WarehouseAPIContext : DbContext
{
public WarehouseAPIContext(): base("name=WarehouseAPIContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new RequestSessionMapper());
modelBuilder.Configurations.Add(new TaskDetailMapper());
}
}
public TaskDetail UpdateTaskCompleted(TaskDetail entity)
{
try
{
var entry = dbSet.Find(entity.TaskDetailID);
entry.TaskStatus = entity.TaskStatus;
entity.RequestSession = new RequestSession()
{
IsActive = false
};
_context.SaveChanges();
return entity;
}
catch (Exception ex)
{
throw ex;
}
}
TaskDetail.Id is of type int and Session.Id is of type Guid.
Firstly, I would choose to use either Annotation, or FluentAPI for configuring your model. There are edge-cases where features can only be done in one approach and not the other, but these are rare only a small handful and well documented.
I use FluentAPI as it is more expressive, and allows for all of the configuration to be in one place.
What you need to do here, is check out this very good resource on EF relationships: http://www.entityframeworktutorial.net/entity-relationships.aspx
A google for any entity framework issue/feature will have this site in its top results on the first page - Take some time and do a good bit of research before asking questions - Everyone is more than happy to help with answers, but by researching and reading material looking for the solution to your issues is where the real value will come from, as you will learn a lot more than just how to fix your current issue.

Optional 1:1 relationship in Entity Framework Code First

I'm trying to create what I think would be either called an optional 1:1 or possibly 0..1:0..1 relationship in Entity Framework. I want to be able to have navigation properties on both objects.
I am using Entity Framework's Fluent API over an existing database schema.
For simplicity, lets assume the following tables:
Car
Id int not null
Driver
Id int not null
CarId int null unique
Using the following classes:
public class Car
{
public int Id { get; set; }
public virtual Driver { get; set; }
}
public class Driver
{
public int Id { get; set; }
public virtual Car { get; set; }
}
The idea is a Car and a Driver can exist independent of one another, but when a Driver gets associated with a Car it is a mutually exclusive association: the Driver can only be associated with that Car and that Car can only be associated to that Driver.
I tried the following fluent configuration:
Inside Driver's Configuration:
HasOptional(d => d.Car)
.WithOptionalDependent()
.Map(d => d.MapKey("CarId"));
And inside the Car configuration
HasOptional(c => cDriver)
.WithOptionalPrincipal()
.Map(d => d.MapKey("CarId"));
When I try this I get the following:
Schema specified is not valid. Errors:
(203,6) : error 0019: Each property name in a type must be unique. Property name 'CarId' was already defined.
Is there a way to model this scenario with navigation properties on both objects in Entity Framework?
You don't need to set it up in both fluent classes. I'm surprised that is the error that you received, and not that the relationship is already set up.
Your Drive class will need the CarId as part of the class:
public class Driver
{
public int Id { get; set; }
// Make this int? if a Driver can exist without a Car
public int CarId { get; set; }
public virtual Car { get; set; }
}
Then you just need this in the Fluent Config file for Driver, and nothing in the one for Car.
HasOptional(d => d.Car)
.WithOptionalDependent()
.Map(d => d.MapKey("CarId"));
You can do this without Fluent API:
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public int? DriverId { get; set; }
[ForeignKey("DriverId")]
public virtual Driver Driver { get; set; }
}
public class Driver
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Car> Cars { get; set; }
}
Then you need to check if the Driver already has a car, to guarantee that he can have only one.

entity framework - many to many relationship

Hi I try use Many to Many relationship with EF Fluent API. I have 2 POCO classes.
public class Project
{
public int ProjectId { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public Project()
{
Authors = new List<Author>();
}
}
public class Author
{
public int AuthorId { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public Author()
{
Projects = new List<Project>();
}
}
And I map many to many relationship with this part of code:
////MANY TO MANY
modelBuilder.Entity<Project>()
.HasMany<Author>(a => a.Authors)
.WithMany(p => p.Projects)
.Map(m =>
{
m.ToTable("ProjectAuthors");
m.MapLeftKey("ProjectId");
m.MapRightKey("AuthorId");
});
This created table ProjectsAuthors in DB. It is my first attempt with this case of relationship mapping.
If I omitted this mapping it created table AuthorProject with similar schema. It is correct bevahior?
By trial and error I found the following. Given two classes...
public class AClass
{
public int Id { get; set; }
public ICollection<BClass> BClasses { get; set; }
}
public class BClass
{
public int Id { get; set; }
public ICollection<AClass> AClasses { get; set; }
}
...and no Fluent mapping and a DbContext like this...
public class MyContext : DbContext
{
public DbSet<AClass> AClasses { get; set; }
public DbSet<BClass> BClasses { get; set; }
}
...the name of the created join table is BClassAClasses. If I change the order of the sets...
public class MyContext : DbContext
{
public DbSet<BClass> BClasses { get; set; }
public DbSet<AClass> AClasses { get; set; }
}
...the name of the created join table changes to AClassBClasses and the order of the key columns in the table changes as well. So, the name of the join table and the order of the key columns seems to depend on the order in which the entity classes are "loaded" into the model - which can be the order of the DbSet declarations or another order if more relationship are involved - for example some other entity refering to AClass.
In the end, it doesn't matter at all, because such a many-to-many relationship is "symmetric". If you want to have your own name of the join table, you can specify it in Fluent API as you already did.
So, to your question: Yes, naming the join table AuthorProjects is correct behaviour. If the name had been ProjectAuthors it would be correct behaviour as well though.

Categories

Resources