You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Entity Framework数据库优先模式下用FromSqlRaw引用Lookup表字段?

问题:FromSqlRaw调用存储过程无法加载关联的SlotDenom数据

我查了大量类似文章,都没讲清楚如何用DbSet的FromSqlRaw方法从多表(如Lookup表)返回关联数据。我的场景中SlotData与SlotDenom通过外键关联,已用NuGet生成数据模型。控制器的Details()方法能正常传递包含关联表的数据,但Index()方法用FromSqlRaw调用存储过程时,仅能获取SlotData的数据,SlotDenom的信息无法加载——尽管存储过程已包含联查逻辑。


相关代码片段

DbContext配置

modelBuilder.Entity<SlotDatum>(entity =>
{
    entity.HasKey(e => e.Id).HasName("PK_Slot_Data_1");
    entity.ToTable("Slot_Data");
    entity.HasIndex(e => e.Id, "IX_Slot_Data");

    entity.Property(e => e.CalDate)
        .HasColumnType("date")
        .HasColumnName("Cal_Date");
    entity.Property(e => e.Denom_Id).HasColumnName("Denom_ID");
    entity.Property(e => e.Drop).HasColumnType("money");
    entity.Property(e => e.Handle).HasColumnType("money");
    entity.Property(e => e.Win).HasColumnType("money");

    entity.Property(d => d.Denom_Id).HasColumnType("int");

    entity.HasOne(d => d.Denom).WithMany(p => p.SlotData)
        .HasForeignKey(d => d.Denom_Id)
        .HasConstraintName("FK_Slot_Data_SlotDenom");
});

SlotDatum模型

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace FlashWeb.Models.DB;

public partial class SlotDatum
{
    [Key]
    public int Id { get; set; }

    public DateOnly CalDate { get; set; }

    public int? Denom_Id { get; set; }
    public decimal? Handle { get; set; }
    public decimal? Drop { get; set; }
    public decimal? Win { get; set; }
    public int? SlotCount { get; set; }

    public virtual SlotDenom? Denom { get; set; }
}

SlotDenom模型

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace FlashWeb.Models.DB;

public partial class SlotDenom
{
    [Key]
    public int Denom_Id { get; set; }

    public decimal? Value { get; set; }
    public string? Description { get; set; }
    public string? Status { get; set; }
    public int? ReportOrder { get; set; }

    public virtual ICollection<SlotDatum> SlotData { get; } = new List<SlotDatum>();
}

存储过程SQL逻辑

SELECT
    Id,
    Cal_Date,
    SUM(Handle) AS 'Handle',
    SUM([Drop]) AS 'Drop', 
    SUM(Win) AS 'Win',
    SlotCount,
    denom.Denom_id,
    denom.[Description] AS 'Description',
    ReportOrder,
    [Status]
FROM
    Slot_Data sd
RIGHT OUTER JOIN
    SlotDenom denom ON denom.Denom_id = sd.Denom_id
WHERE
    denom.ReportOrder IS NOT NULL
    AND Cal_Date = @sBusDate
GROUP BY
    SlotCount, denom.Denom_id, [Description], ReportOrder, 
    [Status], Id, Cal_Date
ORDER BY 
    ReportOrder

原因分析

EF Core的FromSqlRaw默认仅加载主实体(此处为SlotDatum),不会自动加载关联的导航属性(Denom)——哪怕SQL查询包含关联表字段。而Details方法通过Include(s => s.Denom)显式告知EF要加载关联数据,因此能正常工作。


解决方案

方案1:结合Include与FromSqlRaw(EF Core 3.0+支持)

在FromSqlRaw后链式调用Include,让EF Core执行存储过程后,自动通过外键加载关联的SlotDenom数据。此方式要求存储过程返回结果包含外键字段Denom_Id。

修改Index方法代码:

try
{
    IQueryable<SlotDatum> query;
    if (slotDatum.CalDate != DateOnly.MinValue)
        query = _context.SlotData.FromSqlRaw("EXEC sp_GetSlotData {0}", slotDatum.CalDate).Include(s => s.Denom);
    else
        query = _context.SlotData.FromSqlRaw("EXEC sp_GetSlotsData {0}", DateOnly.FromDateTime(DateTime.Now)).Include(s => s.Denom);
    
    return View(await query.ToListAsync());
}
catch (Exception ex)
{
    Problem("Entity set " + ex.Message + " is null.");
    return View(await _context.SlotData.FromSqlRaw("sp_GetLastSlotsData").Include(s => s.Denom).ToListAsync());
}

注意:使用参数化查询{0}替代字符串拼接,避免SQL注入风险,这是最佳实践。

方案2:手动映射存储过程返回的关联字段

若存储过程已返回SlotDenom的字段(如Description、Status),可在获取数据后手动将这些字段值赋值给SlotDatum的Denom属性。此方式无需EF额外查询SlotDenom表。

步骤:

  1. 先给SlotDatum模型添加对应存储过程返回的SlotDenom字段属性:
    public string? DenomDescription { get; set; }
    public string? DenomStatus { get; set; }
    public int? DenomReportOrder { get; set; }
    
  2. 修改控制器代码:
    var slotDataList = await _context.SlotData.FromSqlRaw("EXEC sp_GetSlotData {0}", slotDatum.CalDate).ToListAsync();
    foreach(var item in slotDataList)
    {
        item.Denom = new SlotDenom
        {
            Denom_Id = item.Denom_Id,
            Description = item.DenomDescription,
            Status = item.DenomStatus,
            ReportOrder = item.DenomReportOrder
        };
    }
    return View(slotDataList);
    

方案3:用DTO接收完整结果

创建包含SlotData和SlotDenom所有所需字段的DTO类,直接用FromSqlRaw查询到DTO中,一次性获取所有数据后再映射到实体。

示例DTO:

public class SlotDataWithDenomDto
{
    public int Id { get; set; }
    public DateOnly CalDate { get; set; }
    public int? Denom_Id { get; set; }
    public decimal? Handle { get; set; }
    public decimal? Drop { get; set; }
    public decimal? Win { get; set; }
    public int? SlotCount { get; set; }
    public string? Description { get; set; }
    public string? Status { get; set; }
    public int? ReportOrder { get; set; }
}

控制器查询代码:

var dtoList = await _context.Set<SlotDataWithDenomDto>().FromSqlRaw("EXEC sp_GetSlotData {0}", slotDatum.CalDate).ToListAsync();
var slotDataList = dtoList.Select(dto => new SlotDatum
{
    Id = dto.Id,
    CalDate = dto.CalDate,
    Denom_Id = dto.Denom_Id,
    Handle = dto.Handle,
    Drop = dto.Drop,
    Win = dto.Win,
    SlotCount = dto.SlotCount,
    Denom = new SlotDenom
    {
        Denom_Id = dto.Denom_Id,
        Description = dto.Description,
        Status = dto.Status,
        ReportOrder = dto.ReportOrder
    }
}).ToList();
return View(slotDataList);

注意事项

  • 方案1会触发一次额外的批量查询(根据Denom_Id加载SlotDenom),数据量大时需考虑性能。
  • 方案2、3无需额外查询,但需手动处理字段映射,适合存储过程已返回所有所需数据的场景。
  • 始终使用参数化查询,避免SQL注入风险。

内容的提问来源于stack exchange,提问作者Doug

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 13:41:25