【问题标题】:Stored Procedure returns value for a column but FromSqlRaw returns null for the class property存储过程为列返回值,但 FromSqlRaw 为类属性返回 null
【发布时间】:2021-12-23 07:25:14
【问题描述】:

我有一个由多种方法调用的类。这个类是:

public class Policy : EntityObject
{
    public Guid PolicyId { get; set; }
    public Guid CustomerId { get; set; }
    public string PolicyNumber { get; set; }
    [NotMapped]
    public string TrimmedPolicyNumber { get; set; }
    public DateTime? PolicyEffectiveDate { get; set; }
    public DateTime? PolicyExpirationDate { get; set; }
    [NotMapped]
    public string PolicyType { get; set; }
    public string InsuranceCompany { get; set; }
    public string WritingCompany { get; set; }
    [NotMapped]
    public string BillMethod_PaymentPlan { get; set; }
    [NotMapped]
    public decimal? FullTermPremium { get; set; }
    [NotMapped]
    public string AccountExecutive { get; set; }
    [NotMapped]
    public string AccountRepresentative { get; set; }

    [NotMapped]
    public string PolicyLineOfBusiness { get; set; }

    [NotMapped]
    public string Status { get; set; }

现在我有以下存储过程:

Create PROCEDURE [GetActivePoliciesByCustomer]
@CustomerId UNIQUEIDENTIFIER
AS
    SET NOCOUNT ON;
    Select
        c.CustId as CustomerId
        , p.PolId as PolicyId
        , p.PolNo as PolicyNumber
        , p.PolTypeLOB as [PolicyLineOfBusiness]
        , p.PolEffDate as PolicyEffectiveDate
        , p.PolExpDate as PolicyExpirationDate
        , cmp.Name as InsuranceCompany
        , wcmp.Name as WritingCompany
        , pr.Description as [Status]
    FROM
        Policies p
        (Further details have been omitted as it is not important)
GO

此存储过程返回上面列出的所有字段的数据。但是,当我拨打以下电话时:

public async Task<List<Policy>> GetActivePoliciesByCustomerId(Guide customerId)
{
    var activePolicies = await _context.Policy
        .FromSqlRaw<Policy>("EXEC [GetActivePoliciesByCustomer] @customerId={0}", customerId)
        .ToListAsync();

    return activepolicies;
}

在调试会话期间,我看到 Status 和 PolicyLineOfBusiness 设置为 null。我怀疑[NotMapped] 属性会阻止这些字段被映射,我已经验证了这一点。如果我删除 [NotMapped] 属性,我会看到填充了 Status 和 PolicyLineOfBusiness 字段。但是,这个类(Policy)被另一个调用使用:

public async Task<Policy> GetPolicyByPolicyId(Guid id)
{
    var policyDetails = await _context.Policy
        .FromSqlRaw<Policy>("EXEC [GetPolicyDetailsByPolicyId] @policyId={0}", id)
        .ToListAsync();

    return policyDetails.FirstOrDefault();
}

它正在调用的存储过程:

CREATE PROCEDURE [GetPolicyDetailsByPolicyId]
@PolicyId UNIQUEIDENTIFIER
AS
    SET NOCOUNT ON;

    SELECT TOP 1
        p.PolId as PolicyId
        , p.CustId as CustomerId
        , p.PolNo as PolicyNumber
        , p.ShortPolNo as TrimmedPolicyNumber
        , p.PolEffDate as PolicyEffectiveDate
        , p.PolExpDate as PolicyExpirationDate
        , p.PolTypeLOB as PolicyType
        , c.[Name] as InsuranceCompany
        , wc.[Name] as WritingCompany
        , p.BillMethod_PaymentPlan
        , p.FullTermPremium
        , e1.[LastName] as AccountExecutive
        , CONCAT(e.FirstName, ' ', e.LastName) as AccountRepresentative 
    From
        Policies p
        (Further details have been omitted as it is not important)

如果我从上述属性(PolicyLineOfBusiness 和 Status)中删除 [NotMapped] 属性并使用以前的方法(GetPolicyByPolicyId)调用上述存储过程,我会得到一个异常:

System.InvalidOperationException: The required column 'PolicyLineOfBusiness' was not present in the results of a 'FromSql' operation.

那么我该如何解决使属性可选的问题,同时它们应该能够映射到两个不同的存储过程返回的字段?如果有更好的方法,我愿意接受建议。提前致谢。

【问题讨论】:

