【问题标题】:Is the EF model class correctly?EF 模型类是否正确?
【发布时间】:2019-10-08 08:53:37
【问题描述】:

我想先用 EF 代码应用现有的数据库表建模。

假设我有三张桌子。

  1. Project 表。主键是ProjectId,还有ProjectName、StartDate、EndDate等其他列。
  2. Technology 表。主键是 TechnologyId,其他列是 technologyName、Note 等。
  3. ProjectTechnologyLink。将两个表链接在一起。它具有主键 ProjectId 和 TechnologyId,它们也是外键。

ProjectTechnologyLink中的样本数据

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

假设一个项目可以有多种技术,而一种技术可以存在于多个项目中。

模型类是: 对于项目:

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

技术方面:

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;}
 }

对于 ProjectTechnologyLink 类。

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

然后在 OnModelCreating 方法中。

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");
      });

我的问题是 它适用于所有代码吗?有时我看到人们将一些属性放在类中的属性之前。但我没有。

【问题讨论】:

  • 看起来不错,除了您应该将删除级联到链接表,而不是将 ProjectTechLink fk 设置为 null。
  • @DavidBrowne-Microsoft 所以你的意思是我需要删除OnDelete(DeleteBehavior.ClientSetNull)
  • 是的。链接表通常使用DeleteBehavior.Cascade,除非您有特殊原因说明 ProjectTechnologyLink 应该在删除项目或技术后仍然存在。

标签: c# entity-framework entity-framework-core


【解决方案1】:

您的代码非常容易接受,应该可以很好地为您服务,但让我们回顾一下为什么会有 Attributes 和 Fluent API 混合使用。

EF 管道有三个主要点,我们可以在其中注入数据库元数据(表配置),其中包括:

  1. 属性表示法
  2. 代码优先约定
  3. Fluent 配置 API

上面的顺序还描述了它们在管道中的处理顺序,在内部,属性对 DbContext 没有意义,直到它们被内置(或自定义)约定解析,这些约定在内部使用 Fluent API 来配置数据库上下文。

不同的场景需要并允许不同的混合,通常在 Code First 场景中属性表示法优于大量使用 Fluent API。然而,包括级联删除或多对多关系的外键约束通常直接在 Fluent API 中表示,因为语法可以更简单一些,并确保没有其他约定可以推翻我们在数据库中的预期实现。

属性表示法允许您的表架构大部分包含在类定义中,但仍允许应用程序运行时通过自定义或禁用内置约定来覆盖这些属性的解释。

如果您的元数据可以使用属性来表达,那么您的架构的整个规范就会变得更加简洁,这在在线展示您的代码解决方案时尤其有用,因为示例中的结构和关系非常重要。如果您的在线示例只需要表达关系,那么这些示例通常只会使用流利的符号。 - 如果您需要分别表达模式和关系映射,则可以实现代码示例。

如果您使用属性表示法,那么您的示例可以这样表示,并且您将不需要在 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>();
}

注意:在上面的 Project 类定义中,我为 ProjectTechnologyLink 关系使用了内联初始化器,我发现这种风格非常适合 Attribute Notation由于默认值现在也定义在与属性非常接近的位置,当初始化器仅在构造函数中定义时,很容易忘记包含 init 或者很难找到代码中的初始化逻辑。现在,一个快速的“Got To Definition”将显示默认实现以及任何与数据库架构相关的属性,而无需查找其他资源。

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; }
}

虚拟导航属性: 在 EF 中,将导航属性标记为 virtual 成员很重要。这将允许 EF 在生成从您的实体类继承的包装类时实现延迟加载并执行这些属性的其他优化实现。即使您不打算支持 延迟加载,在其他情况下 EF 也会包装您的类,无论哪种方式,您的数据定义类都不应该关心或意识到可以做出的操作决策和根据您的上下文需求在运行时更改。

约定: 前面的示例演示了纯属性表示法。很有可能用您自己的替换默认的 Conventions 来定义主键和外键。这意味着理论上可能根本没有任何属性或流利表示法。我试图阻止纯基于约定的方法,因为它使在大型或分布式模式定义中查找配置有点困难,这也是我用来阻止纯 Fluent API 方法的相同论点,属性是逻辑位置记录表或字段的预期用途。

【讨论】:

  • 在两个类的构造函数中,有细微的差别。一个有new HashSet,另一个没有。对吗?
  • 我更新了Project 的代码示例,以显示我喜欢的初始化处理。我更喜欢将其保留在构造函数之外,因为它是一般的管道逻辑,我更愿意将它与您可能希望在构造函数中执行的任何业务逻辑分开。为了促进快速和直观的编码,因为集合是一个通用接口,所以您现在可以控制将在数据库查询响应的反序列化或具体化期间使用的类型,而不是任由偶然或约定俗成。说到这里,让这些关系属性虚拟
猜你喜欢
  • 2018-12-16
  • 1970-01-01
  • 1970-01-01
  • 2013-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多