Using ICollection with MVC & Entity framework - c#

Hi I'm learning to use MVc using Code first method. I'm having trouble understanding the use of ICollection<> with my setup. I have two classes/entitys: 'Restaurant' & 'RestaurantReview' as below:
public class Restaurant
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
public string Country { get; set; }
public ICollection<RestaurantReview> Reviews { get; set; }
}
and.....
public class RestaurantReview
{
public int Id { get; set; }
public int Rating { get; set; }
public string Body { get; set; }
public string ReviewerName { get; set; }
public int RestaurantId { get; set; }
}
now whats confusing me is the last property of the Restaurant class. Why is it of type 'ICollection' and uses my RestaurantReview class as a parameter, what does it do, hope I
have made myself clear

It's a definition of one to many relationship.
With that property (sometimes called Navigation Property) Entity Framework will be able to connect Review with Restaurant it was written about. It will also allow you to get Review entities for given Restaurant entity really easily.
You can also remove public int RestaurantId { get; set; } from RestaurantReview class - That column is gonna be generated automatically by EF because of ICollection<RestaurantReview> in Restaurant class.

Related

'Invalid Column Name' Error After Upgrading from Asp.net core 2.0 to 2.1

I've revisited my web site recently and had to upgrade from ASP.net MVC (DBF) core 2.0 to 2.1.
Since doing so I'm getting the following error...
SqlException: Invalid column name 'MovieTitleId'. Invalid column name 'MovieTitleId'.
Yet there is no such field 'MovieTitleId' in any part of my code or db.
The error occurs only when the site is accessing the 'many table' Scenes
(there is a one-to-many relationship set up in the db with FKs.. Movie > Scenes)
This is the Scene class..
public partial class Scene
{
[Key]
public int SceneId { get; set; }
[ForeignKey("TitleId")]
public int? TitleId { get; set; } // foreign key from Movie
[ForeignKey("LocationSiteId")]
public int? LocationSiteId { get; set; }
[ForeignKey("LocationAliasId")]
public int? LocationAliasId { get; set; }
public string Notes { get; set; }
public int? SceneOrder { get; set; }
public string TitleList { get; set; }
public LocationAlias LocationAlias { get; set; }
public LocationSite LocationSite { get; set; }
public Movie Movie { get; set; }
}
And this is the Movie class which on the 'one side' and call Scenes on a typical 'Master/Detail' type web page...
public partial class Movie
{
public Movie()
{
Scenes = new HashSet<Scene>();
}
[Key]
public int TitleId { get; set; }
[Required]
public string Title { get; set; }
[DisplayName("Title")]
public string ParsedTitle { get; set; }
[DisplayName("Year")]
public int? TitleYear { get; set; }
public string ImdbUrl { get; set; }
public string Summary { get; set; }
public bool? ExcludeTitle { get; set; }
public bool? Widescreen { get; set; }
[DisplayName("Title")]
public override string ToString()
{
return Title + " (" + TitleYear + ")";
}
public ICollection<Scene> Scenes { get; set; }
}
The error occurs in the MoviesController.cs...
Movie movie = _context.Movies.Find(id);
ViewBag.Scenes = _context.Scenes
.Where(s => s.TitleId == id)
.Include(s => s.LocationSite)
.Include(s => s.LocationSite.LocationPlace)
.OrderBy(s => s.SceneOrder).ToList();
Everything used to work fine until i upgraded to core 2.1.
I can't even recall there ever being a field called 'MovietitleId' which is actually 'TitleId'.
Is the error msg concatenating the model 'Movie' and column 'TitleId' somehow?
Try adding virtual keyword for your foreign key. Also the ForeignKey Data Annotation should be on that property where you have declared your virtual property just like below. So it should be something like this:
Scene.cs
public int TitleId { get; set; }
[ForeignKey("TitleId")
public virtual Movie Movie { get; set; }
Why virtual?
If you declare your property virtual your virtual property (by default) won't be loaded right away when querying the main object. It will be retrieved from the database ONLY if you try to access it. This is called lazy loading.
If you want to know why to use virtual in detail, you may visit this link: Why are foreign keys in EF Code First marked as virtual?
Hope this helps.

EF Core 2.2.6: Unable to map 2 foreign keys to the same table

I am having issues trying to map two fields that are foreign keys into the same table. The use case is for a modifier and creator. My class already has the Ids, and then I wanted to add the full User object as virtual.
I am using a base class so that each of my tables have the same audit fields:
public class Entity
{
public long? ModifiedById { get; set; }
public long CreatedById { get; set; } = 1;
[ForeignKey("CreatedById")]
public virtual User CreatedByUser { get; set; }
[ForeignKey("ModifiedById")]
public virtual User ModifiedByUser { get; set; }
}
The child class is very simple:
public class CircleUserSubscription : Entity
{
[Required]
public long Id { get; set; }
public long SponsorUserId { get; set; }
[ForeignKey("SponsorUserId")]
public virtual User User { get; set; }
public long TestId { get; set; }
[ForeignKey("TestId")]
public virtual User Test { get; set; }
}
This is a standard junction table.
When I try to generate the migration, I am getting errors that I don't understand fully.
Unable to determine the relationship represented by navigation property 'CircleUserSubscription.User' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I tried what this answer had, but the code is basically the same: https://entityframeworkcore.com/knowledge-base/54418186/ef-core-2-2---two-foreign-keys-to-same-table
An inverse property doesn't make sense since every table will have a reference to the user table.
For reference, here is the User entity:
public class User : Entity
{
public long Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
I am hoping you all can help me out, TIA :)
EDIT: One thing to note, all of this worked fine when the entity class was as follows:
public class Entity
{
public long? ModifiedById { get; set; }
public long CreatedById { get; set; } = 1;
}
It was only after I added the entity that things went awry.

Entity Framework: how to solve table in table

I'm testing the following scenario (code below): create a shopping mall, with shops, with cashiers, with cash desks and persons operating it.
So I've created the following classes
Table Mall
Table Shop
Table CashDesk
Table Person
Basic classes with an ID and a name.
Then I need a derived class from Person being PersonCashier or PersonCustomer.
Since everything is related, I need to create intersection tables for the many to many scenarios.
I've created the following intersection tables:
The Mall contains shops: MallShop
The shop contains CashDesks: MallShopCashDesk
And the CashDesk containing cashiers: MallShopCashDeskPersonCashier
This just doesnt feel right. Can anyone help me out on a best practice
public class Mall
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Required]
public int NumberOfShopSpaces { get; set; }
}
public class Shop
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
}
public class CashDesk
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class Person
{
public int Id { get; set; }
[Required]
public string FullName { get; set; }
[Required]
public string Gender { get; set; }
}
public class PersonCashier : Person
{
[Required]
public int ShopId { get; set; }
public virtual Shop Shop { get; set; }
}
These are the base classes. How should i add a shop to a mall, a cashdesk to a shop, and a cashier to a cashdesk? (i've tried alot but posting the code would make the question look like spaghetti)
If this is code first then EF will make the relationship tables for you. You just need to add the relationships in your classes.
Exactly what they should be is not clear from your description because it depends on what relationships you want; if you want a many-many between PersonCashier and CashDesk it would be like this:
public class CashDesk
{
public List<PersonCashier> Cashiers { get; set; }
}
public class PersonCashier : Person
{
public List<CashDesk> CashDesks { get; set; }
}
(not showing all the fields for brevity)
public class Mall
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Required]
public int NumberOfShopSpaces { get; set; }
List<Shop> CurrentShops { get; set; }
}
public class Shop
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
public List<CashDesk> CashDesks { get;set; }
}
I added the list of shops to the Mall class and CashDesks to Shops. This gives you a list of shops in the mall, and a list of cashdesks in each shop and you can follow this method for everything else you need.
If you have a database, you will have a Mall table and a Shop table.
The Shop table can have a Foreign Key to the Mall table, that's how you link them and this will work with the class structure at the top.
Or, you can have another table called MallShops where you have 2 fields, one being the MallID, the other the ShopID. This is called a Link table.
Both approaches will work with the second allowing a more complex structure with lots of Malls linked to Lots of Shops.
I would start with the database structure first, make sure you cover all you need, then you can do the classes etc in a way that makes sense. If you use something like EntityFramework then it will create all the classes for you once your database structure is ready.

Entity Framework - Invalid column name 'CourseLesson_Id'

After trying to execute the following query:
List<CourseLesson> courseLessons = (from cl in context.CourseLessons
.Include(x => x.CourseLessonTestQuestions)
select cl).ToList();
I get the the error Invalid column name 'CourseLesson_Id'.
My models and DataContext looks like this(this is from a test project I've created to repreduce the problem)
public class CourseLesson
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<CourseLessonTestQuestion> CourseLessonTestQuestions { get; set; }
}
public class CourseLessonTestQuestion
{
public int Id { get; set; }
public int CourseLessonId { get; set; }
[ForeignKey(nameof(CourseLessonId))]
public CourseLesson CourseLesson { get; set; }
public int? ReturnCourseLessonId { get; set; }
[ForeignKey(nameof(ReturnCourseLessonId))]
public CourseLesson ReturnCourseLesson { get; set; }
}
I have 2 foreign keys that point to the same table and I'm assuming EF is trying to create or map something that doesn't really exist.
After reading for a while I've found a way to fix my problem in (this answer) with the following code:
modelBuilder.Entity<CourseLessonTestQuestion>()
.HasOptional(cltq => cltq.ReturnCourseLesson)
.WithMany(x => x.CourseLessonTestQuestions);
What really bugs me about this situation is why everything works when I use the Fluent API, but it doesn't work with the ForeignKey attribute? This looks like something that could lead to future problems and I want to know what is really happening.
And the real question is there a solution for fixing this problem without the Fluent API? Like using attributes or some other convention?
I'm using Entity Framework 6.1.3
Solution without Fluent API, but with the help of InversePropertyAttribute, whose constructor's argument is the name of corresponding CourseLessonTestQuestion's property:
public class CourseLesson
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[InverseProperty("CourseLesson")]
public ICollection<CourseLessonTestQuestion> CourseLessonTestQuestions { get; set; }
[InverseProperty("ReturnCourseLesson")]
public ICollection<CourseLessonTestQuestion> ReturnCourseLessons { get; set; }
}

Entity Framework DbContext Delete child object

I am trying to delete the child objects in foreach loop but that doesn't seem to be working.. Here is what I have.
I have a meeting and meeting_category entities which has 1-to-many relationship..
foreach (meeting_category meetingCategory in currentMeeting.meeting_category)
{
dbContext.meeting_category.Remove(meetingCategory);
dbContext.Entry(meetingCategory).State = EntityState.Deleted;
dbContext.SaveChanges();
}
I am then returning to the same view with updated results.. This seems to be very inconsistent. Sometimes it deletes entities and sometimes it doesn't. Even when it deletes it, my meeting object still have meeting_category objects I deleted.
When it doesn't delete from DB, it updates the meeting object and removed meeting_category objects from the meeting..
Is there any kind of reset or refresh that I will have to do in order for meeting object to remove the meeting_category objects..
How can I make sure that it consistently deletes the meeting_category objects both from DB and from the meeting object and when I return to the view, I have an updated meeting object?
Here is my meeting entity
public partial class meeting
{
public meeting()
{
this.meeting_questions = new HashSet<meeting_questions>();
this.meeting_abstract = new HashSet<meeting_abstract>();
this.meeting_category = new HashSet<meeting_category>();
this.meeting_image = new HashSet<meeting_image>();
}
public int meeting_id { get; set; }
public int language_id { get; set; }
public string meeting_code { get; set; }
public string meeting_name { get; set; }
public string meeting_description { get; set; }
public System.DateTime meeting_start_date { get; set; }
public System.DateTime meeting_end_date { get; set; }
public System.DateTime abstract_cutoff_date { get; set; }
public string meeting_guidelines { get; set; }
public string created_by { get; set; }
public Nullable<System.DateTime> created_datetime { get; set; }
public Nullable<bool> meeting_published { get; set; }
public Nullable<bool> meeting_deleted { get; set; }
public Nullable<bool> meeting_fully_created { get; set; }
public virtual language language { get; set; }
public virtual ICollection<meeting_questions> meeting_questions { get; set; }
public virtual ICollection<meeting_abstract> meeting_abstract { get; set; }
public virtual ICollection<meeting_category> meeting_category { get; set; }
public virtual ICollection<meeting_image> meeting_image { get; set; }
}
And here is my meeting_category entity
public partial class meeting_category
{
public meeting_category()
{
this.abstract_category = new HashSet<abstract_category>();
}
public int meeting_category_id { get; set; }
public int meeting_id { get; set; }
public int category_type_id { get; set; }
public int category_id { get; set; }
public string category_name { get; set; }
public string category_name_en { get; set; }
public virtual ICollection<abstract_category> abstract_category { get; set; }
public virtual category category { get; set; }
public virtual category_type category_type { get; set; }
public virtual meeting meeting { get; set; }
}
I have a couple of questions about what it is that you are trying to achieve.
Firstly are you sure that the Meeting -> Category relationship is many-to-one? I would normally expect it to be many-to-many, i.e. each Meeting can have many Categories and each Category can be attached to many Meetings.
Secondly, are you actually trying to delete the Category or are you trying to remove a Category from a Meeting?
If you are simply trying to remove the relationship between a Meeting and a Category then you need to edit the navigation property, i.e. MyMeeting.Categories.Remove(MyCategory).
If you are trying to delete the actual Category then you will need to make sure that that Category is not currently linked to any Meeting otherwise you will get a Foreign Key violation if you try to remove the Category.
On top of all of this you may be encountering chaching or simply object lifetime management issues.
What type of application are you creating? ASP.NET? WinForms? etc.
How are you actually instantiating a concrete version of your DBContext? Are you using DI?
Cheers Mike
I managed to fix the issue by creating the DbContext per controller action and destroying it before loading the view.
The problem was that I was creating the DbContext in another class and was reusing the same context on all controller action. Since Entities are cached in dbcontext, I was always getting the old cached copy of entities and was working very inconsistently.

Categories