简体   繁体   中英

How to combine related data from two related models using EF?

Concept

I have two models that are related in one to many relationship. the first model basically stores patients information ( PatReg ). the second Model join partners together ( PatPar ). ie (FileId=1 Sam, FileId=2 Victoria, FileId=3 jessica, FileId=4 monica) info are all stored in PatReg . in order to say that Sam is a partner to Victoria and Jessica I join them in PatPar the following fashion:

  FileId= 1 FileId=2
  FileId= 1 FileId=3

the following states these two models.

public class PatReg
    {
        public Int64 FileId { get; set; }
        [Required, Display(Name = "First Name")]
        public string FName { get; set; }
        [Required, Display(Name = "Middle Name")]
        public string MName { get; set; }
        [Required, Display(Name = "Last Name")]
        public string LName { get; set; }
        [Display(Name = "Full Name"), NotMapped]
        public string fullname
        {
            get { return FName + " " + MName + " " + LName; }
        }
        [Required, Display(Name = "Date of Birth")]
        [DataType(DataType.Date)]
        public DateTime Dob { get; set; }
        public ICollection<PatPar> PatPar { get; set; }
    }

    public class PatPar

    {
        [Key]
        public Int64 RecId { get; set; }
        [Display(Name = "Patient File Id"), Required]
        public Int64 FileId { set; get; }
        [Display(Name = "Partner File Id"), Required]
        public Int64 ParFileId { set; get; }
        [Display(Name = "Start Date"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true), Required]
        public DateTime SDate { set; get; }
        [Display(Name = "End Date"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime? EDate { set; get; }
    }

Getting Related Data I m using an API controller in my project, the way I query my record is as follows,

    [HttpGet("{id}")]
    public async Task<IActionResult> GetPatReg([FromRoute] long id)
    {

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
         var patReg = await _context.PatReg.SingleOrDefaultAsync(m => m.FileId == id);     // here I get my Main Record
        var patRegPar = await _context.PatPar  // here I get related Records
            .Where(m => m.FileId == id)
            .ToListAsync();
        if (patReg == null)
        {
            return NotFound();
        }

        var DataRes = new {
            sdata = patReg                
        };

        return Ok(DataRes);
    }

Resulting JSON

{
    "sdata": {
        "fileId": 1708010001,
        "fName": "Json",
        "mName": "S",
        "lName": "Makenzi",
        "fullname": "Json S Abu Makenzi",
        "dob": "1984-04-26T00:00:00",
        "patPar": [{
            "recId": 2,
            "fileId": 1708010001,
            "parFileId": 1708010002,
            "sDate": "1999-12-12T00:00:00",
            "eDate": null,
        }, {
            "recId": 3,
            "fileId": 1708010001,
            "parFileId": 1708010003,
            "sDate": "1955-12-14T00:00:00",
            "eDate": null,
        }]
    }
}

I want my JSON output for patPar list to look like the following for each record,

"patPar": [{
                "recId": 2,
                "fullname": "Json S Abu Makenzi",
                "fileId": 1708010001,
                "parFileId": 1708010002,
                "sDate": "1999-12-12T00:00:00",
                "eDate": null,
            }, {
                "recId": 3,
                "fullname": "Sarah S Maz",
                "fileId": 1708010001,
                "parFileId": 1708010003,
                "sDate": "1955-12-14T00:00:00",
                "eDate": null,
            }]

The problem, the value fullname exists in the Main model PatReg but can be read via linking records instead of fileId to fileId , fileId to parFileId .

in another words, How can I add the fullname value in PatPar along with the other values even if it does not exist in the model? do I need a property for it?

Update for clarification

I m doing basically Three selections

first I select the record from PatReg using Id second PatPar has related data and are getting selected using the same Id

My issue is the fullname , I need to include it in PatPar but with a different key selection

Third Selection should goes as parFileId in PatPar to FileID in PatReg

the equivalent SQL is

first selection:

SELECT FileId, FName, LName, Dob FROM PatReg Where FileId=id 

Second and third selection:

SELECT        PatReg.FileId, PatReg.FName, PatReg.MName, PatReg.LName, PatPar.EDate, PatPar.ParFileId, PatPar.SDate, PatReg_1.FName AS PFName, PatReg_1.MName AS PMName, PatReg_1.LName AS PLName
FROM            PatPar INNER JOIN
                         PatReg ON PatPar.FileId = PatReg.FileId INNER JOIN
                         PatReg AS PatReg_1 ON PatPar.ParFileId = PatReg_1.FileId

Update

Desired JSON is

{
    "sdata": {
        "fileId": 1708010001,
        "fName": "**",
        "mName": "**",
        "lName": "**",
        "fullname": "***",
        "dob": "1984-04-26T00:00:00",
        "patPar": [{
                "recId": 2,
                "fullname": "*****",
                "fileId": 1708010001,
                "parFileId": 1708010002,
                "sDate": "1999-12-12T00:00:00",
                "eDate": null,
            }, {
                "recId": 3,
                "fullname": "*****",
                "fileId": 1708010001,
                "parFileId": 1708010003,
                "sDate": "1955-12-14T00:00:00",
                "eDate": null,
            }]
    }
}

It's a good practice to use DTOs(Data Transfer Objects) to transfer data between two endpoints. This way you can specify what properties you want to share, edit your model as you wish and you can get rid of those noisy properties that you wont use. In this case you can add a class like below :

public class PatParDto

{
    public int RecId { get; set; }
    public int FileId { get; set; }
    public int ParFileId { get; set; }
    public DateTime SDate { get; set; }
    public DateTime? EDate { get; set; }

    public string FullName {get; set;}
}

and then in your linq query you can select your PatPar as PatPatDto :

var patRegPar = await _context.PatPar
            .Where(m => m.FileId == id)
            .Select(m => new PatParDto {

             //Here you can set properties 
             FullName = patReg.fullname,
             RecId = m.RecId

            })
            .ToListAsync();

Instead of setting properties manually you can use a library like AutoMapper

my seggestion is to make an anonymous type from the virtual property when we use the related table:

var patRegPar = await _context.PatPar  // here I get related Records
        .Include(c=>c.PatReg)
        .Where(m => m.FileId == id)
        .Select(t=>new{ recId = t.recId , fullname = t.PatReg.fullname , ... })
        .ToListAsync();

Thanks Ege Tuncoz

I added the DTO ,

public class PatParDto

{
    public int RecId { get; set; }
    public int FileId { get; set; }
    public int ParFileId { get; set; }
    public DateTime SDate { get; set; }
    public DateTime? EDate { get; set; }

    public string FullName {get; set;}
}

then in my controller I ran a loop to add the desired value for each record in the DTO.

     [HttpGet("{id}")]
        public async Task<IActionResult> GetPatReg([FromRoute] long id)
        {

                    if (!ModelState.IsValid)
                    {
                        return BadRequest(ModelState);
                    }
                    var patReg = await _context.PatReg
                        .Where(m => m.FileId == id)
                        .ToListAsync();

                    var patpar = await _context.PatPar.Select(m => new PatParDto {
                        RecId = m.RecId,
                        FileId = m.FileId,
                        ParFileId = m.ParFileId,
                        SDate = m.SDate,
                        EDate = m.EDate,
                    }).ToListAsync();
                    for (int i = 0; i < patpar.Count; i++)

                    {
                        patpar[i].FullName = (from a in _context.PatReg
                        where (a.FileId == patpar[i].ParFileId)
                                              select new { a.fullname }
                                              ).Single().fullname;  

                                    or 

                        patpar[i].FullName = _context.PatReg.Where(a => 
                        a.FileId == patpar[i].ParFileId)
                                       .Select(t=>new {t.fullname })
                                       .Single().fullname;                                    

                    }


                    if (patReg == null)
                    {
                        return NotFound();
                    }

                    var DataRes = new {
                        sdata = patReg,
                        test= patpar
                    };

                    return Ok(DataRes);
         }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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