EF5 Many to Many join table fluent API - c#

Do I have to create a POCO class to represent a join table in a many to many relationship?
This is my scenario:
public class Event
{
public int EventId { get; set; }
public int OrganizerId { get; set; }
}
public class Person
{
public int PersonId { get; set; }
ICollection<Event> Events { get; set; }
}
public class Company
{
public int CompanyId { get; set; }
ICollection<Event> Events { get; set; }
}
Both a Person or a Company can organize an event, I thought to create a join table like
Table Events_People
PersonId
OrganizerId
Table Events_Companies
CompanyId
OrganizerId
I know that should easy to do if I create two POCOs classes, like
public class EventPerson
{
public int EventId { get; set; }
public Person PersonId { get; set; }
}
than with Fluent API something like
modelBuilder.Entity<Person>()
// PK
.HasKey(e => e.PersonId)
// FK
.HasMany(e => e.Events)
.WithRequired(e => e.PersonId);
Is there a way to avoid two POCOs and directly tell to the API to create the join table?
Thanks

You can do Many to Many with EF yes. The bigger question is that a good idea. For example you may find you have issues with cascade delete if you do and you need or want that. If you end up with 2 required relationships to the same entity ( not necessarily your specific sample code) but more generically matched to your question, then you can get EF compile/runtime errors unless you use WillCascadeOnDelete(false). Just in case people think, you can so therefore do it. Becareful.:-)
But you asked can you tell EF to create a Join
Did you not see a join table generated by EF ? I would have expected that. Im curious why not.
You can explicitly manage it:
HasMany(t => t.NavigationProperty1)
.WithMany(a => a.ReverseNavigationProperty)
.Map(c => c.ToTable("TheJoinTable"));
// rename keys as required, may not be required...
c.MapLeftKey("ColumnKeyName");
c.MapRightKey("ABetterName2");
BTW the example you suggest is exactly a pattern that is required to get around some CascadeOnDelete issues.

Related

Mapping unconventional one-to-many relationship in EntityFramework

I'm trying to code my object relationships with my database using EntityFramework (EFCore 5). My database has an unconventional many-to-many relationship. We have a Store table which has an integer ID primary key and related fields (name, et cetera), Customer, which has an ID primary key, a name, and other related fields.
Our StoreCustomer join table schema is the odd part. The join table has StoreID which points to a single Store, but instead of CustomerID, it is 'Customer Name'. It's not a one-to one relationship from StoreCustomer to Customer, because multiple Customers can have the same name. This is intended by the schema. Thus StoreCustomer to Store is a one-to-many relationship. My classes are laid out like so:
public partial class StoreCustomer
{
[Column("Store ID")]
public long StoreId { get; set; }
[Column("Customer Name")]
public string CustomerName { get; set; } = null!;
[Column]
public long Order { get; set; }
[ForeignKey(nameof(CustomerName))]
public ICollection<Customer> Customers { get; set; } = null!;
}
public partial class Customer
{
[Key]
[Column("Customer ID")]
public long CustomerId { get; set; }
[Column]
public string Name { get; set; } = null!;
}
And my OnModelCreating looks like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<StoreCustomer>(entity =>
{
entity.HasKey(entity => new { entity.StoreId, entity.CustomerName, entity.Order });
entity.HasMany(sc => sc.Customers)
.WithOne()
.HasPrincipalKey(sc => sc.CustomerName);
});
modelBuilder.Entity<Customer>(entity =>
{
entity.Property(e => e.CustomerId).ValueGeneratedNever();
});
OnModelCreatingPartial(modelBuilder);
}
The issue is, when I call 'context.StoreCustomers.Include(sc => sc.Customers);', the resulting query has an extra column "Customer"."CustomerName" which of course causes an exception. It's like EFCore is creating a new ghost property for this column and I don't understand why. Why is it doing this? Also, in the future when weird stuff like this happens, are there ways to debug EF to catch it in the act so I understand why it's doing what it's doing?
EDIT: Sorry, I almost forgot to mention - I am using MS Access, so I am using the unofficial EntityFrameworkCore.Jet libraries to access it. I'm not sure if that has anything to do with it, but it is the reason I am using EFCore 5, because the library doesn't support 6 yet.

Entity Framework : Invalid column name *_ID even with fluent api or data annotations

