存储过程为列返回值,但 FromSqlRaw 为类属性返回 null
Posted
技术标签:
【中文标题】存储过程为列返回值,但 FromSqlRaw 为类属性返回 null【英文标题】:Stored Procedure returns value for a column but FromSqlRaw returns null for the class property 【发布时间】: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.
那么我该如何解决使属性可选的问题,同时它们应该能够映射到两个不同的存储过程返回的字段?如果有更好的方法,我愿意接受建议。提前致谢。
【问题讨论】:
这是关于这里回答的内容:Assigning NotMapped property value after "regular" (mapped) property value has been assigned 吗? 也许您可以拥有一个具有公共字段的类,并为第二个存储过程创建另一个具有继承性的类?或者它不是一种选择? 不相关,但为什么名为 'GetPolicyDetailsByPolicyId' 的 proc 需要 TOP 1 ? 您使用的是哪个 dbms? (上面的 SQL 是特定于产品的。) 请将sample code 设为最小且完整。大多数Policy
属性和数据库列都可以删除,并添加了_context
的声明。
【参考方案1】:
这个解决方案可以帮助你。
首先,我们需要使用继承。
其次,我们必须创建一个鉴别器来处理 SP 和类。
最后,我们在存储过程中设置判别器。
让我们看一些代码。
-
使用您的课程:
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;
-
设置鉴别器:
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));
// ...
-
在这一步中,我们必须将鉴别器(类名)传递给存储过程。
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
类同时具有PolicyType
和PolicyLineOfBusiness
属性。
在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】:对于单独的存储过程,您需要有单独的模型,因为两个存储过程中的列不同。
【讨论】:
以上是关于存储过程为列返回值,但 FromSqlRaw 为类属性返回 null的主要内容,如果未能解决你的问题,请参考以下文章
如何通过 FromSqlRaw 在 EF Core 3.0 中调用存储过程
如何在 c# 实体框架核心中使用 FromSqlRaw() 将 OUTPUT 参数发送到 MySql 存储过程
包含在FromSqlRaw和EF Core 3.1中的存储过程中
使用存储过程时出现 ASP.Net Core 3.1 MVC 错误
使用 EF 生成的 MySQL 在 LinQ 中为 FromSqlRaw 在从系统中选择变量时返回“SQL 语法错误”
实体框架“FromSqlRaw 或 FromSqlInterpolated 是用不可组合的 SQL 调用的,并且在它上面有一个查询。”返回对象的错误