Entity Framework Multiple Cascading Delete - c#

I have three models, User, Room, and PlayerRoom defined as such:
public class User
{
public int id { get; set; }
public string UserName { get; set; }
//flags user to be deleted when room is no longer available
public bool temporaryUser { get; set; }
[JsonIgnore]
public bool permanent { get; set; }
}
public class Room
{
public int id { get; set; }
public string RoomName { get; set; }
[JsonIgnore]
public int CreatedById { get; set; }
public virtual User CreatedBy { get; set; }
}
public class PlayerRoom
{
public int id { get; set; }
[JsonIgnore]
public int RoomId { get; set; }
public virtual Room Room { get; set; }
[JsonIgnore]
public int UserId { get; set; }
public virtual User User { get; set; }
}
What i'm trying to accomplish is setting up the models so that when a User is deleted or when a Room is deleted, all associated PlayerRoom get deleted.
Currently when I generate the migration and run update-database I get the error:
Introducing FOREIGN KEY constraint 'FK_dbo.Rooms_dbo.Users_CreatedById' on table 'Rooms' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
From what research I've done it is because PlayerRoom can be deleted in multiple ways from a cascade however, this is the intended behavior.
How can I get the migration tool to generate a migration that will not throw this error?
Thanks!

I ended up altering my classes to make them a bit more restrictive, which in this case actually works out better. I removed the PlayerRoom object and moved the Room reference onto the user object.
public class User
{
public int id { get; set; }
public string UserName { get; set; }
//flags user to be deleted when room is no longer available
public bool temporaryUser { get; set; }
public bool? isHost { get; set; }
[JsonIgnore]
public int? RoomId { get; set; }
public virtual Room Room { get; set; }
[JsonIgnore]
public bool permanent { get; set; }
}
public class Room
{
public int id { get; set; }
public string RoomName { get; set; }
}
By moving Room to the User instead of on a separate object it restricts users to only be in one Room and gets rid of my cascading delete issue

gets rid of my cascading delete issue
EF using Code First sets up cascading by default to be on. To turn it off from the model one can either strategically place 'WillCascadeOnDelete` off the entity in question:
.WillCascadeOnDelete(false);
or globally
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

Related

Entity Framework Model Properties Not Created On Update-Database Command

I recently needed to change the type of three properties in a model class from string to an ICollection custom type as shown below.
There are also three classes (SpecialType, TypeToAdd, TypeToRemove) that were added that have a primary key ID, name (string), qty (int) and a FK to corresponding Subscriptions_Regular_Id.
I ran the migration, then update-database to script, but when I ran the script in SSMS console it left off the three ICollection properties below. It did create the three dependent tables with their foreign keys back to the parent, but I can't understand why it's not creating these three ICollection properties. Something simple I'm overlooking I'm sure and wanted to get some input if anyone might have a suggestion.
public class Subscriptions
{
[Key]
public int Subscriptions_Regular_Id { get; set; }
public string CustomerNumber { get; set; }
public string Type { get; set; }
public int TypeQty { get; set; }
public ICollection<SpecialType> SpecialType { get; set; }
public ICollection<TypeToAdd> TypeToAdd { get; set; }
public ICollection<TypeToRemove> TypeToRemove { get; set; }
}
For context:
Subscriptions can have many SpecialTypes, TypeToAdd, and TypeToRemove
SpecialTypes, TypeToAdd, TypeToRemove can be tied to only one Subscription.
Thanks in advance for any input.
====== EDIT ======
Adding 3 ICollection classes:
public class TypeToAdd
{
[Key]
public int TypeToAddId { get; set; }
public string TypeToAdd { get; set; }
public int Qty { get; set; }
public int Subscriptions_Regular_Id { get; set; }
public Subscriptions Subscriptions { get; set; }
}
The other two classes are the same as above other than the first two property names (they are TypeToRemove and SpecialType).
but I can't understand why it's not creating these three ICollection properties
Collection Navigation Properties are implemented using seperate tables with foreign keys. Relational databases don't have multi-valued attributes, so that's just how related collections are implemented in an RDBMS.
Try to change the classes to this
public class Subscription
{
[Key]
public int Id { get; set; }
public string CustomerNumber { get; set; }
public string Type { get; set; }
public int TypeQty { get; set; }
[InverseProperty(nameof(TypeToAdd.Subscription))]
public ICollection<TypeToAdd> TypeToAdds { get; set; }
[InverseProperty(nameof(SpecialType.Subscription))]
public virtual ICollection<SpecialType> SpecialTypes { get; set; }
[InverseProperty(nameof(TypeToRemove.Subscription))]
public ICollection<TypeToRemove> TypeToRemoves { get; set; }
}
public class TypeToAdd
{
[Key]
public int Id { get; set; }
public string TypeToAdd { get; set; }
public int Qty { get; set; }
public int SubscriptionId { get; set; }
[ForeignKey(nameof(SubscriptionId))]
[InverseProperty("TypeToAdds")]
public virtual Subscription Subscription { get; set; }
}
SpecialType and TypeToRemove classes should be configured the same way as TypeToAdd.

Having a hard time understanding how cascade delete works and how to configure relationships

So, I have the dependent entity ArtItem, with parent class Location, which is a dependent entity of Company, which is a dependent entity of Country. ArtItem is also dependent on Artist and Artist is dependent on Country.
public class Country
{
[Required]
public int Id { get; set; }
}
public class Company
{
public Country Country { get; set; }
[Required]
public int CountryId { get; set; }
[Required]
public int Id { get; set; }
}
public class Location
{
[Required]
public int Id { get; set; }
public Company Company { get; set; }
public int CompanyId { get; set; }
//public List<ArtItem> ArtItems { get; set; }
}
public class ArtItem
{
public Artist Artist { get; set; }
public int ArtistId { get; set; }
[Required]
public int Id { get; set; }
public Location Location { get; set; }
[Required]
public int LocationId { get; set; }
}
public class Artist
{
public Country Country { get; set; }
[Required]
public int CountryId { get; set; }
[Required]
public int Id { get; set; }
}
My issue is that whenever I try to do update-database in powershell, I get the error Introducing FOREIGN KEY constraint 'FK_ArtItems_Locations_LocationId' on table 'ArtItems' may cause cycles or multiple cascade paths..
I tried configuring the relationship in my DbContext class,
.
but to no avail. I simply don't understand how I'm supposed to configure relationships in EF Core, or how modelBuilder works. I would highly appreciate an explanation on how to avoid this error and what actually causes it. I thought that having a navigation property and a foreign key defined would be enough for EF Core to properly generate the relationships. I tried the online documentation for EF Core on relationships to no avail.
Help is much appreciated.
Thanks in advance.

Foreign keys with Code First in a Web API

Very simple question but it looks like I'm trying to implement a simple one-to-many relationship between two models.
So far, what I have is this :
A product class :
public class Products
{
public long Id { get; set; }
public long Code { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public Boolean Reviewed { get; set; }
[ForeignKey("BundleId")]
public int BundleId { get; set; }
public virtual Bundles Bundle { get; set; }
}
And the Defects class looks like this:
public class Defects
{
public long Id { get; set; }
public String Description { get; set; }
public String Picture { get; set; }
public DateTime DateCreated { get; set; }
[ForeignKey("ProductId")]
public int ProductId { get; set; }
public Products Product { get; set; }
[ForeignKey("UserId")]
public int UserId { get; set; }
public virtual Users User { get; set; }
}
I thought that I did not need to add an ICollection of Defects to the Products class because it's a "simple" one-to-many relationship and this code would be enought to be able to get the ID of a Product in the Defects class (I don't need more).
But, of course I get an exception :
The property 'ProductId' cannot be configured as a navigation property. The property must be a valid entity type and the property should have a non-abstract getter and setter
How may I solve that issue ?
I might be doing someting wrong with my two foreign keys but since I declared the name of the foreign keys, I assumed it would have been enought.
Thanks for your attention.
This is what your relationship can be distilled to.
Please note that ForeignKey annotation is applied to navigation property with the name of the key property.
If you build one-to-many relationship - then ICollection is absolutely necessary. Otherwise where's the "many"
public class Products
{
public int Id { get; set; }
public virtual List<Defects> Bundle { get; set; }
}
public class Defects
{
public long Id { get; set; }
public int ProductId { get; set; }
[ForeignKey("ProductId")]
public Products Product { get; set; }
}
FK can also be applied to the key property. But in that case you have to put the name of the instance of related class there
public class Defects
{
public long Id { get; set; }
[ForeignKey("Product")]
public int ProductId { get; set; }
public Products Product { get; set; }
}

"Clue" like relational database design: Schema must enforce entity multiplicity across related entities.

I have a business case that requires as much as possible to implement a database schema for the application which prevents an incorrect state for an entity called Trailers to be in more than one location entity, called Bays or Lots.
The code snippets for relevant parts of the entity models are below.
Essentially, my problem is I cannot figure out how to create a schema that will prevent a trailer from being in a lot, and in a Bay at the same time, while also keeping the 1 to many relationship from lots to trailers and 1 to 1 for bays trailers.
I can obviously do things to mitigate this problem in the application, but having a schema that prevents this state is better.
Anyone have any thoughts on how this might be best accomplished?
public class Trailer
{
public int TrailerId { get; set; }
public MaintStatuses MaintStatus { get; set; }
....
}
public class Location
{
[Key]
public string LocationName { get; set; }
//Navigation
public virtual ICollection<Bay> LocationBays { get; set; }
public virtual Lot ParkingLot { get; set; }
...
}
public class Bay
{
[Key]
public int Id { get; set; }
public string BayName { get; set; }
//Navigation
public virtual Trailer Trailer { get; set; }
public virtual Location Location { get; set; }
...
}
public class Lot
{
[Key]
public string LotName { get; set; }
[ForeignKey("Location")]
public string LocationName { get; set; }
public virtual Location Location { get; set; }
public virtual ICollection<Trailer> ParkedTrailers{get; set;}
...
}