  • 也许您可以拥有一个具有公共字段的类,并为第二个存储过程创建另一个具有继承性的类?或者它不是一种选择?
  • 不相关,但为什么名为 'GetPolicyDetailsByPolicyId' 的 proc 需要 TOP 1 ?
  • 您使用的是哪个 dbms? (上面的 SQL 是特定于产品的。)
  • 请将sample code 设为最小且完整。大多数Policy 属性和数据库列都可以删除,并添加了_context 的声明。

标签: c# sql entity-framework-core


【解决方案1】:

这个解决方案可以帮助你。

首先,我们需要使用继承。

其次,我们必须创建一个鉴别器来处理 SP 和类。

最后,我们在存储过程中设置判别器。

让我们看一些代码。

  1. 使用您的课程:
public class Policy : EntityObject
{
    public Guid PolicyId { get; set; }
    public Guid CustomerId { get; set; }
    public string PolicyNumber { get; set; }    
    public DateTime? PolicyEffectiveDate { get; set; }
    public DateTime? PolicyExpirationDate { get; set; }
    public string InsuranceCompany { get; set; }
    public string WritingCompany { get; set; }
}

public class CustomerPolicy : Policy
{
    public string Status { get; set; }
    public string PolicyLineOfBusiness { get; set; }        
}

public class DetailedPolicy : Policy
{    
    public string TrimmedPolicyNumber { get; set; }
    public string PolicyType { get; set; }
    public string BillMethod_PaymentPlan { get; set; }    
    public decimal? FullTermPremium { get; set; }    
    public string AccountExecutive { get; set; }
    public string AccountRepresentative { get; set; }
}
  1. 设置鉴别器:
public class Policy : EntityObject
{
    public Guid PolicyId { get; set; }
    // ...
    [NotMapped]  
    public string discriminator { get; set; } 
}

然后,流畅的 API 设置

// ...
public DbSet<Policy> Policy { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  // here you can use enum, byte, int, etc. instead of string.
  modelBuilder.Entity<Policy>()
    .HasDiscriminator<string>("discriminator")
    .HasValue<Policy>(nameof(Policy)) 
    .HasValue<CustomerPolicy>(nameof(CustomerPolicy))
    .HasValue<DetailedPolicy>(nameof(DetailedPolicy));
  // ...
}
  1. 在这一步中,我们必须将鉴别器(类名)传递给存储过程。
CREATE PROCEDURE [GetPolicyDetailsByPolicyId]
@PolicyId UNIQUEIDENTIFIER
AS
    SET NOCOUNT ON;

    SELECT TOP 1
        p.PolId as PolicyId
        , p.CustId as CustomerId
        , p.PolNo as PolicyNumber
        , p.ShortPolNo as TrimmedPolicyNumber
        , p.PolEffDate as PolicyEffectiveDate
        , p.PolExpDate as PolicyExpirationDate
        , p.PolTypeLOB as PolicyType
        , c.[Name] as InsuranceCompany
        , wc.[Name] as WritingCompany
        , p.BillMethod_PaymentPlan
        , p.FullTermPremium
        , e1.[LastName] as AccountExecutive
        , CONCAT(e.FirstName, ' ', e.LastName) as AccountRepresentative 

        // Add the discriminator here
        ,'DetailedPolicy' discriminator

    From
        Policies p
        (Further details have been omitted as it is not important)

在另一个 sp 中重复。

Create PROCEDURE [GetActivePoliciesByCustomer]
@CustomerId UNIQUEIDENTIFIER
AS
    SET NOCOUNT ON;
    Select
        c.CustId as CustomerId
        , p.PolId as PolicyId
        , p.PolNo as PolicyNumber
        , p.PolTypeLOB as [PolicyLineOfBusiness]
        , p.PolEffDate as PolicyEffectiveDate
        , p.PolExpDate as PolicyExpirationDate
        , cmp.Name as InsuranceCompany
        , wcmp.Name as WritingCompany
        , pr.Description as [Status]

        // Add the discriminator here
        'CustomerPolicy' discriminator
         
    FROM
        Policies p
        (Further details have been omitted as it is not important)
GO

EF 上下文调用:

using Microsoft.EntityFrameworkCore;
using Microsoft.Data.SqlClient;

// ...

var activePolicies = await context.Policy
    .FromSqlRaw("exec [GetActivePoliciesByCustomer] @customerId", 
        new SqlParameter("customerId", customerId))
    .AsAsyncEnumerable();

var policyDetails = await context.Policy
    .FromSqlRaw("exec [GetPolicyDetailsByPolicyId] @policyId", 
        new SqlParameter("policyId", id))
    .AsAsyncEnumerable();

注意:您可能需要使用 AsAsyncEnumerable 或 IQueryable 来正确转换您的类。

更新:

注意2:如果编译器出现问题,你必须用其他派生类的null填充所有字段。

例如:

CREATE PROCEDURE [GetPolicyDetailsByPolicyId]
// ...
        ,'DetailedPolicy' discriminator
        ,null Status
        ,null PolicyLineOfBusiness 
// ...

以下是 .NET Core 5 和 EF Core 5 的示例:

public class Policy
{
  public int field1 { get; set; }    
  [NotMapped]
  public string discriminator { get; set; } 
}

public class Policy2 : Policy
{
  public int? field2 { get; set; }
}

public class Policy3 : Policy
{
  public int? field3 { get; set; }
}

流畅的 API:

// ...
public DbSet<Policy> Policies { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.Entity<Policy>().HasNoKey();
  
  modelBuilder.Entity<Policy>()
    .HasDiscriminator<string>("discriminator")
    .HasValue<Policy>(nameof(Policy))
    .HasValue<Policy2>(nameof(Policy2))
    .HasValue<Policy3>(nameof(Policy3));

  //...
}

自定义存储过程:

create procedure GenPolicyQuery
    @id int,
    @source varchar(10)
as

declare @field1 int, @field2 int, @field3 int

if @source =  'Policy'
    set @field1 = @id
else if @source = 'Policy2'
    select @field1 = 10, @field2 = @id
else if @source = 'Policy3'
    select @field1 = 10, @field3 = @id

select @field1 field1, @field2 field2, @field3 field3, @source discriminator

go

结果:

using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Data.SqlClient;
using Newtonsoft.Json;
// ...

var policy1 = db.Policies
  .FromSqlRaw("exec GenPolicyQuery @id, @source", 
    new SqlParameter("id", 10), 
    new SqlParameter("source", "Policy"))        
  .AsEnumerable();

var policy2 = db.Policies
  .FromSqlRaw("exec GenPolicyQuery @id, @source", 
    new SqlParameter("id", 20), 
    new SqlParameter("source", "Policy2"))
  .AsEnumerable();

var policy3 = db.Policies
  .FromSqlRaw("exec GenPolicyQuery @id, @source", 
    new SqlParameter("id", 30), 
    new SqlParameter("source", "Policy3"))
  .AsEnumerable();
    
Console.WriteLine(JsonConvert.SerializeObject(policy1));
Console.WriteLine(JsonConvert.SerializeObject(policy2));
Console.WriteLine(JsonConvert.SerializeObject(policy3))

// Result
/*
  [{"field1":10,"discriminator":"Policy"}]
  [{"field2":20,"field1":10,"discriminator":"Policy2"}]
  [{"field3":30,"field1":10,"discriminator":"Policy3"}]
*/

【讨论】:

    【解决方案2】:

    您的Policies 类同时具有PolicyTypePolicyLineOfBusiness 属性。

    GetActivePoliciesByCustomer 过程中,列p.PolTypeLOB 别名为PolicyLineOfBusiness,但在GetPolicyDetailsByPolicyId 过程中,列p.PolTypeLOB 别名为PolicyType

    所以,两个属性的 origin 列是相同的,我不明白原因, 但为避免第二个过程出现异常,您可以对其进行修改,同时添加PolicyLineOfBusiness 所需的输出:

    CREATE PROCEDURE [GetPolicyDetailsByPolicyId]
    @PolicyId UNIQUEIDENTIFIER
    AS
        SET NOCOUNT ON;
    
        SELECT TOP 1
            p.PolId as PolicyId
            , p.CustId as CustomerId
            , p.PolNo as PolicyNumber
            , p.ShortPolNo as TrimmedPolicyNumber
            , p.PolEffDate as PolicyEffectiveDate
            , p.PolExpDate as PolicyExpirationDate
            , p.PolTypeLOB as PolicyType
            , p.PolTypeLOB as PolicyLineOfBusiness /* *** ADD THIS LINE  *** */
            , c.[Name] as InsuranceCompany
            , wc.[Name] as WritingCompany
            , p.BillMethod_PaymentPlan
            , p.FullTermPremium
            , e1.[LastName] as AccountExecutive
            , CONCAT(e.FirstName, ' ', e.LastName) as AccountRepresentative 
        From
            Policies p
            (Further details have been omitted as it is not important)
    

    【讨论】:

      【解决方案3】:

      由于 PolicyLineOfBusiness 属性存在于您的域模型中。因此,如果您删除属性(未映射),两个 SP 都必须返回该属性值。但是通过删除该属性,您的第二个 SP 将给出异常,第一个 SP 将获取数据。所以这意味着第二个 SP 不想返回 PolicyLineOfBusiness 属性。

      不幸的是,您无法通过删除域模型属性中的属性 (NotMapped) 来强制编写 PolicyLineOfBusiness 属性。因此,如果要保持域模型相同,则必须从第二个 SP 返回属性。

      CREATE PROCEDURE [GetPolicyDetailsByPolicyId]
      @PolicyId UNIQUEIDENTIFIER
      AS
          SET NOCOUNT ON;
      
          SELECT TOP 1
              p.PolId as PolicyId
              , p.CustId as CustomerId
              , p.PolNo as PolicyNumber
              , p.ShortPolNo as TrimmedPolicyNumber
              , p.PolEffDate as PolicyEffectiveDate
              , p.PolExpDate as PolicyExpirationDate
              , p.PolTypeLOB as PolicyType
              , c.[Name] as InsuranceCompany
              , wc.[Name] as WritingCompany
              , p.BillMethod_PaymentPlan
              , p.FullTermPremium
              , "" As PolicyLineOfBusiness --Have to add this as the domain model contain this property
              , e1.[LastName] as AccountExecutive
              , CONCAT(e.FirstName, ' ', e.LastName) as AccountRepresentative 
          From
              Policies p
              (Further details have been omitted as it is not important)
      

      如果您不想这样做,请为 2 个 SP 创建 2 个不同的模型。

      【讨论】:

        【解决方案4】:

        对于单独的存储过程,您需要有单独的模型,因为两个存储过程中的列不同。

        【讨论】:

          猜你喜欢
          • 2014-08-04
          • 1970-01-01
          • 2021-06-11
          • 2023-03-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-03
          • 1970-01-01
          相关资源
          最近更新 更多