How to update an existing object in a many to many relationship (.Net 5) - .net-5

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

Related

Adding a property to a Model in the ViewModel

With the Model
public class Person {
public string Forename { get; set; }
public string Surname { get; set; }
public string DOB { get; set; }
}
I have a ViewModel which I'll be passing through to the View
public class PersonViewModel {
public IQueryable<Person> PersonVM { get; set; }
public string sometext{ get; set; }
}
If, for example, I wanted to calculate the age in the controller code and store it against each Person row in the IQueryable so it could be seen in the View, what's the best way of adding an Age property to each row?
I'm guessing that I don't have to include a fake property in the Person model like so
public string Age { get; set; }
You can use the NotMapped attribute which will exclude the property from database mapping.
public class Person
{
public string Forename { get; set; }
public string Surname { get; set; }
public string DOB { get; set; }
[NotMapped]
public string Age { get; set; }
}
You can make Age property set private and write your logic in get to calculate it at run time.
public class Person {
public string Forename { get; set; }
public string Surname { get; set; }
public string DOB { get; set; }
public string Age {
get{
//.... you logic
}
private set{}
}

ASP.NET Model Relationship

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

Entity Framework not binding entity

I have the following db structure:
I am using EF6 to create the entities from database and have the following classes created by EF6:
public partial class Mechanic
{
public Mechanic()
{
this.MechanicAddresses = new HashSet<MechanicAddress>();
this.MechanicServices = new HashSet<MechanicService>();
}
public string ID { get; set; }
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public string PasswordHash { get; set; }
public string SecurityStamp { get; set; }
public string PhoneNumber { get; set; }
public bool PhoneNumberConfirmed { get; set; }
public bool TwoFactorEnabled { get; set; }
public Nullable<System.DateTime> LockoutEndDateUtc { get; set; }
public bool LockoutEnabled { get; set; }
public int AccessFailedCount { get; set; }
public string UserName { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string MobileNumber { get; set; }
public Nullable<bool> IsMobile { get; set; }
public string Url { get; set; }
public string FaceBookUrl { get; set; }
public string TwitterUrl { get; set; }
public string Description { get; set; }
public string Discriminator { get; set; }
public bool IsEnabled { get; set; }
public bool IsAuthorised { get; set; }
public string Logo { get; set; }
public System.DateTime CreationTimestamp { get; set; }
public virtual ICollection<MechanicAddress> MechanicAddresses { get; set; }
public virtual ICollection<MechanicService> MechanicServices { get; set; }
}
public partial class MechanicAddress
{
public int ID { get; set; }
public string MechanicId { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string AddressLine3 { get; set; }
public string District { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public int CountryId { get; set; }
public System.DateTime CreationTimestamp { get; set; }
public bool IsPrimary { get; set; }
public Nullable<double> Latitude { get; set; }
public Nullable<double> Longitude { get; set; }
public System.Data.Entity.Spatial.DbGeography Location { get; set; }
public virtual Country Country { get; set; }
public virtual Mechanic Mechanic { get; set; }
}
public partial class MechanicService
{
public int ID { get; set; }
public string MechanicId { get; set; }
public string Service { get; set; }
public virtual Mechanic Mechanic { get; set; }
}
The data is correct so i expect to get data in all entities.
When i run the following linq query in my DAL:
Mechanic mech = context.Mechanics.Where(a => a.ID == id).Include(a => a.MechanicAddresses).Include(a => a.MechanicServices).FirstOrDefault();
It returns the mechanic and mechanicAddresses but mechanicServices is always empty (count == 0).
When i run the same query in LinqPad I get all entities filled as expected.
I have removed the edmx and re-created it but still get the same issue.
Please check if "MultipleActiveResultSets" is set to true and LazyLoadingEnabled is enabled in connection string. It may help.
And what about your OnModelCreating?
protected override void OnModelCreating(DbModelBuilder modelBuilder)
It's not necessary to use Include if you have LazyLoading (virtual). And If it works fine in LinqPad try to do migration into empty DB (just test). And then try to get data from test DB.
The only way i was able to resolve this was to:
delete the EDMX
script the create for the mechanicsServices table
script the data
drop the mechanicsServices table
run the create table script from above
run the insert data script
regenerate the EDMX
This now works, WTF! Can't explain it.
I know it's always best to understand what went wrong but this one beat me.
I had same problem.
If you using git, please check .edmx file old version. SSDL content may be missing.

EF 4.1 Code First multiple Many-to-Many relationships

I'm having trouble wrapping my head around a certain code-first relationship. I have three entities: Group, User, GroupPermission. The GroupPermission entity holds information about permissions that relate to a group. There are three permissions: leader, editor, user. The GroupPermission table should include the primary key Id and the name of the permission. Then I want a relationship table that looks something like this: Id - Group_Id - User_Username - GroupPermission_Id. There can be multiple groups, multiple users, multiple permissions. I have plenty of examples that help me make a single relationship table, but I can't find anything that includes multiple relationships.
Here are my entities...
User:
public class User
{
[Key, StringLength(EntityLength.UsernameLength)]
public string Username { get; set; }
[Required, StringLength(EntityLength.NameLength)]
public string FirstName { get; set; }
[Required, StringLength(EntityLength.NameLength)]
public string LastName { get; set; }
[Required, StringLength(EntityLength.Email)]
public string Email { get; set; }
public bool Active { get; set; }
public DateTime DateCreated { get; set; }
public virtual UserPermission UserPermission { get; set; }
public virtual ICollection<Group> Groups { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<Issue> Issues { get; set; }
public virtual ICollection<GroupPermission> GroupPermissions { get; set; }
public string FullName
{
get { return FirstName + ' ' + LastName; }
}
}
Group:
public class Group
{
[Key]
public int Id { get; set; }
[Required, StringLength(EntityLength.GenericLength)]
public string Name { get; set; }
[Required, StringLength(EntityLength.DescriptionLength)]
public string Description { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<GroupPermission> GroupPermissions { get; set; }
}
GroupPermission:
public class GroupPermission
{
[Key]
public int Id { get; set; }
[StringLength(EntityLength.GenericLength)]
public string Name { get; set; }
public int GroupId { get; set; }
public virtual ICollection<Group> Groups { get; set; }
public int UserId { get; set; }
public virtual ICollection<User> Users { get; set; }
public enum Permission
{
Leader = 1,
Editor = 2,
User = 3
}
}
When the tables are created using this structure, I get a GroupPermissions table that has Id, Name, GroupId, and UserId. This table needs to only be Id and Name. Then it creates a GroupPermissionUsers table that holds GroupPermissions_Id and User_Username. This is the table that should be Id, Group_Id, User_Username, GroupPermission_Id.
Does anybody have any tips to accomplish this or am I thinking about the design of this incorrectly?
In such case you are missing additional entity. It should look like:
New Permission entity with Id and Name:
public class Permission
{
[Key]
public int Id { get; set; }
[StringLength(EntityLength.GenericLength)]
public string Name { get; set; }
public virtual ICollection<GroupPermission> GroupPermissions { get; set; }
}
Modified GroupPermission entity to form junction table among Users, Groups and Permissions:
public class GroupPermission
{
[Key]
public int Id { get; set; }
public int GroupId { get; set; }
public virtual Group Group { get; set; }
public string UserName { get; set; }
[ForeignKey("UserName")]
public virtual User User { get; set; }
public int PermissionId { get; set; }
public virtual Permission Permission { get; set; }
}

EF 4.1 - Model Relationships

I'm trying to create a quick ASP.NET MVC 3 application using the RC version of EF 4.1. I have two models:
public class Race
{
public int RaceId { get; set; }
public string RaceName { get; set; }
public string RaceDescription { get; set; }
public DateTime? RaceDate { get; set; }
public decimal? Budget { get; set; }
public Guid? UserId { get; set; }
public int? AddressId { get; set; }
public virtual Address Address { get; set; }
}
and
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
public string StreetCont { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public virtual Race Race { get; set; }
}
I get the following error when trying to insert a new Race:
Unable to determine the principal end
of an association between the types
'rcommander.Models.Race' and
'rcommander.Models.Address'. The
principal end of this association must
be explicitly configured using either
the relationship fluent API or data
annotations.
Shouldn't it recognize RaceId as the primary key of the Races table and AddressId as the FK to the Addresses table automatically? Am I missing something?
Thanks!
The problem here seems to be that EntityFramework can't recognize where the foreing key is, as you are holding cross references in both objects. Not being sure what you want to achieve, I may suggest something like this:
public class Race
{
public int RaceId { get; set; }
public string RaceName { get; set; }
public string RaceDescription { get; set; }
public DateTime? RaceDate { get; set; }
public decimal? Budget { get; set; }
public Guid? UserId { get; set; }
public int? AddressId { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
public string StreetCont { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
Skipping reference to Race in second entity.
The problem here is 1:1 relation between Address and Race! You probably want to map it as 1:N so you need to modify address to:
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
public string StreetCont { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public virtual ICollection<Race> Races { ... }
}
If you want to use 1:1 then you can't use AddressId in Race but AddressId in Address must be foreign key of Race because entity framework can achive 1:1 only be "sharing" primary key.
For one-to-one relationship, you need to add "[required]" attribute in the second class. See below:
public class Race
{
public int RaceId { get; set; }
public string RaceName { get; set; }
public string RaceDescription { get; set; }
public DateTime? RaceDate { get; set; }
public decimal? Budget { get; set; }
public Guid? UserId { get; set; }
public int? AddressId { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
public string StreetCont { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
[required]
public Race Race { get; set; }
}
There is a good post: Associations in EF Code First CTP5: Part 2 – Shared Primary Key Associations
http://weblogs.asp.net/manavi/archive/2010/12/19/entity-association-mapping-with-code-first-one-to-one-shared-primary-key-associations.aspx
It recognizes Id as the primary key by convention. So what you need to do:
public class Race
{
[Key]
public int RaceId { get; set; }
public string RaceName { get; set; }
public string RaceDescription { get; set; }
public DateTime? RaceDate { get; set; }
public decimal? Budget { get; set; }
public Guid? UserId { get; set; }
public int? AddressId { get; set; }
public virtual Address Address { get; set; }
}
and
public class Address
{
[Key]
public int AddressId { get; set; }
public string Street { get; set; }
public string StreetCont { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
[ForeignKey("RaceId")] // Maybe telling it what the ForeignKey is will help?
public virtual Race Race { get; set; }
}
The [Key] attribute indicates that it should be the PrimaryKey
If you don't want this, you need to rename your primary keys to simply public int Id {get; set; }
I think it would be solved also like this... I assumed that an address is not required to be associated with a race, but a race must always be associated with an address.
I had the same problem with Patients and Incidents and i solved it with InverseProperty which is actually the same with foreign key, but the other direction
public class Race
{
public int RaceId { get; set; }
public string RaceName { get; set; }
public string RaceDescription { get; set; }
public DateTime? RaceDate { get; set; }
public decimal? Budget { get; set; }
public Guid? UserId { get; set; }
public int AddressId { get; set; }
[ForeignKey("AddressId")]
public virtual Address Address { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
public string StreetCont { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public int? RaceId { get; set; }
[InverseProperty("RaceId")]
public Race Race { get; set; }
}

Resources