如何在 Entity Framework Core 中的多个表上调用存储过程?

How to call Stored Procedure with join on multiple tables in Entity Framework Core?

我必须调用一个存储过程,它 select 从多个表中获取记录。

我已经尝试了以下代码,但是对于实体 class.

以外的其他表中的列,它 returning 为 null
private async Task<IEnumerable<TEntity>> InvokeStoredProcedureAsync(string input = "")
{
    var storedProcedureName = "sp_BulkSelect";

    using (var db = new MyDbContext(_options))
    {
        var result = await db.Set<TEntity>().FromSql(storedProcedureName + " @inputIds", new SqlParameter("inputIds", input)).ToListAsync();
        return result;
    }
}

存储过程:

SELECT 
    [MainTable].[Id],
    [Table1Id],
    [Table2Id],
    [MainTable].[Table1Code],
    [Table2].[Table2Code]
FROM
    [MainTable] [MainTable]
LEFT JOIN 
    [Table1] [Table1] ON [MainTable].Table1Id = [Table1].[Id]
LEFT JOIN 
    [Table2] [Table2] ON [MainTable].[Table2Id] = [Table2].[Id];

MainTable class:

[Table("MainTable")]
public class MainTable : FullAuditedEntity
{

    [ForeignKey("Table1Id")]
    public virtual Table1 Table1 { get; set; }
    public virtual int Table1Id { get; set; }

    [ForeignKey("Table2Id")]
    public virtual Table2 Table2 { get; set; }
    public virtual int? Table2Id { get; set; }      

}

因此,当我调用此存储过程时,return 值中缺少 Table1CodeTable2Code

我尝试在 MainTable class 中添加以下代码,但它也不起作用。

[NotMapped]
public virtual string Table2Code { get; set; }

[NotMapped]
public virtual string Table1Code { get; set; }

然后我从属性中删除了 [NotMapped] 并添加了迁移,在本例中,它的 returning 是正确的值。但它会在 MainTable 中添加两列。 真是BAD设计.

所以我的问题是如何在 Entity Framework 核心的存储过程中 select 来自多个表的列。

我正在使用 EF Core 2.0。

我认为必须有一些方法可以使用实体调用存储过程,然后将其映射到任何 class,因为使用连接从多个表中获取 select 列是一个非常基本的要求。

我尝试了类似的solution,但它给出了编译错误。

'DatabaseFacade' does not contain a definition for 'SqlQuery' and no extension method 'SqlQuery' accepting a first argument of type 'DatabaseFacade' could be found (are you missing a using directive or an assembly reference?)

从存储过程中获取数据的完整思路如下:

  1. 您需要添加一个与过程 select 查询具有相同属性的实体。
  2. 将实体添加到您的 DbContext 并创建迁移。更改迁移的 Up()Down() 方法中的代码,以便它在数据库中创建过程。
  3. 现在使用FromSql()方法获取数据为普通实体数据。

下面是一些可以指导您的代码。假设您的应用程序域中有这些实体:

  1. 学生
  2. Parent
  3. 学校班级
  4. 部分
  5. 注册人数

向上迁移方法

protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "StudentDetails");

        migrationBuilder.Sql(
            @"create proc GetStudentDetail
            @ssid int,
            @sectionId int = null
            as
            select Id, name, Gender, RollNumber, Status, Type,
            FatherName, FatherContact, SchoolClass, Section,
            SsId, SectionId, EnrollmentId
            from 
            (
                SELECT stu.Id, stu.name, stu.Gender, en.RollNumber, en.Status, en.Type,
                p.FatherName, p.FatherContact, sc.Name as SchoolClass, sec.Name as Section,
                ss.SessionId as SsId, sec.Id as SectionId, en.Id as EnrollmentId,
                en.EntryDate, row_number() over (partition by studentid order by en.entrydate desc) as rowno
                from SchoolSessions ss
                join SchoolClasses sc on ss.SessionId = sc.ssid
                join Sections sec on sc.Id = sec.ClassId
                join Enrollments en on sec.id = en.SectionId
                join Students stu on en.StudentId = stu.Id
                join parents p on stu.ParentId = p.Id 
                where ss.SessionId = @ssid 
            ) A
            where rowno = 1 and
            (SectionId = @sectionId or @sectionId is null)"
            );
    }

