Entity Framework [Foreignkey]

Asked

Viewed 2,266 times

3

Reading a tutorial on Entity framework, I came across a somewhat strange example for me. This example is about Dataannotation [ForeignKey]

public class Student
{
    public Student()
    { 

    }
    public int StudentID { get; set; }
    public string StudentName { get; set; }

    //Foreign key for Standard
    public int StandardId { get; set; }

    public Standard Standard { get; set; }
}

public class Standard
{
    public Standard()
    { 

    }
    public int StandardId { get; set; }
    public string StandardName { get; set; }

    public ICollection<Student> Students { get; set; }

    }

In a scenario without ORM, that is, dealing directly with ADO.net, I see no sense that Student has an attribute referring to Id of Standard, for Student already has a reference to Standard, where this reference already has the Id. Why does the RU work this way? Seems redundant to me.

  • It is not clear what [ForeignKey] has to do with the question. If you think you have redundancy, you need to look at the context. It may be redundant, but it was a decision of who made this code. But it may not be. It is without context.

3 answers

5


Matheus, I believe you’re not taking one thing into account: LazyLoad.

Defido to Lazyload, to fetch one Student, only the data of Student are recovered from the Bank, only if you come to access the Navigation property Standard that the EF will fetch your data.

To make this load lazy, it is necessary that the navigation property has the modifier virtual, with this the EF can modify the behavior of the same.

Observe the mapping of your entities using Code First, maybe you can notice it better.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

public partial class Contexto : DbContext
{
    public Contexto() : base("name=Contexto")
    {
    }

    public virtual DbSet<Standard> Standards { get; set; }
    public virtual DbSet<Student> Students { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Standard>().Property(e => e.Name).IsUnicode(false);
        modelBuilder.Entity<Standard>().HasMany(e => e.Students).WithRequired(e => e.Standard).WillCascadeOnDelete(false);
        modelBuilder.Entity<Student>().Property(e => e.Name).IsUnicode(false);
    }
}

[Table("Student")]
public partial class Student
{
    public int StudentID { get; set; }

    [Required]
    [StringLength(50)]
    public string Name { get; set; }

    public int StandardID { get; set; }

    public virtual Standard Standard { get; set; }
}

[Table("Standard")]
public partial class Standard
{
    public Standard()
    {
        Students = new HashSet<Student>();
    }

    public int StandardID { get; set; }

    [Required]
    [StringLength(50)]
    public string Name { get; set; }

    public virtual ICollection<Student> Students { get; set; }
}

If you create a new entity Student, and set a value for StandardID that already exists in the database, and after this try to access the property Standard the RU retrieves the data from the Entity Standard in the Database.

But if you do not access the navigation property and only set the Property Value StandardID, the additional query will not be performed, but if you try to save the entity Student, EF will have no problem doing it.

Note that it is not always necessary to access a navigation property, sometimes knowing only the ID of the same is enough.

  • The Nhibernate does not use this "foreign key" in the class and makes lazyload very well. I think the EF team went soft even. Nhibernate loads a proxy class with only the completed identifier and when it needs more information goes to the BD. and instead of setting Standardid with id we set the Standard variable itself with Session.load(id) that it loads the proxy class without going to the BD, trusting that the ID exists.

  • Thanks for the clarification. Seeing other examples I came across another strange situation. There are two classes Student and Address. By UML logic, a Student has a Address, that is, in the class Student there must be a navigational property for Address. But in the example I’m seeing, Student has a navigation object for Address and Address tb has a navigation attribute for Student.&#That doesn’t make much sense to me because it’s a Student is that it has a Address and not the other way around.

  • @Matheussaraiva, in EF you can also context.Standards.Find(id)should be something similar to your example in Nhibernate, and in the same way as Nhibernate, EF needs the navigation property to be virtual in order to create the proxy, thus doing Lazyload. And there is this of loading the proxy class without going to the bank, you can even turn on the profile to see the queries being executed

  • As for the factor of the Student having an Address and not the reverse, you can understand this as a business rule, and not as a technological limitation, after all we can have bidirectional 1:1 relationships, in any case I see no problem in the Studant being accessible from Adress, but if you prefer, you may not do this mapping.

  • As for comparing the Nhibernate with the Entity Framework, the EF supports asynchronous operations, has a better support to Latin, I’m used to EF and now I work with Nhibernate, I always get pissed when an Exception appears OperationNotSupported.

