简体   繁体   English

EFCore 2.1存储过程加载相关数据

[英]EFCore 2.1 Stored Procedure Loading Related Data

I'm having an issue with a problem that was supposed to be corrected in EFCore 2.1. 我遇到了应该在EFCore 2.1中更正的问题。 I know this was not capable in 2.0. 我知道这在2.0中不具备。 I have my project as a MVC Core 2.1 app with EFCore being at 2.1 also. 我将我的项目作为MVC Core 2.1应用程序使用,EFCore也为2.1。

I have searched the web for several different ways of wording the problem and I get pieces of information that do not complete the entire code process required to make the code work correctly. 我已经在网络上搜索了几种不同的措词方式,但是我得到的信息却不足以使代码正常工作所需的整个代码过程。

I have a model Mother and a mother has a sub model of Mailing Address and Physical Address. 我有一个母亲模型,母亲有一个邮寄地址和实际地址的子模型。 I am using a stored procedure with FromSql in order to give me the capability of having the multiple joins in my procedure. 我在FromSql中使用存储过程,以便使我能够在过程中进行多个联接。 The stored procedure is almost impossible to write in Linq. 该存储过程几乎不可能在Linq中编写。

I will include everything I have coded that meets the examples I have already read. 我将包括与已经阅读的示例相符合的所有编码内容。 PLEASE NOTE: I am using Areas for my code because I am writing a system with multiple areas of functionality. 请注意:我正在使用Areas作为代码,因为我正在编写具有多个功能区域的系统。

Models - Mother.cs 型号-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; }
    }
}

Interface - IbirthRepository.cs 介面-IbirthRepository.cs

public interface IBirthRepository
{
    IQueryable<Mother> Mothers { get; }
    IQueryable<MotherAddress> MotherAddresses { get; }
}

Repository - BirthRepository.cs 储存库-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;
}

Controller - GetMotherController.cs 控制器-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);
        }
    }
}

Main View - Mother.cshtml 主视图-Mother.cshtml

@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)*@

Partial View - MotherAddress.cshtml 部分视图-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">&nbsp;</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">&nbsp;</div>
    </div>
</div>

Models.Data - GetBirthData.cs 模型。数据-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 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 Stored Procedure - MAINTGetMotherAddresses 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

The Mother_Detail and Mother_Address tables have a lot more fields than I have listed here, so I'm not going to show the full table structure. Mother_Detail和Mother_Address表的字段比我在这里列出的要多得多,因此,我不会显示完整的表结构。 If you create the tables and add an addresses to the Mother_Address table, being Address_Type of 176, then you will be able to see the result set I am trying to work with currently. 如果创建表并将地址添加到Mother_Address表中,即Address_Type为176,那么您将能够看到我目前正在尝试使用的结果集。 My desire is to join the address a second time as seen in the stored procedure and return the mailing and physical in one resultset and have the system populate the MotherAddress model appropriately with both addresses. 我的愿望是第二次加入该地址,如在存储过程中所见,并在一个结果集中返回邮件和物理地址,并让系统用这两个地址适当地填充MotherAddress模型。

Please help! 请帮忙! This is very frustrating. 这非常令人沮丧。

Randy 兰迪

  1. your stored procedure will get all cross joined data for 1 mom, here is correct sql: 您的存储过程将获取1位妈妈的所有交叉连接数据,这是正确的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 
  2. The reason your SP cannot map result to your models is: your SP returns a view which does not exist in your context. SP无法将结果映射到模型的原因是:SP返回的视图在您的上下文中不存在。 Current EF & Core do not give a good way to execute Query SP, but for inserting,updating,deleting only. 当前的EF和Core并不是执行Query SP的好方法,而是仅用于插入,更新,删除。

     //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; } } 
  3. you SP is just a query, I provide you 2 options to replace it: 您的SP只是一个查询,我为您提供2个替换它的选项:

    • use compile query: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/compiled-queries-linq-to-entities 使用编译查询: https : //docs.microsoft.com/zh-cn/dotnet/framework/data/adonet/ef/language-reference/compiled-queries-linq-to-entities
    • use my simple sultion to remove SP, SP is not designed for query data, but for process data. 使用我的简单方法删除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"); }); } 

      then in your html: 然后在您的html中:

       @Html.RenderPartialAsync("Birth/_MotherAddress", Model.PhysicalAddress()) @*@Html.RenderPartialAsync("Birth/_MotherAddress", Model.WhateverAddress())*@ 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM