EF 0 to Many code first database setup - c#

I am having trouble setting up my database to properly for 0 to many (not 1 to many). I have WordModel objects which represent a given word. Within each are ICollection<WordModel> representing the synonyms of that word as a noun, adverb, ect. The database is slowly filled out with queries to an online thesaurus API. The API is only queried when the synonyms of a given word are requested. My WordModel class:
[Key]
public int Id { get; set; }
public string Word { get; set; }
public WordType Traits { get; set; }
public bool SynonymsQueried { get; set; }
public virtual ICollection<WordModel> NounSynonyms { get; set; }
public virtual ICollection<WordModel> VerbSynonyms { get; set; }
public virtual ICollection<WordModel> AdjectiveSynonyms { get; set; }
public virtual ICollection<WordModel> AdverbSynonyms { get; set; }
I use the SynonymsQueried to check if this word has been queried or not. This is so I can populate the synonym collections with WordModel objects while keeping track of whether or not those word's themselves have been queried (or else I would query recursively for potentially a very long time). There is no reason for any WordModel to have any knowledge of any synonym lists it might be included in, I only want to keep track of its own personal synonyms (which will remain null or empty until specifically queried).
The database that is generated from this model is a single table with a 1 to many relationship. Every WordModel has an Id referencing to a parent WordModel which wont make sense if multiple WordModel objects include another WordModel in their synonym collection.
I could make the relationship Many-To-Many but I don't know how to force this using Code First. It is also important to note that I have no experience with Model First or Database First EF, I am hoping this is possible with Code First.

What you want are four many-to-many self-referencing relationships without reverse navigation properties. Please try the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<WordModel>()
.HasMany(x => x.NounSynonyms).WithMany()
.Map(x => x.ToTable("WordModelNounSynonym")
.MapLeftKey("WordModelId").MapRightKey("SynonymId"));
modelBuilder.Entity<WordModel>()
.HasMany(x => x.VerbSynonyms).WithMany()
.Map(x => x.ToTable("WordModelVerbSynonym")
.MapLeftKey("WordModelId").MapRightKey("SynonymId"));
modelBuilder.Entity<WordModel>()
.HasMany(x => x.AdjectiveSynonyms).WithMany()
.Map(x => x.ToTable("WordModelAdjectiveSynonym")
.MapLeftKey("WordModelId").MapRightKey("SynonymId"));
modelBuilder.Entity<WordModel>()
.HasMany(x => x.AdverbSynonyms).WithMany()
.Map(x => x.ToTable("WordModelAdverbSynonym")
.MapLeftKey("WordModelId").MapRightKey("SynonymId"));
}

Related

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.

How to create relationships between models that are found on different DbContext?