Odd issue that I've been looking at all day. I am working with Entity Framework 6. The issue I have is that I have three entities:
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
// other properties and relationships here
public int? OrderProfileId { get; set; }
public int OrderTemplateId { get; set; }
public virtual OrderProfile Profile { get; set; } // optional property
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderProfile
{
public int Id { get; set; }
// other properties
// added here 6/15/2021
public virtual OrderTemplate OrderTemplate{ get; set; }
}
public class OrderTemplate : EntityMetaData
{
public int Id { get; set; }
// other properties
public int? OrderProfileId{ get; set; }
public OrderProfile OrderProfile { get; set; }
}
In our model builder, we have these definitions:
modelBuilder.Entity<Order>()
.HasOptional(x => x.OrderProfile)
.WithMany(x => x.Orders)
.HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
But even with the above fluent api model, we get the error
Invalid column name 'OrderProfile_Id'
Throughout various testing I was unable to find why this issue was occurring, so I looked at our logs and found when this error started popping it's head up and then was able to find the changes associated to OrderProfile and found that the only change that was made was adding the relationship from OrderProfile to OrderTemplate.
When I removed that fluent api relationship OrderProfile to OrderTemplate, it worked as expected... I don't need that relationship to OrderTemplate, but would like it to be there, how can I establish a optional 1 to optional 1 relationship without breaking other relationships? Also, why would additional relationships be effected by this?
UPDATE 6/15/2021
So I found I had a reverse navigation property in the OrderProfile model:
public virtual OrderTemplate OrderTemplate{ get; set; }
removing that and the associated fluent relationship
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile);
Doing the above resolved the issue, but for some reason, the issue seems to have cascaded down to another relationship that has a circular reference like the above. The Order class is involved with this cascaded issue. I guess this is a pretty big cause for concern since this application worked fine for the last 4 years and for these relationships to be decaying like this is worrisome. Does anyone know why this is happening?
if you use the right naming convention, EF will do magic. in this sample, you don't need fluent API to relate entities.
public partial class Order : ILocationBearingObject
{
public int Id { get; set; }
public int? OrderProfileId { get; set; } //means HasOptional (nullable) and ForeignKey
//variable name must be OrderProfile not Profile
public virtual OrderProfile OrderProfile { get; set; }
}
public class OrderProfile
{
public OrderProfile()
{
Orders = new HashSet<Order>();
}
public int Id { get; set; }
//be aware circular reference at any conversion or mapping
public virtual ICollection<Order> Orders {get; set;} //means WithMany
}
I've got an error like this too. It's caused by unmatching OrderProfileId property in OrderTemplate class with the fluent api model
If I'm not wrong, you want the OrderProfile model a many to many relation between Order and OrderTemplate. Then if it was the case, add the nvaigation property in OrderProfile.
public class OrderProfile
{
public int Id { get; set; }
// other properties
public virtual ICollection<Order> Orders { get; set; }
public virtual OrderTemplate OrderTemplate { get; set; }
}
Then change the fluent api model to be like this
// the EF has modelled the relation for normal 1 to many relation
// modelBuilder.Entity<Order>()
// .HasOptional(x => x.OrderProfile)
// .WithMany(x => x.Orders)
// .HasForeignKey(x => x.OrderProfileId);
modelBuilder.Entity<OrderTemplate>()
.HasOptional(x => x.OrderProfile)
.WithOptional(x => x.OrderTemplate);
You're working database-first, which always leaves room for a mismatch between the actual database model and the model EF infers from class and property names and mapping code (= conceptual model). If this happens, it may help to make EF generate a database from the conceptual model and see where it creates the column it expects, OrderProfile_Id.
This is what you'll see when logging the SQL statements:
CREATE TABLE [dbo].[OrderTemplates] (
[Id] [int] NOT NULL IDENTITY,
[OrderProfileId] [int],
[OrderProfile_Id] [int],
CONSTRAINT [PK_dbo.OrderTemplates] PRIMARY KEY ([Id])
)
...
ALTER TABLE [dbo].[OrderTemplates]
ADD CONSTRAINT [FK_dbo.OrderTemplates_dbo.OrderProfiles_OrderProfile_Id]
FOREIGN KEY ([OrderProfile_Id]) REFERENCES [dbo].[OrderProfiles] ([Id])
There you see the expected nullable column OrderProfile_Id which is the FK to OrderProfiles. It's noteworthy to see that EF does not use OrderProfileId as a foreign key field. It's just a field that could be used for anything.
That's because EF6 doesn't support 1:1 associations as foreign key associations (reference property and primitive FK property).
Knowing this, the remedy is simple: remove the property OrderTemplate.OrderProfileId and tell EF to use the field OrderTemplate.OrderProfileId in the database:
modelBuilder.Entity<OrderProfile>()
.HasOptional(x => x.OrderTemplate)
.WithOptionalPrincipal(x => x.OrderProfile)
.Map(m => m.MapKey("OrderProfileId"));
That said, I wonder why Order has a foreign key to OrderProfile. Isn't its OrderProfile determined by its OrderTemplate? If it's a redundant relationship it may be better to remove it.

What type of relationship is this and how to implement it in .NET Core (EFCore, FluentAPI)?

The problem I have is pretty easy, but my mind stopped working. Sorry if my questions is dumb, but I'm not really good with databases (neither I am with EFCore).
I want to have the following tables:
CVs: with ID and Name/Title (string)
Skills: with ID and Name/Title (string)
SkillsCV: with ID, CvID (foreign key to a record in CVs), SkillID (foreign key to a record in Skills)
I don't want to have a foreign key to SkillsCV in the CVs and Skills tables. Is it possible? Is it possible in .NET Core and more importantly with Fluent API?
I've made a small research for FluentAPI and there are foreign keys in the both ends in one-to-one relationships. Is this needed? BTW, it is a one-to-one relationship, right? I don't want the one side to know about the other side. Is this what's called 0 to 1, or this is a completely different thing? I'm really confused.
So what I've seen for one-to-one relationships in FluentAPI, I need the following code:
modelBuilder.Entity<Skill>()
.HasOne(skill => skill.SkillCV) // but I don't have a SkillCV object in Skill model
.WithOne(skillCV => skillCV.Skill) // I have skillCV.Skill
.HasForeignKey<SkillCV>(skillCV => skillCV.SkillID); // I have this foreign key in skillCV as well
But I don't want to have an object (or foreign key) in the Skills table (as I don't want such in the CVs table). Is this possible? I'm for sure doing something wrong. Can you help me to find my mistake (if there is one)?
If you know a better way to do this, please share it. Thanks in advance!
EDIT: A quick example to what I want to create:
CVs Table:
ID, Name
1 "CV1"
2 "CV2"
3 "CV3"
Skills Table:
ID, Name
1 "C#"
2 "Java"
3 "Python"
SkillsCVs Table:
ID, CvID, SkillID
1 1 1
2 1 3
3 2 1
Is this a good solution to solve this problem? I haven't created the SkillsCVs table yet, now I have only CVs and Skills (every skill has a CV_ID), but this way when I need to populate a select box in the frontend, I need to return DISTINCT Skills from the API (because there are 800 C# for example records for different CVs). I thought a SkillsCVs table will solve this issue, but I'm not entirely sure now :D
What you have is a many-to-many relationship. You could model things like this:
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
}
public class CV
{
public int Id { get; set; }
public string Name { get; set; }
}
public class SkillCV
{
public int SkillID { get; set; }
public Skill Skill { get; set; }
public int CVID { get; set; }
public CV CV { get; set; }
}
Then to set this up with Fluent API, you could do this:
modelBuilder.Entity<SkillCV>()
.HasKey(t => new { t.SkillID, t.CVID});
modelBuilder.Entity<SkillCV>()
.HasOne(pt => pt.Skill)
.WithMany()
.HasForeignKey(pt => pt.SkillID);
modelBuilder.Entity<SkillCV>()
.HasOne(pt => pt.CV)
.WithMany()
.HasForeignKey(pt => pt.CVID);
This way your joins later will be simple, and the SkillCV table will have a composite key made up of SkillID and CVID (ensuring referential integrity).
You can do it only with annotations if I understand you correctly.
public class Skill
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class Cv
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class SkillCv
{
[ForeignKey("Skill")]
public int SkillId { get; set; }
[ForeignKey("Cv")]
public int CvId { get; set; }
public virtual Skill Skill{ get; set; }
public virtual Cv Cv { get; set; }
}
public class TestContext : DbContext
{
public DbSet<Skill> Skills { get; set; }
public DbSet<Cv> Cvs { get; set; }
public DbSet<SkillCv> SkillCvs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//The entity does not have its own key, only the relationship of the two
modelBuilder.Entity<SkillCv>().HasNoKey();
}
}
So you will have a row in the table for each skill that has a cv
var allSkillFromCv = await _context.SkillCvs.Where(s => s.CvId == 1).ToListAsync()
If you want to use the navigation property
var allSkillFromCv = await _context.SkillCvs.Where(s => s.CvId == 1).Include(s => s.Skill).ToListAsync();
You need three table here: CVs Table, Skills Table and SkillsCVs Table. You have a
Many-to-Many relationship between CVs Table and Skills Table. You have to define the Many-to-Many relationship using Fluent API. you need navigation property in both CVs Table and Skills Table
public class CV
{
....
public IList<SkillCV> SkillsCVs { get; set; }
}
public class Skill
{
....
public IList<SkillCV> SkillsCVs { get; set; }
}
in DB context
public DbSet<SkillCV> SkillsCVs { get; set; }
You also need to define relationship using Fluent API
modelBuilder.Entity<SkillCV>()
.HasOne<Skill>(sc => sc.Skill)
.WithMany(s => s.SkillsCVs)
.HasForeignKey(sc => sc.SkillID);
modelBuilder.Entity<SkillCV>()
.HasOne<CV>(sc => sc.CV)
.WithMany(s => s.SkillsCVs)
.HasForeignKey(sc => sc.CvID);

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.