3

As stated in the comments by @Maniero, it is difficult to judge whether or not the mapping is correct without the context, what can be done is to analyze and explain what the code is doing.

This mapping is not wrong:

public class Student
{
    public Student()
    { 

    }
    public int StudentID { get; set; }
    public string StudentName { get; set; }

    //Não necessida da anotação [ForeignKey]
    public int StandardId { get; set; }

    public Standard Standard { get; set; }
}

public class Standard
{
    public Standard()
    { 

    }
    public int StandardId { get; set; }
    public string StandardName { get; set; }

    public ICollection<Student> Students { get; set; }

    }

In the code above, we have a mapping of 1:N, where a Standard has several Students , where the property StandardId is the foreing key of model Student. This is correct if the goal is this. Using the Entity Framework, you do not need to use the annotation [ForeignKey], for the name of foreing key is the name of the navigation property with the prefix id, that is to say, StandardId (Standard + Id).

On the contrary, the model Standard has a collection of Students, where it does not need to have a foreing key for Students, characterizing the connection of 1:N.

-1

It’s very simple. Entity Framework will anyway create this Standardid in the database. What happens is, whether or not you have control of creating this column?

Another, you can perform queries in the table of Student without having to go to the Standard. Example: You need to select all Student of Standard 10.

_context.Students
    .Where(student => student.StandardId == 10)
    .ToList(); // Acessa apenas a entidade Student
// SELECT * FROM Student WHERE StandardId = 10

If you let implicit the StandardId, would have to do:

_contexto.Students
    .Where(student => student.Standard.Id == 10)
    .ToList(); // Acessa entidades Student e Standard
// SELECT Student.* FROM Student
//   JOIN Standard ON Standard.Id = Student.StandardId
//  WHERE Standard.Id = 10
  • _context.Students.Where(student => student.StandardId == 10) and _contexto.Students.Where(student => student.Standard.Id == 10) will not execute any query, will generate a Expression Tree, but when executing Tolist, this Expression Tree is converted to a comando SQL, and in the case above, the two commands generate the same query, so they are equivalent, so I’m not seeing your point.

  • @Tobymosque, in the example I had already put the .ToList(). I edited the answer to show the queries that Entityframework will create.

  • @Thiafolunardi, experimented to make a var query1 = (_context.Students.Where(student => student.StandardId == 10)).ToString() and var query 2= (_context.Students.Where(student => student.Standard.Id == 10)).ToString()? if you do so you will see that query1 is equal to query2, something similar to SELECT &#xA; [Extent1].[StudentID] AS [StudentID], &#xA; [Extent1].[Name] AS [Name], &#xA; [Extent1].[StandardID] AS [StandardID]&#xA;FROM [dbo].[Student] AS [Extent1]&#xA;WHERE 10 = [Extent1].[StandardID]

  • @Tobymosque, the query2 will stay SELECT [Extent1].[StudentID] AS [StudentID], [Extent1].[Name] AS [Name], [Extent1].[StandardID] AS [StandardID] FROM [dbo].[Student] AS [Extent1], [dbo].[Standard] AS [Extent2] WHERE [Extend2].[ID] = [Extent1].[StandardID] AND 10 = [Extent1].[StandardID]

  • I don’t know what version of EF is using, but I am using the EF 6.1.3 and in my case the query1 is equal to query2, in both cases the JOIN with Standard

Browser other questions tagged

You are not signed in. Login or sign up in order to post.