Navigation Properties Null until DbSet is called - entity-framework-core

Setup
I'm getting into Entity Framework Core (2.1) and I'm having trouble working with the relationships set up. To keep it simple I've set up a simple one-to-one relationship between a person and address table:
public class Person
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public PersonAddress Address { get; set; }
}
public class PersonAddress
{
public int ID { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public int ZipCode { get; set; }
public int PersonID { get; set; }
public virtual Person Person { get; set; }
}
With a simple explicit link between the two (to make sure this isn't an issue in how things are connected):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOne(p => p.Address)
.WithOne(a => a.Person);
modelBuilder.Entity<PersonAddress>()
.HasOne(a => a.Person)
.WithOne(p => p.Address);
}
Here's my issue
This project is set up in an asp.net core app and I've got a breakpoint set up at the end of my "PeopleController" constructor. I've also added a line to take the first person out of the DbSet and assign it to a variable:
public PeopleController(SmallGroupsSiteContext context)
{
_context = context;
Person miles = context.Person.First();
Debug.Print(miles.Address.ToString());
}
When I look into my locals and look at the variable "miles" its address field's value is Null. Furthermore if I were to make a call to the DbSet Addresses in my immediate window, the value for Address in the "miles" object gets set to the correct value.
What's going on? When does Entity actually set Navigation Properties? Should I be making calls to other tables as I'd like the data populated? Is there something lazy going on in the background that I need to change?

Oops it looks like I was just a few Google searches away...
It looks like I should have been using the .Include Extension as I was loading the DbSet like so:
public PeopleController(SmallGroupsSiteContext context)
{
_context = context;
Person miles = context.Person.Include(person => person.Address).First();
Debug.Print(miles.Address.ToString());
}

Related

Entity Framework one to many : SqlException

I have a little problem when I try to save an item in my DB using EntityFramework.
My classes looks like:
public partial class Site
{
public int Id { get; set; }
public string Name { get; set; }
public string LongName { get; set; }
public string Adress { get; set; }
public City City { get; set; }
public Country Country { get; set; }
public string VATNumber { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public string IsoCode { get; set; }
}
And when I try to create a new site in my controller it works, but when I try to add a link to an existing Country :
if (SiteProvider.GetSiteByName(Site.Name) == null)
{
Site.Country = CountryProvider.GetCountryById(1);//it's working, i see the link to the right country
SiteProvider.Create(Site);
}
public static void Create(Site Site)
{
using (MyDBContext Context = new MyDBContext())
{
Context.Site.Add(Site);
Context.SaveChanges(); //here is the problem
}
}
I got this error:
SqlException: Cannot insert explicit value for identity column in
table 'Country' when IDENTITY_INSERT is set to OFF
Thanks in advance for your help.
Add CountryId property to Site class and when adding a new Site set CountryId instead of Country property
public int CountryId { get; set; }
[ForeignKey("CountryId")]
public Country Country{ get; set; }
You have a slight issue with your use of contexts here. You have used one DBContext instance to load the country (where this country object will be tracked) and then a second DBContext to save the site (where the first country object is a property).
It is preferable to perform all your operations for a single unit of work by using one DB context (that would be shared between your classes) and the responsibility for disposing of it to be handled outside your repository layer.

Defining Self Referencing Foreign-Key-Relationship Using Entity Framework 7 Code First

I have an ArticleComment entity as you can see below:
public class ArticleComment
{
public int ArticleCommentId { get; set; }
public int? ArticleCommentParentId { get; set; }
//[ForeignKey("ArticleCommentParentId")]
public virtual ArticleComment Comment { get; set; }
public DateTime ArticleDateCreated { get; set; }
public string ArticleCommentName { get; set; }
public string ArticleCommentEmail { get; set; }
public string ArticleCommentWebSite { get; set; }
public string AricleCommentBody { get; set; }
//[ForeignKey("UserIDfk")]
public virtual ApplicationUser ApplicationUser { get; set; }
public Guid? UserIDfk { get; set; }
public int ArticleIDfk { get; set; }
//[ForeignKey("ArticleIDfk")]
public virtual Article Article { get; set; }
}
I want to define a foreign key relationship in such a way that one comment can have many reply or child, I've tried to create the relationship using fluent API like this:
builder.Entity<ArticleComment>()
.HasOne(p => p.Comment)
.WithMany()
.HasForeignKey(p => p.ArticleCommentParentId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
I followed the solution that was proposed here and here, but I get an error with the message:
Introducing FOREIGN KEY constraint 'FK_ArticleComment_ArticleComment_ArticleCommentParentId' on table 'ArticleComment' 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 or index. See previous errors.
First I though by setting the OnDelete(DeleteBehavior.Restrict) this would go away, but the problem persist, also I've tried to use the data annotation [ForeignKey("ArticleCommentParentId")] as you can see the commented code in the ArticleComment definition, but it didn't work, I'd appreciate any though on this.
You are not modeling correctly your entity. Each comment needs a Set of replies, which are of type ArticleComment too, and each of those replies are the ones that point back to its parent (Note the added ICollection Replies property):
public class ArticleComment
{
public ArticleComment()
{
Replies = new HashSet<ArticleComment>();
}
public int ArticleCommentId { get; set; }
public int? ParentArticleCommentId { get; set; }
public virtual ArticleComment ParentArticleComment{ get; set; }
public virtual ICollection<ArticleComment> Replies { get; set; }
//The rest of the properties omitted for clarity...
}
...and the fluent Mapping:
modelBuilder.Entity<ArticleComment>(entity =>
{
entity
.HasMany(e => e.Replies )
.WithOne(e => e.ParentArticleComment) //Each comment from Replies points back to its parent
.HasForeignKey(e => e.ParentArticleCommentId );
});
With the above setup you get an open-ended tree structure.
EDIT:
Using attributes you just need to decorate ParentArticleComment property.
Take into account that in this case EF will resolve all the relations by convention.
[ForeignKey("ParentArticleCommentId")]
public virtual ArticleComment ParentArticleComment{ get; set; }
For collection properties EF is intelligent enough to understand the relation.
I simplified the class (removing foreign key support fields) and it works.
It could be an issue of your EF version (I've just installed it but actually I think I'm using rc1 but I'm not sure because I had several dependency issues) or it could be your model.
Anyway, this source works fine
public class ArticleComment
{
public int ArticleCommentId { get; set; }
public virtual ArticleComment Comment { get; set; }
public DateTime ArticleDateCreated { get; set; }
public string ArticleCommentName { get; set; }
public string ArticleCommentEmail { get; set; }
public string ArticleCommentWebSite { get; set; }
public string AricleCommentBody { get; set; }
}
class Context : DbContext
{
public Context(DbContextOptions dbContextOptions) : base(dbContextOptions)
{}
public DbSet<ArticleComment> Comments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ArticleComment>()
.HasOne(p => p.Comment)
.WithMany();
}
}
static class SampleData
{
public static void Initialize(Context context)
{
if (!context.Comments.Any())
{
var comment1 = new ArticleComment()
{
AricleCommentBody = "Article 1"
};
var comment2 = new ArticleComment()
{
AricleCommentBody = "Article 2 that referes to 1",
Comment = comment1
};
context.Comments.Add(comment2);
context.Comments.Add(comment1);
context.SaveChanges();
}
}
}

Entity Framework Navigation Property Error

I am getting this error in my .Net MVC 4 web application:
The property 'Username' 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. For collection properties the type must implement
ICollection<T> where T is a valid entity type.
I am very new to Entity Framework and I can't seem to get around this issue. Here is some code:
//DB Context
public class EFDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasMany(u => u.Roles).WithMany(r => r.Users).Map(x => x.MapLeftKey("Username").MapRightKey("RoleName").ToTable("Users_Roles"));
}
}
//Entity Classes
public class User
{
[Key]
public string Username { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Comment { get; set; }
public int Level { get; set; }
public string PasswordQuestion { get; set; }
public string PasswordAnswer { get; set; }
public bool IsApproved { get; set; }
public DateTime LastActivityDate { get; set; }
public DateTime LastLoginDate { get; set; }
public DateTime LastPasswordChangedDate { get; set; }
public DateTime CreationDate { get; set; }
public bool IsOnLine { get; set; }
public bool IsLockedOut { get; set; }
public DateTime LastLockedOutDate { get; set; }
public int FailedPasswordAttemptCount { get; set; }
public DateTime FailedPasswordAttemptWindowStart { get; set; }
public int FailedPasswordAnswerAttemptCount { get; set; }
public DateTime FailedPasswordAnswerAttemptWindowStart { get; set; }
[InverseProperty("RoleName")]
public virtual ICollection<Role> Roles { get; set; }
public override string ToString()
{
return this.Username;
}
}
public class Role
{
[Key]
public string RoleName { get; set; }
public int Level { get; set; }
[InverseProperty("Username")]
public virtual ICollection<User> Users { get; set; }
public override string ToString()
{
return this.RoleName;
}
}
//Repository
public class EFUsersRepository : IUsersRepository
{
private EFDbContext context = new EFDbContext();
public IQueryable<User> Users
{
get { return context.Users; }
}
public User GetUser(string username)
{
return context.Users.Find(username); //THIS IS WHERE THE CRASH OCCURS
}
}
//DB Setup
Table Users, Role and Users_Role. Users_Role is a simple linking table with [username, role] columns both of type varchar.
The database tables columns & types match the two classes above (User,Role).
I inherited this project which was unfinished but I can't get it to run successfully. Any help understanding what the issue is would be helpful. Thanks!
It might be that Entity Framework is updated. Easiest way will be to recreate the DataModel.
Even if the previous programmer did not use Entity Data Mode, you can at least copy the auto generated code such as EFDbContext, Users and Roles classes.
It turns out, after commenting out enough items all day long, the the following lines are what caused this error for me:
[InverseProperty("RoleName")] //In file User.cs (as shown above)
[InverseProperty("UserName")] //in file Role.cs (as shown above)
I am still learning Entity Framework and I don't know why this was the solution, but it stopped the error which I reported above.
I hope that this helps someone else and if anyone wants to help me understand what the issue was in detail, please feel free. I am eager to learn.

MVC EF code first creating model class

I'm new to MVC and EF code first. I'm in struggle to model a real-estate company DB model using EF code-first approach and I did some exercises as well as reading some online tutorials.
First thing I have a customers table that would be in relation with one or more properties he/she has registered as it's owner to sell or to rent, I was wondering if it is possible to have some sub classes inside a model class for registered properties as below:
public Property
{
public int PropertyID { get; set; }
public bool IsforSale { get; set; }
public bool IsforRent { get; set; }
public class Apartment{
public int ApartmentID { get; set; }
public int AptSqureMeter { get; set; }
. . .
. . .
}
public class Villa{
public int VillaID { get; set; }
public int VillaSqureMeter { get; set; }
. . .
. . .
}
and also other sub-classes for other types of properties
}
If the answer is Yes, then how should I declare the relations using data annotation or Fluent API, and then please help me how to update both Customers table and Property table with the customer information and property info at the same time?
thanks for your answer in advance.
As #Esteban already provided you with a pretty detailed answer on how to design your POCOs and manage the relationship between them, I will only focus on that part of your question:
how should I declare the relations using data annotation or Fluent API
First of all, you should know that certain model configurations can only be done using the fluent API, here's a non exhaustive list:
The precision of a DateTime property
The precision and scale of numeric properties
A String or Binary property as fixed-length
A String property as non-unicode
The on-delete behavior of relationships
Advanced mapping strategies
That said, I'm not telling you to use Fluent API instead of Data Annotation :-)
As you seem to work on an MVC application, you should keep in mind that Data Annotation attributes will be understood and processed by both by Entity Framework and by MVC for validation purposes. But MVC won't understand the Fluent API configuration!
Both your Villa and Apartment classes have similar properties, if they are the same but as it's type, you could create an enum for that.
public enum PropertyType {
Apartment = 1,
Villa
}
public class Property {
public int PropertyID { get; set; }
public bool IsforSale { get; set; }
public bool IsforRent { get; set; }
public PropertyType PropertyType { get; set; }
public int SquareMeter { get; set; }
}
This way of modelating objects is refered as plain old clr object or POCO for short.
Assume this model:
public class User {
public int UserId { get; set; }
public string Username { get; set; }
public virtual List<Role> Roles { get; set; }
}
public class Role {
public int RoleId { get; set; }
public string Name { get; set; }
public virtual List<User> Users { get; set; }
}
Creating relations with fluent api:
Mapping many to many
On your OnModelCreating method (you'll get this virtual method when deriving from DbContext):
protected override void OnModelCreating(DbModelBuilder builder) {
// Map models/table
builder.Entity<User>().ToTable("Users");
builder.Entity<Role>().ToTable("Roles");
// Map properties/columns
builder.Entity<User>().Property(q => q.UserId).HasColumnName("UserId");
builder.Entity<User>().Property(q => q.Username).HasColumnName("Username");
builder.Entity<Role>().Property(q => q.RoleId).HasColumnName("RoleId");
builder.Entity<Role>().Property(q => q.Name).HasColumnName("Name");
// Map primary keys
builder.Entity<User>().HasKey(q => q.UserId);
builder.Entity<Role>().HasKey(q => q.RoleId);
// Map foreign keys/navigation properties
// in this case is a many to many relationship
modelBuilder.Entity<User>()
.HasMany(q => q.Roles)
.WithMany(q => q.Users)
.Map(
q => {
q.ToTable("UserRoles");
q.MapLeftKey("UserId");
q.MapRightKey("RoleId");
});
Mapping different types of relationships with fluent api:
One to zero or one:
Given this model:
public class MenuItem {
public int MenuItemId { get; set; }
public string Name { get; set; }
public int? ParentMenuItemId { get; set; }
public MenuItem ParentMenuItem { get; set; }
}
And you want to express this relationship, you could do this inside your OnModelCreating method:
builder.Entity<MenuItem>()
.HasOptional(q => q.ParentMenuItem)
.WithMany()
.HasForeignKey(q => q.ParentMenuItemId);
One to many
Given this model:
public class Country {
public int CountryId { get; set; }
public string Name { get; set; }
public virtual List<Province> Provinces { get; set; }
}
public class Province {
public int ProvinceId { get; set; }
public string Name { get; set; }
public int CountryId { get; set; }
public Country Country { get; set; }
}
You now might want to express this almost obvious relationship. You could to as follows:
builder.Entity<Province>()
.HasRequired(q => q.Country)
.WithMany(q => q.Provinces)
.HasForeignKey(q => q.CountryId);
Here are two useful links from MSDN for further info:
Configuring Relationships with the Fluent API.
Code First Relationships Fluent API.
EDIT:
I forgot to mention how to create a many to many relationship with additional properties, in this case EF will NOT handle the creation of the join table.
Given this model:
public class User {
public int UserId { get; set; }
public string Username { get; set; }
public virtual List<Role> Roles { get; set; }
pubilc virtual List<UserEmail> UserEmails { get; set; }
}
pubilc class Email {
public int EmailId { get; set; }
public string Address { get; set; }
public List<UserEmail> UserEmails { get; set; }
}
public class UserEmail {
public int UserId { get; set; }
public int EmailId { get; set; }
public bool IsPrimary { get; set; }
public User User { get; set; }
public Email Email { get; set; }
}
Now that we've added a new property into our join table ef will not handle this new table.
We can achieve this using the fluent api in this case:
builder.Entity<UserEmail>()
.HasKey( q => new {
q.UserId, q.EmailId
});
builder.Entity<UserEmail>()
.HasRequired(q => q.User)
.WithMany(q => q.UserEmails)
.HasForeignKey(q => q.EmailId);
builder.Entity<UserEmail>()
.HasRequired(q => q.Email)
.WithMany(q => q.UserEmails)
.HasForeignKey(q => q.UserId);

Asp.net MVC 4 Code First Return Specific Fields from Navigation Property

Been stuck on this for a while so i thought i would ask. I am sure there is something simple i am missing here. Trying to learn Asp.net mvc 4 on my own by building a simple app.
Here is the model:
public class Category
{
public int Id { get; set; }
[Required]
[StringLength(32)]
public string Name { get; set; }
//public virtual ICollection<Note> Notes { get; set; }
private ICollection<Note> notes;
public ICollection<Note> Notes
{
get
{
return this.notes ?? (this.notes = new List<Note>());
}
}
}
public class Note
{
public int Id { get; set; }
[Required]
public string Content { get; set; }
[Required]
[StringLength(128)]
public string Topic { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual IEnumerable<Comment> Comments { get; set; }
public virtual ICollection<Tag> Tags {get; set;}
public Note()
{
Tags = new HashSet<Tag>();
}
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Note> Notes { get; set; }
public Tag()
{
Notes = new HashSet<Note>();
}
}
I call this method in a repository from the controller:
public IQueryable<Note> GetAll()
{
var query = _db.Notes.Include(x => x.Category).Include(y => y.Tags);
return query;
}
On the home controller i am trying to return a list of all the notes and wanted to include the category name that it belongs to as well as the tags that go with the note. At first the did not show up so i read some tutorials about eager loading and figured out how to get them to show.
However, my method is not that efficient. The mini-profiler is barking at me for duplicate queries because the navigation properties for category and tags are sending queries for the notes again. IS there any way to just return the properties i need for the category and tag objects?
I have tried several methods with no luck. I was hoping i could do something like this:
var query = _db.Notes.Include(x => x.Category.Name).Include(y => y.Tags.Name);
But i get an error: Cannot convert lambda expression to type 'string' because it is not a delegate type
I have seen that error before that was caused by some missing using statements so i already double checked that.
Any suggestions? Thanks for the help