【问题标题】:Invalid column name error from SQL generated by EFEF 生成的 SQL 中的列名无效错误
【发布时间】:2017-07-13 01:05:32
【问题描述】:

您可以在下面看到 SQL 应该使用 [ClassId1] 而不是 [Class1_ClassId] 加入,因为后者不存在。

我很确定我可以使用 Fluent API 来纠正这个问题,但不确定是什么方法。

生成的 SQL

SELECT ...
FROM [dbo].[School] AS [Extent1]
LEFT OUTER JOIN [dbo].[Student] AS [Extent2] ON [Extent1].[SchoolId] = [Extent2].[SchoolId]
LEFT OUTER JOIN [dbo].[Class] AS [Extent3] ON [Extent2].[Class1_ClassId] = [Extent3].[ClassId]
LEFT OUTER JOIN [dbo].[Class] AS [Extent4] ON [Extent2].[Class2_ClassId] = [Extent4].[ClassId]
WHERE ...   

表格

School
- SchoolId
- Name
- StudentId

Student
- StudentId
- Name
- Class1Id
- Class2Id

Class
- ClassId
- Name

模型

public class School
{
    [Required]
    public long SchoolId { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public long StudentId { get; set; }

    public virtual Student Student { get; set; }
}

public class Student
{
    [Required]
    public long StudentId { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public long ClassId1 { get; set; }

    public long? ClassId2 { get; set; }

    public virtual Class Class1 { get; set; }
    public virtual Class Class2 { get; set; }
}

public class Class
{
    [Required]
    public long ClassId { get; set; }

    [Required]
    public string Name { get; set; }
}

【问题讨论】:

    标签: c# sql entity-framework ef-fluent-api


    【解决方案1】:
    [Required]
    public long ClassId1 { get; set; }
    
    public long? ClassId2 { get; set; }
    
    public virtual Class Class1 { get; set; }
    public virtual Class Class2 { get; set; }
    

    您尚未在这些属性之间设置任何关系。由于您尚未为 Class1Class2 定义外键列,因此它将为您创建它们:Class1_ClassIdClass2_ClassId。创建迁移应该为您创建这些列;但你最终会得到重复(例如Class1IdClass1_ClassId)。

    相信如果名称以Id 结尾,EntityFramework 将解析属性之间的关系。这意味着您的设置应该是:

    [Required]
    public long Class1Id { get; set; }
    
    public long? Class2Id { get; set; }
    
    public virtual Class Class1 { get; set; }
    public virtual Class Class2 { get; set; }
    

    但是,我发现最好是明确的 - 纯粹是为了可读性并确保 EF 不会试图变得太聪明。我会这样写:

    [Required]
    public long ClassId1 { get; set; }
    
    public long? ClassId2 { get; set; }
    
    [ForeignKey("ClassId1")]
    public virtual Class Class1 { get; set; }
    [ForeignKey("ClassId2")]
    public virtual Class Class2 { get; set; }
    

    这应该会正确设置您在数据库中的外键关系。

    【讨论】:

    • 正是我所缺少的!感谢您的帮助和快速响应。最后一个后续问题:我猜我不需要在其他虚拟属性上使用这些,因为 EF 能够自行解决它们?
    • @user3953989 这取决于您如何设置其他属性。我相信意识到ForeignRowForeignRowId 是相关的就足够聪明了;但老实说,我有一段时间没有与 EF 合作过,并且不确定确切的规则。我倾向于对所有外键都明确表示,不管 EF 是否可以自动计算出来。
    【解决方案2】:

    我认为 Entity Framework 从你的 linq 构造了这个 SQL,因为你的模型中的类之间的关系不清楚。

    根据您的模型,School 只有一个StudentStudent 不知道他参加的是哪个School,并且必须拥有一个Class,可能还有第二个。一个class 不知道它在哪个School 中,也不知道哪个StudentsClass 中。

    你确定你的模型吗?

    我认为School 将有零个或多个Students。一个School 也有零个或多个Classes。每个class 都是School 中的一个类。

    在数据库术语中,这是典型的一对多关系。见Entity Framework Configure One-to-Many Relationship

    此外,Student 参与零个或多个 ClassesClass 具有一个或多个 Students

    在数据库术语中,这是典型的多对多关系。见:Entity Framework configure many-to-many relationship

    这些文章还描述了学校、学生和学校。总结类定义应该是:

    class School
    {
        public int Id {get; set;}
    
        // a School has many Students:
        public virtual ICollection<Student> Students {get; set;}
        // a School has many Classes:
        public virtual ICollection<Class> Classes {get; set;}
        ...
    }
    
    public class Student
    {
        public int Id {get; set;}
    
        // A student belongs to one School via Foreign Key
        public int SchoolId {get; set;}
        public virtual School School {get; set;}
    
        // A student attends many classes
        public virtual ICollection<Class> Classes {get; set;}
        ...
    }
    
    class Class
    {
        public int Id {get; set;}
    
        // a class belongs to one School via foreign key:
        public int SchoolId {get; set;}
        public virtual School School {get; set;}
    
        // a class has many Students
        public virtual ICollection<Student> Students {get; set;}
        ...
    }
    

    在此之后 DbContext 将如下所示:

    class MyDbContext : DbContext
    {
        public DbSet<School> Schools {get; set;}
        public DbSet<Student> Students {get; set;}
        public DbSet<Class> Classes {get; set;}
    }
    

    如果你这样建模,实体框架会自动识别学校和学生之间的一对多关系,并为其创建适当的外键。它还将识别学生和班级之间的多对多关系。它甚至会为您在 LINQ 查询中不需要的多对多创建表。

    Entity Framework uses default conventions如果你关注他们,你就不需要告诉模型表名和列名,主键和外键等。

    回到你的问题

    您想告诉您的模型,它应该为属性使用某个列名,而不是根据您的类关系构造的列名。

    这可以在您的类中使用 Data Annotations 或在您的 DbContext 中使用 Fluent API 来完成。我更喜欢使用 Fluent Api,因为它允许您在不同的数据库结构中使用相同的类,而无需更改类。如果您想要不同的表名,或不同的主键名称,不同的小数精度等,您所要做的就是创建一个新的 DbContext。您不必更改课程,您课程的用户不会注意到这些更改。

    Fluent API is described here.

    在你的情况下:指定一个表名而不是默认表名。

    在我的示例中,A Class 将放在表 Classs 中,而您当然希望将它放在表 Classes 中:

     class MyDbContext : DbContext
    {
        public DbSet<School> Schools {get; set;}
        public DbSet<Student> Students {get; set;}
        public DbSet<Class> Classes {get; set;}
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Entity Class should be put in table Classes
            modelBuilder.Entity<Class>().ToTable("Classes");
    
            // property Student.ClassId in column "ClassId1"
            modelBuilder.Entity<Student>()               // from class Student
                .Property(student => student.ClassId)    // take property ClassId
                .HasColumnName("ClassId1");              // give it the column name "ClassId1"
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 2021-08-19
      • 2020-03-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多