向下迁移方法

protected override void Down(MigrationBuilder migrationBuilder)
    {

        migrationBuilder.Sql("drop proc GetStudentDetail");

        migrationBuilder.CreateTable(
            name: "StudentDetails",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                EnrollmentId = table.Column<int>(nullable: false),
                FatherContact = table.Column<string>(nullable: true),
                FatherName = table.Column<string>(nullable: true),
                Gender = table.Column<int>(nullable: false),
                Name = table.Column<string>(nullable: true),
                RollNumber = table.Column<string>(nullable: true),
                SchoolClass = table.Column<string>(nullable: true),
                Section = table.Column<string>(nullable: true),
                SectionId = table.Column<int>(nullable: false),
                SsId = table.Column<int>(nullable: false),
                Status = table.Column<int>(nullable: false),
                Type = table.Column<int>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_StudentDetails", x => x.Id);
            });
    }

假实体:该实体的所有属性都来自 above-said 个实体。你可以称之为假实体。

public class StudentDetail
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Gender Gender { get; set; }
    public string RollNumber { get; set; }
    public StudentStatus Status { get; set; }
    public StudentType Type { get; set; }
    public string FatherName { get; set; }
    public string FatherContact { get; set; }
    public string SchoolClass { get; set; }
    public string Section { get; set; }
    public int SsId { get; set; }
    public int SectionId { get; set; }
    public int EnrollmentId { get; set; }
}

服务层获取数据

public IEnumerable<StudentDetail> GetStudentDetails(int ssid)
    {
        var ssidParam = new SqlParameter("@ssid", ssid);
        var result = _appDbContext.StudentDetails.FromSql("exec GetStudentDetail @ssid", ssidParam).AsNoTracking().ToList();
        return result;
    }

这就是它在 EF Core 2.1 中的工作方式:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   modelBuilder.Query<YourModel>();
}

SqlParameter value1Input = new SqlParameter("@Param1", value1 ?? (object)DBNull.Value);
SqlParameter value2Input = new SqlParameter("@Param2", value2 ?? (object)DBNull.Value);

List<YourModel> result;
using (var db = new MyDbContext(_options))
{
    result = await db.Query<YourModel>().FromSql("STORED_PROCEDURE @Param1, @Param2", value1Input, value2Input).ToListAsync();
}

Source

以下步骤适用于数据库设计优先方法 EF 核心 3.1.0

1) 假设 SP(sp_BulkSelect) return 3 列乘以多个 tables(col1 int,col2 string,col3 string)

2) 创建将保存此数据的假 Class,DataType 应与每个 SP 列相同

  public Class1
      {
       public int col1 {get;set;}
       public string col2 {get;set;}
       public string col3 {get;set;}
      }

3) 使用模型构建器映射 MyDbContext 中的调用和实体 class

 modelBuilder.Entity<Class1>(entity =>
            {
                entity.HasNoKey();
                entity.Property(e => e.col1);
                entity.Property(e => e.col2);
                entity.Property(e => e.col3);

            });

4) 在 MyDbContext class

中创建假 table
  public virtual DbSet<Class1> Class_1 { get; set; }

5) 使用 MyDbContext class 调用 SP

 var Class1data = MyDbContext.Class_1.FromSqlRaw("EXECUTE sp_BulkSelect {0}", id);

1- 从 sql 查询的 select 部分创建视图 -- 没有 where 条件--。

2- 然后从数据库生成模型,Entity Framework 为视图生成模型。

3- 现在您可以使用从视图生成的模型执行存储过程

dbContext.GeneratedView.FromSqlRaw("MyStoredProcedure {0}, {1} ", param1, param2)