I am developing an application following the DDD pattern.
I have the following contexts for employee management and user account management called
EmployeeManagementContext
and
UserAccountManagementContext
Both contexts are on a separate project.
The project for employee management has the following models.
public class Employee
{
public int Id { get; private set; }
public string Name { get; private set; }
public DateTime DateOfBirth { get; private set; }
}
The project for user account management has the following models.
public class Employee
{
public int Id { get; private set; }
public string Name { get; private set; }
}
public class UserAccount
{
public int Id { get; private set; }
public string Username { get; private set; }
public string Password { get; private set; }
}
EmployeeManagementContext
public class EmployeeManagementContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
}
UserAccountManagementContext
public class UserAccountManagementContext : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set; }
}
I can successfully migrate both context by having different context keys but the problem is I loose the relationship between the Employee and UserAccount models.
Basically, the business rules that I need to implement between the two models are as follow:
An Employee may or may not have a UserAccount.
A UserAccount is owned by exactly one Employee only.
This means that I should have a one to zero-or-one relationship between Employee and UserAccount like the diagram below.
Please disregard the wrong relationship notation, its seems to be a limitation of the tool I am using but it is a one-to-zero-or-one relationship I assure you.
I tried the following configurations in UserAccount project:
public class UserAccountConfiguration : EntityTypeConfiguration<UserAccount>
{
HasKey(x => x.Id);
Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
HasKey(x => x.Id);
Property(x => x.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasOptional(x => x.UserAccount)
.WithRequired(x => x.Employee);
}
public class UserAccountManagementContext : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserAccountConfiguration());
modelBuilder.Configurations.Add(new EmployeeConfiguration());
base.OnModelCreating(modelBuilder);
}
}
The above configurations result to an error because Employee table already exist because of the EmployeeManagementContext in employee management project.
If I try to add the following configuration in EmployeeConfiguration of employee management project,
ToTable("Users");
EF doesn't complain anymore and creates a Users table which then creates the relationship I need between Employee and UserAccount. But the problem is, if I try to query Employee/Users in UserAccountManagementContext, it doesn't contain anything and I don't think its good design to keep on creating smaller tables that is derived from the orginal table because it will only clutter the database, right?...
I would greatly appreciate your help, thanks.
You should focus more on the domain and less on the database.
From what I can see, you have two Aggregates (Employee and UserAccount), in possible 2 bounded contexts (I can't name them as I don't have enough data). In general it's not recommended to force any invariant in a strongly consistent manner between the two Aggregates but there are exceptions. They may be as well in different databases, having different technologies. Let's now see how you can enforce the two invariants:
An Employee may or may not have a UserAccount.
This can be modeled with a nullable UserAccountId on a Employee, without any low level database references. Depending on the business rules, when an UserAccound is deleted (if this is a valid business operation on it), using a Saga/Process manager, you can set to null the corresponding UserAccountId in the Employee that had this account.
A UserAccount is owned by exactly one Employee only.
The simplest way to enforce this invariant is of technological nature: create an unique index on the UserAccountId. Other solutions imply using Sagas but are not as good as this one, for example would permit for a short period of time for the invariant to be broken.

Entity Framework : Handle multiple relationships between two entities

There are two entities:
Group
Yuvak - Person
"Other" person is designed in back-end who has no group. (null)
A Yuvak - Person will always have one HomeGroup. (1=>1) And will have no groups to control.
A Nirikshak(Head) - Person will always have one HomeGroup. (1=>1) But he will also have multiple groups to control - GroupsOfNirikshak. (1=>Many)
A Group will have multiple Yuvaks (1=>Many)
and all groups mostly will have only one Head. (Initially a new group might not have any head but zero or more yuvaks-persons.)
[Table("Group")]
public class Group
{
[Key]
public int Id { get; set; }
.....
public virtual List<Yuvak> Yuvaks { get; set; }
[ForeignKey("Nirikshak")]
public int? NirikshakId { get; set; }
public virtual Yuvak Nirikshak { get; set; }
}
[Table("Yuvak")]
public class Yuvak
{
[Key]
public int Id { get; set; }
.....
[ForeignKey("HomeGroup")]
public int? HomeGroupId { get; set; }
public virtual Group HomeGroup { get; set; }
public virtual List<Group> GroupsOfNirikshak { get; set; }
}
I already has provided two foreign keys for 1=>1 relationships (nullable) in both entities.
And now to manage many to many relationship it should automatically create a third table with "Yuvak_Id" and "Group_Id" columns if they are not null. But as here the FKs are null; instead of creating a third table it adds a foreign key column in both the tables.(Group:"Yuvak_Id" and Yuvak:"Group_Id")
What should I do so that to maintain Yuvak-HomeGroup it should use above provided foreign keys only and for many to many relationship (Head-GroupsOfNirikshak & Group-Yuvaks )it should create a seperate table.
Can I create a separate table for many to many relationship like : (ID,YuvakID,GroupID) How can I do that?
I tried to do that but got different errors like below :
The navigation property 'HomeGroup' declared on type
'YTKGoshthiManagment.Models.Yuvak' has been configured with
conflicting foreign keys.
Multiplicity is not valid in Role 'Yuvak_HomeGroup_Target' in
relationship 'Yuvak_HomeGroup'. Because the Dependent Role
properties are not the key properties, the upper bound of the
multiplicity of the Dependent Role must be '*'.
.....
and so on.
Use the "Fluent Api" !
In your context class write (for example) :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Group>()
.HasOptional(t => t.Nirikshak)
.WithMany(t => t.GroupsOfNirikshak)
.HasForeignKey(t => t.NirikshakId);
}
You can remove the annotations on the classes and properties. Once you have a Model-based Class on another, Entity Framework will automatically create a foreign key relationship on it. It will process Yuvak as a node on the Group Graph object. You need not declare the annotations since EF will do that for you automatically.

