Many-to-many relationships using EF Code First - c#

I have two classes defined as such:
public class Questionnaire
{
public int QuestionnaireID { get; set; }
public string Title { get; set; }
public bool Active { get; set; }
public virtual ICollection<Question> Questions { get; set; }
public virtual ICollection<Vendor> Vendors { get; set; }
}
public class Vendor
{
public int VendorID { get; set; }
public string VendorName { get; set; }
public virtual ICollection<Questionnaire> OpenQuestionnaires { get; set; }
public virtual ICollection<Questionnaire> SubmittedQuestionnaires { get; set; }
public virtual ICollection<QuestionnaireUser> QuestionnaireUsers { get; set; }
}
I beleive this is the correct way to establish a many-to-many relationship between these classes, and when the project is built, I would expect three tables to be created.
However, when I attempt to to relate one Questionnaire to two different Vendors, I receive the following error when attempting to save the changes (context.SaveChanges()):
*Multiplicity constraint violated. The role 'Vendor_OpenQuestionnaires_Source' of the relationship 'QuestionnaireApp.Models.Vendor_OpenQuestionnaires' has multiplicity 1 or 0..1.*
If I assign a Questionnaire to only one Vendor, save the changes and then assign it to another and again save changes I no longer get the error; however the Questionaire is then related only to the last Vendor to which it was assigned, indicating that (at best) there is a one-to-many relationship being created.
I'm hoping that there is something wrong with the way I'm declaring the many-to-many relationship between these classes, or perhaps there is something I need to add to the context class to "encourage" the relationsip, but perhaps many-to-many relationships like this are not supported, or cannot be created using "Code First"?
Thank you for your time,
Jason

