I have a pretty simple need, but I can't figure out how to do it with EF core 2.1.1 in code first.
I have a table Right and a table Role:
Role
public int RoleId { get; set; }
public string Name { get; set; }
Right
public int RightId { get; set; }
public string Name { get; set; }
Usually, in a standard database, I would simply make an intersection table Named:
RoleRights(RoleId int, RightId int)
But it seems in ef core 2.1.1, you instead add navigation properties.
Role
public int RoleId { get; set; }
public string Name { get; set; }
public IEnumerable<Right> Rights { get; set; }
Right
public int RightId { get; set; }
public string Name { get; set; }
public IEnumerable<Role> Roles { get; set; }
A Role can contain any number of Right and a Right can be contained in any number of Role.
By doing:
modelBuilder.Entity<Role>().HasMany(r => r.Rights);
modelBuilder.Entity<Right>().HasMany(r => r.Roles);
It flattens my Role table and add a RightId instead of making an intersection table. Same thing for the Right table. It adds a RoleId.
In the Migration script:
migrationBuilder.AddColumn<int>(
name: "RightId",
table: "Roles",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RoleId",
table: "Rights",
nullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Rights_Roles_RoleId",
table: "Rights",
column: "RoleId",
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_Roles_Rights_RightId",
table: "Roles",
column: "RightId",
principalTable: "Rights",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
How can I configure my model to have an intersection table instead? In this case, it is generating a wrong schema. I cannot insert and empty Role or a Right in no Role. Thinking of it, I should probably never do that anyway, but it feels wierd to me.
Thanks for your time!
If anything is not clear, tell me what needs more detail and I'll clarify!
So I had followed something outdated. The solution is to explicitly make the join table.
public class RoleRight : IEntity
{
public int RoleId { get; set; }
public Role Role { get; set; }
public int RightId { get; set; }
public Right Right { get; set; }
}
With both Right and Role looking like this.
public class Right : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<RoleRight> RoleRights { get; set; }
}
With this configuration on the OnModelCreating
modelBuilder.Entity<RoleRight>().HasKey(rr=> new { rr.RightId, rr.RoleId });
modelBuilder.Entity<RoleRight>().HasOne(rr => rr.Right)
.WithMany(r => r.RoleRights)
.HasForeignKey(rr => rr.RightId);
modelBuilder.Entity<RoleRight>().HasOne(rr => rr.Role)
.WithMany(r => r.RoleRights)
.HasForeignKey(rr => rr.RoleId);
Which is basically the last section in the link I provided in the comment earlier.
I have no clue how I had missed it when I read the page the first time!
Related
I have been using the .Net 5 and EF Core 5 for a small web app. Given EF Core 5 supports many - many out of the box there is no need for a joining table.
I've run into an issue when updating a object that already exists in the DB. For my app I have Athletes and Parents which have the many - many relationship.
public class Athlete
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
public string ContactNumber { get; set; }
public string Street { get; set; }
public int Postcode { get; set; }
public string City { get; set; }
public StateEnum State { get; set; }
public DateTime DateofBirth { get; set; }
public DateTime DateSignedUp {get; set;}
public virtual ICollection<Parent> Parents { get; set; }
}
public class Parent
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
public string ContactNumber { get; set; }
public string Street { get; set; }
public int Postcode { get; set; }
public string City { get; set; }
public StateEnum State { get; set; }
public DateTime DateofBirth { get; set; }
public DateTime DateSignedUp {get; set;}
public virtual ICollection<Athlete> Athletes { get; set; }
}
When I try to update the existing athlete that has a relation ship with two other parents I get an error:
Violation of PRIMARY KEY constraint 'PK_AthleteParent'. Cannot insert
duplicate key in object 'dbo.AthleteParent'. The duplicate key value
is (31, 1)
[HttpPost]
public async Task<ActionResult<Athlete>> PostAthlete(Athlete athlete)
{
_context.Athletes.Update(athlete);
await _context.SaveChangesAsync();
return Ok(athlete));
}
From what I can tell when entity tries to update my Athlete it tries to insert new rows into the joining table even though the parents already exist in there. Is there a way to get entity to remove any records when the relationship is updated? Or is there a way to tell entity to take update the joining table to match the Athlete object that is passed in?
Given a simple example like this:
public class Foo {
Guid Id { get; set; }
ICollection<Bar> Bars { get; set; }
}
public class Bar {
Guid Id { get; set; }
ICollection<Foo> Foos { get; set; }
}
You can call clear() on a tracked instance of Foo, and then re-add the Bar instances that you want assigned. I've found this is a nice way to avoid the constraint exception - much easier than manually trying to figure out what Bars have changed.
var foo = context.Foos.Include(x => x.Bars).FirstOrDefault(x => x.Id == someGuid);
foo.Bars.Clear();
foo.Bars.Add(bar1);
foo.Bars.Add(bar2);
...
context.Update(foo);
context.SaveChanges();
I'm currently learning ASP.NET MVC and Web API.
I'm trying to create a User Model. Users can have any number of UserContacts. UserContacts reference the User it is a contact of and the User who is the contact. I have made a model called UserContact because attached to this Model is additional information.
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
[ForeignKey("UserID"), Column(Order = 0)]
[Required]
public User User { get; set; }
public int ContactID { get; set; }
[ForeignKey("ContactID"), Column(Order = 1)]
[Required]
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
So this gives me an error referring to cascading Delete. How do I set up a relationship like this where two foreign keys point to the same Model type? I have yet to grasp Entity Framework syntax as well. If I don't have an ICollection of UserContacts in the User model, does this hinder my ability to grab the UserContacts associated with that User?
When you have a foreign key and the foreign key columns are not nullable(means,required). EF will automatically tries to enable cascading delete on the relationsip. In your case, it will try to enable Cascading delete for both the foreign key columns and both of them points to the same user table! That is the reason you are getting this error. What if you have a UserContact record with Both UserId and ContactID points to the same User record. Cascading delete is confused now :)
Also, since one user can have more than one Contacts, We need a Contacts property on the User table to represent that. This will be a collection of UserContact's. Also this user can be a a contact of many other people. So Let's create another property for that.
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<UserContact> Contacts { set; get; }
public ICollection<UserContact> ContactOf { set; get; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
public User User { get; set; }
public int ContactID { get; set; }
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
And in your DbContext class, We can configure the foreign key relation ships and tell EF to disable cascade delete using fluent configuration inside the overridden OnModelCreating method. The below code will disable cascading delete on both the the relationships. But for your error to go away. disabling on one foreign key is enough.
public class YourDbContext: DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserContact>()
.HasRequired<User>(g=>g.User)
.WithMany(g=>g.Contacts)
.HasForeignKey(g=>g.UserID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserContact>()
.HasRequired<User>(g => g.Contact)
.WithMany(g => g.ContactOf)
.HasForeignKey(g => g.ContactID)
.WillCascadeOnDelete(false); // this one is not really needed to fix the error
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { set; get; }
public DbSet<UserContact> UserContacts { set; get; }
}
This will create the tables like you wanted with the necessary foreign keys.
There is not enough information for EF to figure out the relationships on the other side, so yes, you need collections. You can use the InverseProperty annotation to clarify (or fluent api statements):
public class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[InverseProperty("User")]
public Virtual ICollection<UserContact> Users{ get; set; }
[InverseProperty("Contact")]
public Virtual ICollection<UserContact> Contacts { get; set; }
}
public class UserContact
{
public int UserContactID { get; set; }
public int UserID { get; set; }
[ForeignKey("UserID"), Column(Order = 0)]
[Required]
public User User { get; set; }
public int ContactID { get; set; }
[ForeignKey("ContactID"), Column(Order = 1)]
[Required]
public User Contact { get; set; }
public DateTime ContactSince { get; set; }
}
http://www.entityframeworktutorial.net/code-first/inverseproperty-dataannotations-attribute-in-code-first.aspx
Note: Technoligies in use are ASP.Net MVC 3, Entity, SQL Server Management Studio
Problem?
It seems that when I run, the context as: public class DatabaseInit : DropCreateDatabaseAlways<LocationAppContext>
That it creates the database, but my service assignments table has an extra foreign key called
ServiceAssignment_Service when it shouldn't.
My service assignment model is as such:
namespace LocationApp.Models
{
public class ServiceAssignment
{
public int id { get; set; }
public int locationID { get; set; }
public int ServiceID { get; set; }
public virtual Location Location { get; set; }
public virtual ServiceAssignment Service { get; set;}
}
}
and the service model is as such:
namespace LocationApp.Models
{
public class Service
{
public Service()
{
this.ServiceAssignments = new HashSet<ServiceAssignment>();
}
public int id { get; set; }
public string name { get; set; }
public string description { get; set; }
public bool active { get; set; }
public string icon { get; set; }
public virtual ICollection<ServiceAssignment> ServiceAssignments { get; set; }
}
}
with that said, the relation ship is simple:
service assignments have many location id's and service id's.
why is this extra foriegn key being generated? the curent keys, that should e there is:
PK: Main PK for the table
FK 1: Location_ServiceAssignment
FK 2: Service_ServiceAssignment
Those are their, how ever this third one is baffling....
The second part is: If a location of id 2 has a service id of 2,3,6,7 How do I get all service id's returned, such that I can pass the object to a service query to get all information on the service based on the ID?
Update:
Context Class:
namespace LocationApp.DAL
{
public class LocationAppContext : DbContext
{
public DbSet<Content> Contents { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<ServiceAssignment> ServiceAssignments { get; set; }
public DbSet<Service> Services { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Location>().HasMany(sa => sa.ServiceAssignments);
modelBuilder.Entity<Service>().HasMany(sa => sa.ServiceAssignments);
}
}
}
I think you have to tell EF that Service.ServiceAssignments is the inverse navigation property of ServiceAssignment.Service and that Location.ServiceAssignments is the inverse of ServiceAssignment.Location. Right now with your mapping you only specify that Location or Service has many ServiceAssignments. EF will consider the navigation properties in ServiceAssignment as the ends of separate relationships.
Try instead the mapping:
modelBuilder.Entity<Location>()
.HasMany(l => l.ServiceAssignments)
.WithRequired(sa => sa.Location)
.HasForeignKey(sa => sa.LocationID);
modelBuilder.Entity<Service>()
.HasMany(s => s.ServiceAssignments)
.WithRequired(sa => sa.Service)
.HasForeignKey(sa => sa.ServiceID);
You can probably remove this mapping altogether as an alternative because EF should detect the right relationships by convention.
So, use either no mapping (=mapping by convention) or the full mapping (=specifying both ends of the relationships). Just the 50%-mapping you have used is likely the problem.
Models:
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
}
public class Resource
{
[Key]
public int ResourceId { get; set; }
public string ResourceName { get; set; }
public string ResourceDescription { get; set; }
}
public class UserResource
{
[Key, Column(Order=0)]
public int UserId { get; set; }
[Key, Column(Order=1)]
public int ResourceId { get; set; }
public int ResourceQuantity { get; set; }
}
I want to select "ResourceName" from Resource model and "ResourceQuantity" from UserResource model for a given "UserId". Also, once selected, do I need a brand new model to carry only those two specified columns?
Also note that UserResource model has a composite key so I am confused as to how to make the join... Is this right?
var userResources =
from r in imDB.Resources
join ur in imDB.UserResources
on r.ResourceId equals ur.ResourceId
select new { r.ResourceName, ur.ResourceQuantity };
Hence you're using Code first you can create your models are as below by using EF conventions.
public class User {
public int Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<Resource> Resources { get; set; }
}
public class Resource {
public int Id { get; set; }
public string ResourceName { get; set; }
public int ResourceQuantity { get; set; }
public virtual ICollection<User> Users {get;set;}
}
Then EF will generate your junction table is as UsersResources.You don't need to create additional model as you did.EF will look after that.
When using POCOs with EF, if you mark your navigation properties as
virtual you can use additional EF supports like Lazy Loading. So in
general use a virtual keyword in navigation properties considered to
be a good practice.
UPDATE
You may try something like below:
Method 1 : Method based syntax
imDB.Resources.Where(r => r.Users.Any(u => u.UserId == userId))
Method 2 : Query based syntax
from r in imDB.Resources
from u in r.Users
where u.UserId == userId
select r;
I hope this will help to you.
I'm using the Code First approach to build the database in this problem. I have the following (partial) entity:
public class Tournament {
public int TournamentID { get; set; }
public String Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int? SportID { get; set; }
public virtual Sport Sport { get; set; }
public virtual ICollection<Official> Officials { get; set; }
}
In the Official Entity I have this:
public class Official {
public int OfficialID { get; set; }
public String Surname { get; set; }
public String FirstName { get; set; }
public int? TournamentID { get; set; }
public virtual Tournament Tournament { get; set; }
public virtual ICollection<Match> Matches { get; set; }
}
Using some sample data and checking the SQL Server database, this works as I would expect it to. The tournament has a one-to-many relationship with officials.
The problem I'm having is that I would like the tournament to hold the primary key of an official as the head official. So I would add to the Tournament entity:
public int? OfficialID { get; set; } // foreign key to official table
public virtual Official HeadOfficial { get; set; } // navigation property
If I do this I get an attribute OfficialID and HeadOfficial_OfficialID in my Tournament table and I get TournamentID, Tournament_TournamentID and Tournament_TournamentID1 in my Officials table. I realise I now not only have a one-to-many relationship between Tournament and Official (seeing as a tournament can have many officials), but I also have a one-to-one relationship (seeing as a tournament can only have one head official).
How can I fix this problem?
You can fix the problem by giving EF a hint which navigation properties belong together. EF conventions cannot decide this anymore when you have two navigation properties in a class which refer to the same target class:
public class Tournament {
public int TournamentID { get; set; }
//...
public int? OfficialID { get; set; }
[ForeignKey("OfficialID")]
public virtual Official HeadOfficial { get; set; }
[InverseProperty("Tournament")] // the navigation property in Official class
public virtual ICollection<Official> Officials { get; set; }
}
It's also possible with Fluent API if you prefer that:
modelBuilder.Entity<Tournament>()
.HasOptional(t => t.HeadOfficial)
.WithMany()
.HasForeignKey(t => t.OfficialID);
modelBuilder.Entity<Tournament>()
.HasMany(t => t.Officials)
.WithOptional(o => o.Tournament)
.HasForeignKey(o => o.TournamentID);