Zero-or-one-to-one relation and one-to-many relation using annotations in EF6

I am trying to create a migration for a code-first model in which one entity has two relations to another entity. One relation is 1:N and the other is 0:1:
public class Checkpoint
{
[Key, ForeignKey("Count")]
public long Id { get; set; }
// ...
public virtual Count Count { get; set; }
}
public class Count
{
public long Id { get; set; }
public virtual Checkpoint CurrentCheckpoint { get; set; }
public virtual ICollection<Checkpoint> Checkpoints { get; set; }
// ...
}
The idea behind this is that there is one count (the process of counting things, might take some time) which has many checkpoints, and one of those checkpoints is the "most recent" checkpoint which serves as an indicator for doing calculations.
I have tried a myriad of combinations of Required, InverseProperty and ForeignKey but haven't been able to make this work. The closest I've gotten generated column names that won't work for us, and I think wasn't correct anyway, something like this:
.ForeignKey("dbo.Count", t => t.Count_Id)
.ForeignKey("dbo.Count", t => t.Count_Id1)
.Index(t => t.Count_Id)
.Index(t => t.Count_Id1);
Unfortunately these names don't match the class properties and aren't descriptive, so I would need to change them (which is how I got started on this journey). Also I would think it should use t.Id in both cases...but maybe not. Is this possible with annotations in EF6?

Can't map one to zero or one relation

I am using code first to generate tables.
I have User object:
public class ApplicationUser
{
public int? ImageId { get; set; }
public virtual Image Image { get; set; }
public virtual ICollection<Image> Images { get; set; }
and class Image:
public class Image
{
public int ImageId { get; set; }
public int CreatedBy { get; set; }
public virtual ApplicationUser CreatedByUser { get; set; }
I map objects via fluent api:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// User can create many images
modelBuilder.Entity<Image>()
.HasRequired(e => e.CreatedByUser)
.WithMany(e => e.Images)
.HasForeignKey(e => e.CreatedBy)
.WillCascadeOnDelete(false);
// User can and doesn't have to have image
modelBuilder.Entity<ApplicationUser>()
.HasRequired(e => e.Image)
.WithOptional()
.WillCascadeOnDelete(false);
The first part create me one relation UserId > CreatedBy fine.
But second relation is 1:1 and related fields are UserId > ImageId which is not what I am trying to make.
I have tried to use HasOptional instead HasRequired but then I get additional keys in tables.
What should I do to map this two tables?
UPDATE 1
Based on answer. I leave User and Image classes the same.
User have One image (for profile) and list of images (all other images that user created).
And I use fluent api to connect tables:
But EF generate me additional key and doesn't use User > ImageId as key I can't understand why?
modelBuilder.Entity<ApplicationUser>()
.HasOptional(e => e.Image)
.WithOptionalDependent()
.WillCascadeOnDelete(false);
Unfortunately, EF does not support 1:1 mappings in this way. If you think about it, the reason should be obvious. How would you model this in a database? You can't. The best you can do is create dual 1:many and many:1 connections.
ie, if you have two ApplicationUser rows, they could both have the same ImageId. There's no way to guarantee that there is only one row (at least not without constraints, which EF doesn't support).
EF only supports 1:1 when using a shared primary key. That means both entities have to use the same Key name, and they both have to be primary keys, and one has to also make the other a Foreign key.

Categories

Resources