If you don't have any Fluent API code your expected mapping relies on EF Code First conventions. The convention which you expect to kick in here is the AssociationInverseDiscoveryConvention. Now if you look in Intellisense (and probably also documentation) it says about this convention:
Convention to detect navigation properties to be inverses of each
other when only one pair of navigation properties exists between the
related types.
Now, that's the problem: You don't have only "one pair" of navigation properties between Questionnaire and Vendor. You have two collections in Vendor refering to Questionnaire and one collection in Questionnaire refering to Vendor. The result is that this convention doesn't get applied and EF maps actually three one-to-many relationships with only one end exposed as navigation property in the model.
Moreover the mapping you want to achieve is not possible with your model: You cannot map the one end Questionnaire.Vendors to the two ends Vendor.OpenQuestionnaires and Vendor.SubmittedQuestionnaires.
One workaround is to change your model the following way:
public class Vendor
{
public int VendorID { get; set; }
public string VendorName { get; set; }
[NotMapped]
public IEnumerable<Questionnaire> OpenQuestionnaires
{
get { return Questionnaires.Where(q => q.IsActive); }
}
[NotMapped]
public IEnumerable<Questionnaire> SubmittedQuestionnaires
{
get { return Questionnaires.Where(q => !q.IsActive); }
}
public virtual ICollection<Questionnaire> Questionnaires { get; set; }
public virtual ICollection<QuestionnaireUser> QuestionnaireUsers { get; set; }
}
Now Vendor.Questionnaires is mapped to Questionnaire.Vendors (AssociationInverseDiscoveryConvention should detect this) and the helper properties OpenQuestionnaires and SubmittedQuestionnaires allow you to pull out the selected items. (I'm not sure if IsActive is your distinguishing flag. Otherwise you have to introduce some new flag.)
The [NotMapped] attribute is just here to make it explicite. It is probably not necessary because EF won't map IEnumerable collections and readonly properties with only a getter anyway.

Go figure, after an hour or so of searching, I go and find the exact answer 30 seconds after I post my question.
The solution was to add the following to the context class:
modelBuilder.Entity<Vendor>()
.HasMany<Questionnaire>(x => x.OpenQuestionnaires)
.WithMany(x => x.Vendors)
.Map(x =>
{
x.MapLeftKey("vID");
x.MapRightKey("qID");
x.ToTable("VendorQuestionnaires");
});
I found the answer by reading this Stack Overflow post: EF Code First Many-to-Many not working

Related

Need help on table design with Entity Framework Core

I am developing a simple web application where a doctor is adding multiple prescription records for patients and will select multiple drugs while doing prescription. So one patient has multiple prescription and one prescription has multiple selected drugs. I have taken one another table patientrecords for reporting purpose/Normalization perspective where I am referencing patientID and PrescriptionID.
One patient --> many prescriptions --> one to many relationship
One prescriptions -> many drugs --> one to many relationship
Below is the model for patient, prescription and drugs, PatientRecord table.
While running migration, I get this error:
Error Number:1769,State:1,Class:16
Foreign key 'FK_Drugs_Prescriptions_PrescriptionID' references invalid column 'PrescriptionID' in referencing table 'Drugs'.
I am confused with explanation of one to many relationships on Microsoft website.
Can anyone help me with it?
There are two ways to configure the relationships in EF Core
Conventions :By default, a relationship will be created when there is a navigation property discovered on a type. Not applicable to many-to-many relationship
Fluent API:you start by identifying the navigation properties that make up the relationship. HasOne or HasMany identifies the navigation property on the entity type you are beginning the configuration on. HasOne/WithOne are used for reference navigation properties and HasMany/WithMany are used for collection navigation properties.
From your screenshots and the benjamin suggested, you could configure the model like below
Patient - Prescription --> one to many relationship
Prescription - Drug --> many to many relationship
public class Prescription
{
public int PrescriptionId { get; set; }
[Required]
public string Description { get; set; }
[Required]
public DateTime PrescriptionDate { get; set; }
public int PatientId { get; set; }
public Patient Patient { get; set; }
public ICollection<DrugPrescription> DrugPrescriptions { get; set; }
}
public class Drug
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int CurrentStock { get; set; }
public int DrugCost { get; set; }
public string Description { get; set; }
public ICollection<DrugPrescription> DrugPrescriptions { get; set; }
}
//represent a many-to-many relationship by including an entity class for
//the join table and mapping two separate one-to-many relationships.
public class DrugPrescription
{
public int DrugId { get; set; }
public Drug Drug { get; set; }
public int PrescriptionId { get; set; }
public Prescription Prescription { get; set; }
}
//DbContext
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{}
public DbSet<Patient> Patient { get;set; }
public DbSet<Drug> Drug { get;set; }
public DbSet<Prescription> Prescription { get;set; }
public DbSet<PatientRecord> PatientRecord { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
#region Drug-Prescription Many-to-Many
builder.Entity<DrugPrescription>()
.HasKey(dp => new { dp.DrugId, dp.PrescriptionId });
builder.Entity<DrugPrescription>()
.HasOne(dp => dp.Prescription)
.WithMany(p => p.DrugPrescriptions)
.HasForeignKey(dp => dp.PrescriptionId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<DrugPrescription>()
.HasOne(dp => dp.Drug)
.WithMany(d => d.DrugPrescriptions)
.HasForeignKey(dp => dp.DrugId)
.OnDelete(DeleteBehavior.Restrict);
#endregion
}
}
There are a few things that don't quite look right here. Maybe if you clean them up you'll be close to spotting where the error is.
Firstly, I'm a bit confused by your PatientRecord class. It identifies itself with a PatientRecordId and it maps to a Patient, but it doesn't add any other information, so what is it for? If you're not going to add anything to that class, I think you can remove it from the model.
Secondly, your Prescription class maps to a collection of Drugs. That's perfect because you have a one-to-many relationship between them... so why does it also have an integer DrugId property? Unless you want the Prescription class to reference the Id of one single Drug as well as the collection of Drugs, I think you should remove it. It might be confusing Entity Framework and not giving you any value.
Thirdly, your Drug class maps to one Prescription (through its properties Prescription and PrescriptionId) but why? Presumably a drug can appear on multiple prescriptions, as it could be prescribed to many people, or prescribed to the same person several times. So I think you want to remove that too and replace it with a many-to-many relationship.
Finally, if you want to have a many-to-many relationship between Prescription and Drug (and I think you will) you probably need to add a DrugPrescription class, with a Drug property and a Prescription property, to create this many-to-many mapping.
I think if you do that you'll be a lot close to your goal, and your error message will probably go away.

One-to-one with different classes in EF Core 2.2

