我遇到了一个本应在EFCore 2.1中更正的问题。我知道这在2.0中是不可行的。我的项目是MVC Core 2.1应用程序,EFCore也是2.1版本。
我在网上搜索了几种不同的措辞方式,我得到的信息并不能完成使代码正确工作所需的整个代码过程。
我有一个模型"母亲",一个母亲有一个子模型"邮寄地址"one_answers"物理地址"。我正在使用一个带有FromSql的存储过程,以便使我能够在过程中拥有多个联接。存储过程几乎不可能用Linq编写。
我将包括我所编码的符合我已经阅读的例子的所有内容请注意:我在代码中使用区域,因为我正在编写一个具有多个功能区域的系统
型号-Mother.cs
namespace Birth.Models
{
public class Mother
{
[Key]
public int MomId { get; set; }
public string MomFirst { get; set; }
public string MomLast { get; set; }
//[ForeignKey("MomId")]
public MotherAddress Physical { get; set; }
// public MotherAddress Mailing { get; set; }
}
//[Owned]
public class MotherAddress
{
public string pType { get; set; }
public string Street { get; set; }
public string PreDir { get; set; }
[Key]
public int MomId { get; set; }
public Mother Mother { get; set; }
}
}
接口-IbirthRepository.cs
public interface IBirthRepository
{
IQueryable<Mother> Mothers { get; }
IQueryable<MotherAddress> MotherAddresses { get; }
}
存储库-BirthRepository.cs
public class BirthRepository : IBirthRepository
{
private BirthDbContext context;
public BirthRepository(BirthDbContext ctx)
{
context = ctx;
}
public IQueryable<Mother> Mothers => context.Mothers;
public IQueryable<MotherAddress> MotherAddresses => context.MotherAddresses;
}
控制器-GetMotherController.cs
namespace VSMaintenance.Areas.Birth.Controllers
{
[Area("Birth")]
[Route("Birth/GetMother")]
public class GetMotherController : Controller
{
private BirthDbContext context;
private IBirthRepository repository;
public GetMotherController(BirthDbContext ctx, IBirthRepository repo)
{
repository = repo;
context = ctx;
}
public IActionResult Load()
{
return View("Index");
}
[HttpPost]
public async Task<ActionResult> Retrieve(Mother mother)
{
var gbd = new GetBirthData(repository, context);
var mom = new Mother();
mom = await gbd.GetMotherData(mother.MomId);
return View("Mother", mom);
}
}
}
主视图-Mother.chtml
@model Birth.Models.Mother
@{
ViewData["Title"] = "Mother";
}
<h2>Mother</h2>
@Html.DisplayNameFor(model => model.MomId)
@Html.DisplayFor(model => model.MomId)
<br />
@Html.DisplayNameFor(model => model.MomFirst)
@Html.DisplayFor(model => model.MomFirst)
<br />
@Html.DisplayNameFor(model => model.MomLast)
@Html.DisplayFor(model => model.MomLast)
<br /><br /><br />
@Html.RenderPartialAsync("Birth/_MotherAddress", Model.Physical)
@*@Html.RenderPartialAsync("Birth/_MotherAddress", Model.Mailing)*@
部分视图-MotherAddress.cshtml
@model Birth.Models.MotherAddress
<div class="container">
<div class="row">
<div class="col-sm-3">
@Html.DisplayNameFor(model => model.pType)
</div>
<div class="col-sm-3">
@Html.DisplayNameFor(model => model.Street)
</div>
<div class="col-sm-4">
@Html.DisplayNameFor(model => model.PreDir)
</div>
<div class="col-sm-2"> </div>
</div>
<div class="row">
<div class="col-sm-3">
@Html.TextBoxFor(model => model.pType, new { Style = "width:100%" })
</div>
<div class="col-sm-3">
@Html.TextBoxFor(model => model.Street, new { Style = "width:100%" })
</div>
<div class="col-sm-4">
@Html.TextBoxFor(model => model.PreDir, new { Style = "width:80%" })
</div>
<div class="col-sm-2"> </div>
</div>
</div>
模型。数据-GetBirthData.cs
namespace Birth.Models.Data
{
public class GetBirthData
{
private IBirthRepository repo;
pivate BirthDbContext _ctx;
public GetBirthData(IBirthRepository repository, BirthDbContext context)
{
repo = repository;
_ctx = context;
}
public async Task<Mother> GetMotherData(int MomId)
{
Mother list = new Mother();
try
{
string query = "exec MAINTGetMotherAddresses {0}";
list = await repo.Mothers
.FromSql(query, MomId)
.AsNoTracking()
.FirstOrDefaultAsync();
}
catch (Exception ex)
{
var msg = ex.Message;
}
return list;
}
}
}
DbContext-BirthDbContext.cs
namespace Birth.Models.Data
{
public class BirthDbContext : DbContext
{
public BirthDbContext(DbContextOptions<BirthDbContext> options)
:base(options) {
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Mother>(b =>
{
b.HasKey(e => e.MomId);
b.Property(e => e.MomId).ValueGeneratedNever();
//b.OwnsOne<MotherAddress>(e => e.Physical);
b.HasOne<MotherAddress>(e => e.Physical)
.WithOne(e => e.Mother)
.HasForeignKey<MotherAddress>(e => e.MomId);
b.ToTable("Table1");
});
}
public DbSet<Mother> Mothers { get; set; }
public DbSet<MotherAddress> MotherAddresses { get; set; }
}
}
SQLServer存储过程-MAINTGetMotherAddresses
ALTER PROCEDURE MAINTGetMotherAddresses
@MotherId NUMERIC(18,0)
AS
BEGIN
SELECT
CAST(md.MOTHER_DETAIL_ID AS INT) AS MomId,
md.FIRST_NAME AS MomFirst,
md.LAST_NAME AS MomLast,
'Physical' AS pType,
maP.STREET_ADDRESS AS Street,--PhysicalStreet,
maP.PRE_DIRECTION AS PreDir --PhysicalPreDir--,
--'Mailing' AS MailingType,
--maM.STREET_ADDRESS AS MailingStreet,
--maM.PRE_DIRECTION AS MailingPreDir
,CAST(@MotherId AS INT) AS PhysicalMomId
--,CAST(@MotherId AS INT) AS MailingMomId
--,CAST(@MotherId AS INT) AS MotherAddressMomId
FROM dbo.MOTHER_DETAIL md
JOIN dbo.MOTHER_ADDRESS maP
ON maP.MOTHER_DETAIL_ID = md.MOTHER_DETAIL_ID
JOIN dbo.MOTHER_ADDRESS maM
ON maM.MOTHER_DETAIL_ID = md.MOTHER_DETAIL_ID
WHERE md.MOTHER_DETAIL_ID = @MotherId
AND maP.ADDRESS_TYPE in (133, 176)
AND maM.ADDRESS_TYPE IN (132, 175)
END
Mother_Detail和Mother_Address表的字段比我在这里列出的要多得多,所以我不打算显示完整的表结构。如果您创建这些表并将地址添加到Mother_Address表中,即Address_Type为176,那么您将能够看到我当前尝试使用的结果集。我希望第二次加入地址,如存储过程中所示,并在一个结果集中返回邮件和物理地址,并让系统用这两个地址适当地填充MotherAddress模型。
请帮忙!这非常令人沮丧。
Randy
-
您的存储过程将获得1个mom的所有交叉连接数据,以下是正确的sql:
SELECT m.Whatever, physical.Whatever, mailing.Whatever FROM Mother m CROSS APPLY ( SELECT TOP 1 a.Name, a.Whatever FROM Address a WHERE a.momId= m.Id and a.pType in (1,2,3) ) physical CROSS APPLY ( SELECT TOP 1 b.Name, b.Whatever FROM Address b WHERE b.momId= m.Id and b.pType in (4,5,6) ) mailing
-
SP无法将结果映射到模型的原因是:SP返回的视图在您的上下文中不存在。当前EF&Core并不能很好地执行QuerySP,而只能用于插入、更新和删除。
//call extension like this: _context.QueryStoredProcedureAsync<YourViewModel>("YourProcedureName", ("a", 1), ("b", 2), ("c", 3)); //here is extension public static class ContextExtensions { public static async Task<List<T>> QueryStoredProcedureAsync<T>( this DbContext context, string storedProcName, params (string Key, object Value)[] args)//<==c# 7 new feature { using (var command = context.Database.GetDbConnection().CreateCommand()) { command.CommandText = storedProcName; command.CommandType = System.Data.CommandType.StoredProcedure; foreach (var arg in args) { var param = command.CreateParameter(); param.ParameterName = arg.Key; param.Value = arg.Value; command.Parameters.Add(param); } using (var reader = await command.ExecuteReaderAsync()) { return reader.MapToList<T>(); } } } private static List<T> MapToList<T>(this DbDataReader dr) { var result = new List<T>(); var props = typeof(T).GetRuntimeProperties(); var colMapping = dr.GetColumnSchema() .Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower())) .ToDictionary(key => key.ColumnName.ToLower()); if (dr.HasRows) { while (dr.Read()) { T obj = Activator.CreateInstance<T>(); foreach (var prop in props) { var val = dr.GetValue(colMapping[prop.Name.ToLower()].ColumnOrdinal.Value); prop.SetValue(obj, val == DBNull.Value ? null : val); } result.Add(obj); } } return result; } }
-
您的SP只是一个查询,我为您提供了两个替换选项:
- 使用编译查询:https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/compiled-queries-linq-to-entities
-
使用我的简单建议删除SP,SP不是为查询数据设计的,而是为过程数据设计的。
public partial class Mother { public Mother() { MotherAddresses = new HashSet<MotherAddress>(); } [Key, Column("ID")] public int Id { get; set; } [StringLength(50)] public string Name { get; set; } [InverseProperty("Mother")] public ICollection<MotherAddress> MotherAddresses { get; set; } } public partial class MotherAddress { [Key, Column(Order = 0)] public int MotherId { get; set; } [Key, Column(Order = 1)] public int AddressType { get; set; } [StringLength(50)] public string Address { get; set; } [ForeignKey("MotherId")] [InverseProperty("MotherAddress")] public Mother Mother { get; set; } } public enum AddressType { Physical, Mailing, } public static class MotherExtension { public static MotherAddress MailingAddress(this Mother mom) { return mom.Address(AddressType.Mailing); } public static MotherAddress PhysicalAddress(this Mother mom) { return mom.Address(AddressType.Physical); } public static MotherAddress Address(this Mother mom, AddressType addressType) { return mom.MotherAddresses.FirstOrDefault(x => x.AddressType == addressType); } } // here is in your DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<MotherAddress>(entity => { entity.HasKey(e => new { e.MotherId, e.AddressType }); entity.HasOne(d => d.Mother) .WithMany(p => p.MotherAddress) .HasForeignKey(d => d.MotherId) .OnDelete(DeleteBehavior.ClientSetNull) .HasConstraintName("FK_MotherAddress_Mother"); }); }
然后在你的html:
@Html.RenderPartialAsync("Birth/_MotherAddress", Model.PhysicalAddress()) @*@Html.RenderPartialAsync("Birth/_MotherAddress", Model.WhateverAddress())*@