问题
I have to call a stored procedure which is selecting records from multiple tables.
I have tried the following code, but it's returning null for columns from other tables than the entity class.
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;
}
}
Stored procedure:
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; }
}
So when I call this stored procedure, Table1Code
and Table2Code
are missing in the return value.
I tried to add the following code in MainTable
class, but its also not working.
[NotMapped]
public virtual string Table2Code { get; set; }
[NotMapped]
public virtual string Table1Code { get; set; }
Then I removed [NotMapped]
from both the properties and added migration, in this case, its returning proper value. But It will add two columns in MainTable. It's really a BAD
design.
So my question is how to select columns from multiple tables in the stored procedure in Entity Framework Core.
I'm using EF Core 2.0.
I think there has to be some way to call the stored procedure with using Entity and then map it to any class because select columns from multiple tables using join is a very basic requirement.
I tried the similar solution, but its giving compilation error.
'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:
The complete idea to get data from a stored procedure is as follows:
- You need to add an entity that has the same properties as the procedures select query has.
- Add the entity to your
DbContext
and Create a migration. Change the code in theUp()
andDown()
methods of the migration so that it creates the procedure in the database. - Now use the
FromSql()
method to get the data a normal entity data.
Here is some code that can guide you. Suppose you have these entities in your application domain:
- Student
- Parent
- SchoolClass
- Section
- Enrollment
Migrations up method
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)"
);
}
Migrations down method
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);
});
}
The fake entity: All properties in this entity are coming from the above-said entities. You can call it a fake entity.
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; }
}
Service layer to get data
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;
}
回答2:
That's how it works in 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
回答3:
Below steps works in DB Design first approach EF core 3.1.0
1) Suppose SP(sp_BulkSelect) return 3 column by multiple tables(col1 int,col2 string,col3 string)
2) create fake Class which will hold this Data, DataType should be same as per SP column
public Class1
{
public int col1 {get;set;}
public string col2 {get;set;}
public string col3 {get;set;}
}
3) Used Modelbuilder to map call and entity in MyDbContext class
modelBuilder.Entity<Class1>(entity =>
{
entity.HasNoKey();
entity.Property(e => e.col1);
entity.Property(e => e.col2);
entity.Property(e => e.col3);
});
4) create fake table in MyDbContext class
public virtual DbSet<Class1> Class_1 { get; set; }
5) Used MyDbContext class to call SP
var Class1data = MyDbContext.Class_1.FromSqlRaw("EXECUTE sp_BulkSelect {0}", id);
回答4:
1- Create a View from select part of sql query --without where condition--.
2- Then generate your model from database and Entity Framework generates a model for your view.
3- Now you can execute your stored procedure using generated model from view
dbContext.GeneratedView.FromSqlRaw("MyStoredProcedure {0}, {1} ", param1, param2)
来源:https://stackoverflow.com/questions/48594103/how-to-call-stored-procedure-with-join-on-multiple-tables-in-entity-framework-co