I'd like to follow this blog explaining how to configure one-to-one relationship. Its idea is that one entity gets a property of the other's type while the other one gets a property of the former's type plus an ID to it to create a foreign key.
My issue is, though, that I want to brake out the contact part of two different classes like so. The class SomeThing is already refactored and works well with the class Address. However, I'm not sure how to deal with the class SomeThingElse.
public class SomeThing
{
public Guid Id { get; set; }
//public string Street { get; set; }
//public string City { get; set; }
public Address Address { get; set; }
}
public class Address
{
public Guid Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
public Guid SomeThingId { get; set; }
public SomeThing SomeThing { get; set; }
}
public class SomeThingElse
{
public Guid Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
//public Address Address { get; set; }
}
I've tried adding a specialized class for managing the address of SomeThingElse but then, it makes no sense to break it out. I considered adding the two fields below but rejected the idea as poor design for the DB.
public class Address
{
...
public Guid SomeThingElseId { get; set; }
public SomeThingElse SomeThingElse { get; set; }
}
Preferably, this is a school book case for inheritance introducing a base class Contactable and skipping Address altogether. But I recall from before that inheritance and EF don't mix well and that there's a lot of oopsies and gotchas to be expected in such case.
Is there a reliable best-practice for doing that? I haven't found anything that felt trustable enough when I googled.
As from the discussion in the comments, I am going into a details answer:
You can use EF Core newly introduced Owned Entity type feature where Address is the Owned Entity type of Something and SomethingElse while Something and SomethingElse are the owners as follows:
modelBuilder.Entity<SomeThing>().OwnsOne(st => st.Address);
modelBuilder.Entity<SomeThingElse>().OwnsOne(st => st.Address);
By convention, EF Core will name the database columns for the properties of the owned entity type following the pattern Navigation_OwnedEntityProperty. Therefore the Address properties will appear in the Something and SomethingElse table with the names 'Address_Street' and 'Address_City'.
Now if you don't want owned entity type column name to be like Navigation_OwnedEntityProperty then you can give your custom column name as follows:
modelBuilder.Entity<SomeThing>().OwnsOne(st => st.Address,
a =>
{
a.Property(p => p.Street).HasColumnName("Street");
a.Property(p => p.City).HasColumnName("City");
});
modelBuilder.Entity<SomeThingElse>().OwnsOne(ste => ste.Address,
a =>
{
a.Property(p => p.Street).HasColumnName("Street");
a.Property(p => p.City).HasColumnName("City");
});
Moreover owned types can be stored in a separate table from the owner. In order to override the convention that maps an owned type to the same table as the owner, you can simply call ToTable and provide a different table name as follows:
modelBuilder.Entity<SomeThing>().OwnsOne(st => st.Address,
a =>
{
a.ToTable("SomeThingAddress");
});
modelBuilder.Entity<SomeThingElse>().OwnsOne(ste => ste.Address,
a =>
{
a.ToTable("SomeThingElseAddress");
});
Querying owned types
When querying the owner the owned types will be included by default. It is not necessary to use the Include method, even if the owned types are stored in a separate table.
Limitations
Some of these limitations are fundamental to how owned entity types work, but some others are restrictions that we may be able to remove in future releases:
By-design restrictions:
You cannot create a DbSet<T> for an owned type
You cannot call Entity<T>() with an owned type on ModelBuilder
For more details: EF Core Owned Entity Types Limitations

Entity Framework Core still picks up old column

I recently delete a column ConversationId from my tables. When I start to debug my service and try to save I am getting an error:
Invalid column name 'ConversationId'.
Code:
public class AstootContext : DbContext
{
public AstootContext(DbContextOptions<AstootContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
public DbSet<ServiceRequest> ServiceRequests { get; set; }
}
And my entity looks like this:
public class ServiceRequest
{
public int Id { get; set; }
public int SenderUserId { get; set; }
public int PriceTypeId { get; set; }
public decimal Price { get; set; }
public bool IsAccepted { get; set; }
public DateTime Created { get; set; }
public int MessageId { get; set; }
}
All references to ConversationId were removed from the code, I've rebuilt, yet I'm still getting this error and I don't understand why.
This is my SQL Server table as you can see there is no ConversationId:
Is there a secret cache that I need to delete or something I have to run to update this?
EF Core is code based ORM, with the most important here being the M - Mapper. It doesn't matter what the actual database structure is, the important is what EF *thinks** it is based on your code model (entity classes and their properties, combined with data annotations, fluent configuration and set of conventions).
So the problem should originate from code. Since you've removed the explicit property, it should be caused by shadow property. And as explained in the documentation link, shadow properties are usually introduced by convention from relationships:
Shadow properties can be created by convention when a relationship is discovered but no foreign key property is found in the dependent entity class. In this case, a shadow foreign key property will be introduced.
The documentation also explains the naming rules applied in different scenarios.
A shadow property called ConversationId can be introduced in a several ways, but according to the provided information, the most likely cause is to have an entity class called Conversation defining one-to-many relationship with ServiceRequest by having a collection type navigation property:
public class Conversation
{
public int Id { get; set; }
// ...
public ICollection<ServiceRequest> ServiceRequests { get; set; }
}
Which according to your comment was indeed the case.
For completeness, here are some other possible scenarios generating such property:
(1) No collection navigation property in Conversation, reference navigation property in ServiceRequest:
public class Conversation
{
public int Id { get; set; }
// ...
}
public class ServiceRequest
{
// ...
public Conversation Conversation { get; set; }
}
(2) No navigation properties in Conversation and ServiceRequest, fluent configuration:
modelBuilder.Entity<Conversation>()
.HasMany<ServiceRequest>();
or
modelBuilder.Entity<ServiceRequest>()
.HasOne<Conversation>();
or variations of the above.
(3) No relationship involved, shadow property created through fluent configuration:
modelBuilder.Entity<ServiceRequest>()
.Property<int>("ConversationId");

Helper / intermediate class has no columns in database (Entity Framework)

First off, I'm new to the Entity Framework and am migrating an existing project from a database framework that I wrote myself so I have a fair amount of flexibility in the solution I choose.
From what I've researched so far everything appears to be set up correctly. However, when my database is constructed, the table for a helper class I wrote has no columns in it (outside of its primary key). The most simplified version of the classes are included below with their relationships defined in the fluent API.
Classes
public class Concept
{
public long ID { get; set; }
[Index(IsUnique = true), MaxLength(255)]
public string Name { get; set; }
}
public class Tag
{
public long ID { get; set; }
public virtual Content Subject { get; set; }
public virtual Concept Concept { get; set; }
}
public class Helper
{
public long ID { get; set; }
public virtual Content Subject { get; set; }
public virtual List<Tag> Instances { get; set; }
// Helper functionality
}
public class Content
{
public long ID { get; set; }
public virtual Helper Helper { get; set; }
public Content() { Helper = new Helper() { Subject = this }; }
}
Context
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Tag>()
.HasRequired(t => t.Concept);
modelBuilder.Entity<Tag>()
.HasRequired(t => t.Subject);
modelBuilder.Entity<Helper>()
.HasRequired(t => t.Subject)
.WithRequiredDependent(c => c.Helper);
modelBuilder.Entity<Helper>()
.HasMany(t => t.Instances);
modelBuilder.Entity<Content>()
.HasRequired(c => c.Helper)
.WithRequiredPrincipal();
base.OnModelCreating(modelBuilder);
}
Program.cs
static void Main(string[] args)
{
Content content = null;
using (var context = new Context())
{
content = context.Content.Find(1);
if (content == null)
{
content = new Content();
context.Content.Add(content);
context.Helper.Add(content.Helper);
context.SaveChanges();
}
}
}
It's also worth mentioning that when the data is saved, the Helper is assigned an ID but on loading the parent class (Content) the second time around, the Helper is not lazy loaded as I would expect from the 'virtual' keyword. I suspect that this is caused by the same issue causing the absence of data in the table.
I have tried both the data annotation and fluent API approaches that EF provides but it seems that there is something fundamental that I am misunderstanding. I would like to retain this helper class as it helps organize the code far better.
As I have spent a fair amount of time researching these relationships / APIs, and scouring Google / SO without found anything to solve this issue in particular any help would be greatly appreciated!
Updated: Solution
Thanks to a question in the comments, I realized that I was expecting to see the keys of a many-to-many relationship in the tables for the entity types themselves (i.e. in the Helpers table). However, in a many-to-many relationship, the keys will always be placed in a separate table (concatenation of type names) which was not being previously created.
By adding '.WithMany();' to the Helper section of the OnModelCreating function as below
modelBuilder.Entity<Helper>()
.HasMany(t => t.Instances)
.WithMany();
the many-to-many relationship became properly defined and the HelperTags table generated as expected. This is due to the fact that the many-to-many relationship is one way (Helpers always refer to Tags, Tags never refer to Helpers). This is also why the 'WithMany' does not have any arguments (since no Helper properties exist in the Tag class). Fixing this simple oversight solved the problem!
You are probably working harder than you need to in the on ModelCreate. You should probably redesign your classes use Identifiers, like this:
public class Tag
{
public long Id { get; set; }
public long SubjectId { get; set; }
public long ConceptId { get; set; }
public virtual Content Subject { get; set; }
public virtual Concept Concept { get; set; }
}
You need to keep the ID names the EXACT same as the object names + Id and EF will magically link everything up. If you don't want them required then make the id nullable (C# 6 == long? SubjectId).
Also, I have changed the ID -> Id; I have no idea if this matters. At one point I remember having to do that to get things working (it was YEARS ago) and I have been doing it that way ever since.
Consider reading:
Entity Framework Code First Conventions
relationship Convention
In addition to navigation properties, we recommend that you include
foreign key properties on the types that represent dependent objects.
Any property with the same data type as the principal primary key
property and with a name that follows one of the following formats
represents a foreign key for the relationship:
<navigation property name><principal primary key property name>
<principal class name><primary key property name>
<principal primary key property name>
If multiple matches are found then precedence is given in the order
listed above.
Foreign key detection is not case sensitive.
Sample Code from MSDN:
In the following example the navigation properties and a foreign key are used to define the relationship between the Department and Course classes.
public class Department
{
// Primary key
public int DepartmentID { get; set; }
public string Name { get; set; }
// Navigation property
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
// Primary key
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
// Foreign key
public int DepartmentID { get; set; }
// Navigation properties
public virtual Department Department { get; set; }
}