Entity Framework Code First associations/FK issues and assumptions/defaults

I am very confused by the way Entity Framework is able to pick up on relationships between entities. I have a few questions on this.
In a simple test application, I have a table of people, and a table of notes, and a table of picture assets.
There are many pictures, each is owned by a person (a person can own more than one).
There are many notes, each is owned by a person (a person can own more than one).
and finally a person has a logo which is a picture.
.
public class Person
{
public int ID { get; set; }
public string name { get; set; }
public Picture logo { get; set; }
}
public class Note
{
public int ID { get; set; }
public string Text { get; set; }
public Person Owner { get; set; }
}
public class Picture
{
public int ID { get; set; }
public string Path { get; set; }
public Person Owner { get; set; }
}
When I try to run, I get the error "Unable to determine the principal end of an association between the types ....".
(If I drop the Person.logo field, compile/run, then manually add it in to SQL, along with the FK relationship, it works 100% as expected... I just can't seem to work out how to set this from EF itself).
Can you help with the error? I have read quite a few answers here, but, I just can't seem to adapt it to fix my error.
However, now I have a one to many and a many to one (I don't think this is classed as many to many?), I just don't understand how to create people objects, where a FK is non nullable and the FK doesn't exist yet.
A solution I found, which I really don't like is to make the person.picture column nullable, then create a person, followed by creating a picture, then assign a picture to the person object... but, ideally I don't want it nullable. There should always be a picture.
Old answer assuming your 1:1 relation:
Scroll down for an answer on your clarification.
Here are two ways to achieve this, one way is to remove the cross reference navigation by applying 1:N.
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Picture Logo { get; set; }
}
public class Note
{
public int ID { get; set; }
public string Text { get; set; }
}
public class Picture
{
public int ID { get; set; }
public string Path { get; set; }
}
This is a really cheap solution, you probably don't want more notes or pictures than persons...
So, use Data Annotations to instruct what the foreign key is, this will keep navigation and 1:1.
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Picture Logo { get; set; }
}
public class Note
{
[Key, ForeignKey("Person")]
public int OwnerID { get; set; }
public string Text { get; set; }
public virtual Person Owner { get; set; }
}
public class Picture
{
[Key, ForeignKey("Person")]
public virtual int OwnerId { get; set; }
public string Path { get; set; }
public virtual Person Owner { get; set; }
}
As you can see, because of the 1:1 relationship the picture and note will use the same ID. If your key is not named ID, you need to add a KeyAttribute, in this case we also add the ForeignKeyAttribute.
Please note that you should use virtual so that things load only when you request them, you most likely don't want the database to query the Picture information if you only want the name of the Person.
Association properties that are marked as virtual will by default be lazy-loaded. What this means is that if you retrieve a Product entity, its Category information will not be retrieved from the database until you access its Category property (or unless you explicitly indicate that the Category data should be retrieved when you write your LINQ query to retrieve the Product object).
— Scott Gu - Using EF Code First with an existing database
New answer regarding your clarification:
Bulding further on the Foreign Keys by going back to a 1:N structure with ICollection<T>; once you obtain your person like var boss = Person.Find(BossID) you can then access boss.Pictures which will have the various pictures. You can also assign boss.Logo to be one of those pictures.
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Picture Logo { get; set; }
public ICollection<Picture> Pictures { get; set; }
public ICollection<Note> Notes { get; set; }
}
public class Note
{
public int ID { get; set; }
[ForeignKey("Person")]
public int OwnerID { get; set; }
public string Text { get; set; }
public virtual Person Owner { get; set; }
}
public class Picture
{
public int ID { get; set; }
[ForeignKey("Person")]
public virtual int OwnerId { get; set; }
public string Path { get; set; }
public virtual Person Owner { get; set; }
}
You might be interested in hinting your data members using DataAnnotations as well as taking a look to the current Conventions for Code First.
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Picture Logo { get; set; }
public ICollection<Picture> Pictures { get; set; }
public ICollection<Note> Notes { get; set; }
}
public class Note
{
public int ID { get; set; }
public int OwnerID { get; set; }
public string Text { get; set; }
[ForeignKey("OwnerId")]
public virtual Person Owner { get; set; }
}
public class Picture
{
public int ID { get; set; }
public virtual int OwnerId { get; set; }
public string Path { get; set; }
[ForeignKey("OwnerId")]
public virtual Person Owner { get; set; }
}
The above code is right, after trying the wrong code several times, I got the right code.
Hope it is usefull for you.

Categories