Is there a way to model an optional many-to-many relationship in Entity Framework?

Is there a way in Entity Framework (and I assume it will be with fluent syntax as data annotations are somewhat limited) to model a many-to-many relationship in which both sides are optional (a 0..M to 0..N relationship)? The use case is this: I would like to allow users to add tags to entities. Tags to entities is a M:N relationship, but neither should be required. That is, a tag can exist that is not applied to any entities and an entity can be untagged. This seems fairly reasonable to me. I can't simply model this using:
public virtual ICollection<Tag> Tags { get; set; }
and
public virtual ICollection<Entity> Entities { get; set; }
because each class has other relationships, and I get a "foreign key constraint may cause cycles or multiple cascade paths." I was hoping maybe I could do something like:
modelBuilder.Entity<Tag>().HasOptional(t => t.Entities);
modelBuilder.Entity<Entity>().HasOptional(t => t.Tags);
but I am warned that EF is "Unable to determine the principal end of the association." From reading, it seems that such relationships HAVE to have a principal end, but in my case, that's undesirable.
I could add a class to represent the bridge table and handle the mapping manually, but I'd prefer not to clutter the code. I was wondering if there is another way to model this in EF.
To fill in a bit more detail, there is also an Author class (which amounts to Users). Authors and tags are 1:M and Authors to Entities are also 1:M. So of course, the problem is that the Entities class occurs twice in the cascade tree. Making the Tag/Entity relationship optional would fix this. I could also fix it if there was a way to get to Tags through Entities, but since Tags can exist without being connected to an entity, I figured that would be impossible.
Here's a summary of the related code:
public class Author
{
public Guid Id { get; set; }
public virtual List<Entity> Entities { get; set; }
public virtual List<Tag> Tags { get; set; }
}
public class Tag
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
public virtual ICollection<Entity> Entities { get; set; }
}
public class Entity
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
EDIT:
Using .HasMany().WithMany() as suggested below gives me this:
CREATE TABLE [dbo].[TagEntities] (
[Tag_Id] [uniqueidentifier] NOT NULL,
[Entity_Id] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_dbo.TagEntities] PRIMARY KEY ([Tag_Id], [Entity_Id])
)
but what I WANT is for Tag_Id and Entity_Id to be nullable on this table. Maybe this model doesn't make as much sense as I thought?? Can you have a bridge table where both sides are nullable?
Use
modelBuilder.Entity<Tag>().HasMany(t => t.Entities)
.WithMany(t => t.Tags);
Instead of
modelBuilder.Entity<Tag>().HasOptional(t => t.Entities);
modelBuilder.Entity<Entity>().HasOptional(t => t.Tags);
I don't know if this is the RIGHT answer, but I solved this by creating a base class called DbEntity that other classes inherited from. So now Author has just:
// Both entities and tags are part of this collection
public virtual List<DbEntity> Entities { get; set; }
Both "Entities" (which has special meaning in my code) and "Tags" subclass DbEntity. This eliminated the multiple cascade paths while preserving the navigation properties, although I do need to do this:
author.Entities.OfType<Tag>();
or
author.Entities.OfType<Entity>();
to get specific sets of entities.

Categories

Resources