Creating a master table with two child tables linking one-to-zero-or-one with EF 4.1

Using MVC EF4.1, I am trying to link a table (TableMaster) to TableChildOne (relationship one-to-zero-or-one) and also to TableChildTwo (also one-to-zero-or-one).
TableChildOne and TableChildTwo are not directly linked.
TablechildOne and TableChildTwo needs to share the primary key of TableMaster (I read this is not possible, any workarounds?)
I am including an image to make this a bit more clear, not sure if there should be foreign keys added somewhere, this is not an actual model created by the code, but is what i would like. not sure if there should be foreign keys somewhere?
image : http://www.davidsmit.za.net/img/untitled.png
My code below compiles, but when trying to add a controller, I get the error :
"An item with the same key has already been added"
public class TableMaster
{
public int TableMasterID { get; set; }
public DateTime ReportDate { get; set; }
public virtual TableChildOne TableChildOne { get; set; }
public virtual TableChildTwo TableChildTwo { get; set; }
}
public class TableChildOne
{
[Key]
public int TableMasterID { get; set; }
public String Description_1 { get; set; }
public virtual TableMaster TableMaster { get; set; }
}
public class TableChildTwo
{
[Key]
public int TableMasterID { get; set; }
public String Description_2 { get; set; }
public virtual TableMaster TableMaster { get; set; }
}
public class Context : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TableMaster>()
.HasOptional(p => p.TableChildOne).WithRequired(p => p.TableMaster);
modelBuilder.Entity<TableMaster>()
.HasOptional(p => p.TableChildTwo).WithRequired(p => p.TableMaster);
}
When I remove the second table completely, it works fine.
I used the below link as an example (tables OfficeAssignment and Student), which shows how to link a table one-to-zero-or-one. But I have trouble adding another table with the same linkage:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application
Any help will be appreciated.
Thanks
appelmeester
Could you give more background about why you want to do this? If you are sharing the primary key across three tables you are partitioning data. What development scenario are you trying to address. It sounds like you might be wanting to map an object inheritance, is that right?
If you truly only have a couple of Descriptions, then this is really just one table.
EDIT:
Cool. Because the business context of this request is a bit vague, I can't quite understand still, sorry. If you have a TableMaster and then some child tables, then this sounds like an inheritance tree. So with EF, you can choose many different strategies to model this (TPH, TPT etc). For this, I would suggest looking into TPT because this might allow you to get the granularity for how you want to clean up the data. Also, you get the benefit that the tables will be created, by default, largely like you have specified. Check this out for reference.

Categories

Resources