Is the EF model class correctly?

c# entity-framework entity-framework-core

Question

I want to use EF code first to apply the existing database tables modeling.

Let's say I have three tables.

  1. Project table. The primary key is ProjectId, it has other columns such as ProjectName, StartDate and EndDate etc.
  2. Technology table. The primary key is TechnologyId, the other columns are technologyName, Note etc.
  3. ProjectTechnologyLink. Link the two tables together. It has the Primary key ProjectId and TechnologyId also they are the foreign keys.

The sample data in ProjectTechnologyLink

ProjectId    TechnologyId    CreatedBy  CreatedDate
25            3               One       2016-01-01
100           4               One       2016-01-01
100           8               Two       2016-01-01

Assume One project can have many technologies and one technology can exist in many projects.

The model classes are: For Project:

public class Project
{
    public int ProjectId {get;set;}
    public string ProjectName {get;set;}
    ...
    public ICollection<ProjectTechnologyLink> ProjectTechnologyLink
}

For Technology:

public class Technology
{ 
    public Technology()
    {
        ProjectTechnologyLink = new HashSet<ProjectTechnologyLink>();
    }
    public int TechnologyId {get;set;}
    public string TechnologyName {get;set;}
    ...
    public ICollection<ProjectTechnologyLink> ProjectTechnologyLink {get;set;}
 }

For ProjectTechnologyLink class.

public class ProjectTechnologyLink 
{
    public int ProjectId {get;set;}
    public int TechnologyId {get;set;}
    ...
    public Project Project {get;set;}
    public Technology Technology {get;set;}
}

Then in OnModelCreating method.

modelBuilder.Entity<ProjectTechLink>(entity => 
    {
        entity.HasKey(e=> new {e.ProjectId, e.TechnologyId});
        entity.ToTable("ProjectTechnology", "myscheme");
        entity.HasOne(x=>x.Project)
              .WithMany(p=>p.ProjectTechnologyLink)
              .HasForeignKey(d=>d.ProjectId)
              .OnDelete(DeleteBehavior.ClientSetNull)
              .HasConstraintName("FK_PROJECT_TECHNOLOGY_LINK_PID");

       entity.HasOne(x=>x.Technology)
              .WithMany(p=>p.ProjectTechnologyLink)
              .HasForeignKey(d=>d.TechnologyId)
              .OnDelete(DeleteBehavior.ClientSetNull)
              .HasConstraintName("FK_PROJECT_TECHNOLOGY_LINK_TID");
      });

My question is Is it right for the all code? Sometimes I saw people put some attributes before the properties in the class. But I don't have it.

1
2
10/7/2019 1:47:02 AM

Accepted Answer

Your code is very acceptable, and should serve you well, but lets review why there is a mix of Attributes and Fluent API out there.

The EF pipeline has three main points where we can inject database metadata (table configuration), these include:

  1. Attribute Notation
  2. Code First Conventions
  3. Fluent Configuration API

The order above list also depicts the order that they are processed within the pipeline, internally the Attributes are meaningless to the DbContext until they are parsed by built in (or customised) Conventions, these conventions internally use the Fluent API to configure the DbContext.

Different scenarios call for and allow different mixes, generally in a Code First scenario Attribute Notation is preferred over heavy use of the Fluent API. However foreign key constraints that include cascading deletes or many to many relationships such as this are often expressed in Fluent API directly as the syntax can be a bit simpler as well as to ensure that no other conventions can overrule our expected implementation in the database.

Attribute notation allows your table schema to be mostly contained within the class definition, but still allows the application runtime to override the interpretations of these attributes by customising, or disabling built in conventions.

If your metadata can be expressed using attributes, then the entire specification of your schema becomes more concise, which is especially useful when presenting your code solutions online in examples where the structure and relationships are important to be shown. If your online example only needs to express the relationship, then those examples will often only use fluent notation. - It makes for a heard to implement code example if you need express both the schema and relationship mapping separately.

If you use attribute notation, then your example can be expressed like this, and you will not need anything extra in OnModelCreating:

public class Project
{
    [Key]
    public int ProjectId { get; set; }
    public string ProjectName { get; set; }
    ...
    public virtual ICollection<ProjectTechnologyLink> ProjectTechnologyLink { get; set; } = new HashSet<ProjectTechnologyLink>();
}

NOTE: In the above Project class definition I have used an inline initializer for the ProjectTechnologyLink relationship, i find this style fits in well with Attribute Notation as the default value is now also defined in close proximity with the Property, when initializers are only only defined in the constructor it is easy to forget to include an init at all or it can be hard to find the init logic in code. Now a quick "Got To Definition" will reveal the default implementation as well as any database schema related attributes without having to lookup other resources.

public class Technology
{ 
    public Technology()
    {
        ProjectTechnologyLink = new HashSet<ProjectTechnologyLink>();
    }
    [Key]
    public int TechnologyId { get; set; }
    public string TechnologyName { get; set; }
    ...
    public virtual ICollection<ProjectTechnologyLink> ProjectTechnologyLink { get; set; }
 }
public class ProjectTechnologyLink 
{
    [Key, Column(Order = 0)]
    public int ProjectId { get; set; }
    [Key, Column(Order = 0)]
    public int TechnologyId { get; set; }
    ...
    [ForeignKey(nameof(ProjectId))]
    public virtual Project Project { get; set; }
    [ForeignKey(nameof(TechnologyId))]
    public virtual Technology Technology { get; set; }
}

virtual Navigation Properties: In EF it is important to mark your navigation properties as virtual members. This will allow EF to implement lazy loading and perform other optimised implementation of those properties when it generates a wrapper class that inherits from your entity class. Even if you do not intend to support Lazy Loading, there are other scenarios where EF will wrap your class, and either way your data definition classes should not be concerned or aware of operational decision that can be made and changed at runtime depending on your context needs.

Conventions: The previous example demonstrates pure attribute notation. It is very possible to replace the default Conventions with your own for defining primary and foreign keys. Meaning it is theoretically possible to not have any attributes or Fluent notation at all. I try to discourage a pure convention based approach because it makes it a bit harder to find the configuration in a large or distributed schema definition, which is also the same argument I use to discourage a pure Fluent API approach, attributes are the logical place to document the expected usage of a table or field.

1
10/8/2019 12:53:37 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow