Home > Back-end >  Map dapper result to nested object
Map dapper result to nested object

Time:05-25

I have a dapper query that returns a set of columns from the DB:

public async Task<WipCommentCalculation> GetExpenditureCommentsAndData(int projectId, string glPeriodName)
        {
            var output = await _db.LoadData<WipCommentCalculation, dynamic>("dbo.Sp_Get_Expenditure_Comments_Values",
                new { projectId, glPeriodName },
                ConnectionStringName,
                true);

            return output.FirstOrDefault()!;
        }

this is my LoadData method:

public async Task<List<T>> LoadData<T, U>(string sqlStatement, U parameters, string connectionStringName, bool isStoredProcedure = false)
        {
            Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;

            string connectionString = _config.GetConnectionString(connectionStringName)!;

            CommandType commandType = CommandType.Text;

            if (isStoredProcedure == true)
            {
                commandType = CommandType.StoredProcedure;
            }

            using (IDbConnection connection = new SqlConnection(connectionString))
            {
                var rows = await connection.QueryAsync<T>(sqlStatement, parameters, commandType: commandType);
                return rows.ToList();
            }
        }

my WipCommentCalculation class:

public class WipCommentCalculation
    {
        public Calculations Calculations{ get; set; }

        public CommentModel? Comments { get; set; }

        public int ProjectId{ get; init; }

        public string? ProjectNumber { get; init; }

        public string? GlPeriodName { get; init; }

        public DateTime? LastUpdatedDate { get; set; }

        public string? Status { get; set; }
    }

my issue is that the mapping works well for all not nested objects (Status, LastUpdatedDate, etc) but the Calculations and CommentModel are coming as empty.

The mapping inside the object is valid, they have the correct names, is there something I'm missing for getting the entire object here?

for example my query returns among other columns:

Palaborcomment, 
Panonlaborcomment, 
Pasubcontractorcomment, 
Pmlaborcomment, 
Pmnonlaborcomment, 
Pmsubcontractorcomment

and my object "CommentModel" has the following definition:

public class CommentModel
{
    [MaxLength (400, ErrorMessage = "Max length for this field is 400 chars")]
    public string? PALaborComment { get; set; }

    [MaxLength(400, ErrorMessage = "Max length for this field is 400 chars")]
    public string? PANonLaborComment { get; set; }

    [MaxLength(400, ErrorMessage = "Max length for this field is 400 chars")]
    public string? PASubContractorComment { get; set; } 

    [MaxLength(400, ErrorMessage = "Max length for this field is 400 chars")]
    public string? PMLaborComment { get; set; }

    [MaxLength(400, ErrorMessage = "Max length for this field is 400 chars")]
    public string? PMNonLaborComment { get; set; }

    [MaxLength(400, ErrorMessage = "Max length for this field is 400 chars")]
    public string? PMSubcontractorComment { get; set; }
}

the SQL storedprocedure basically is this (simplified):

Select Id, 
    Projectid, 
    Projectnumber, 
    Glperiodname, 
    Palaborcomment, 
    Panonlaborcomment, 
    Pasubcontractorcomment, 
    Pmlaborcomment, 
    Pmnonlaborcomment, 
    Pmsubcontractorcomment, 
    Billablelabor, 
    Billablenonlabor, 
    Billablesubcontractor, 
    Unbilledlabor, 
    Unbillednonlabor, 
    Unbilledsubcontractor, 
    Billingholdlabor, 
    Billingholdnonlabor, 
    Billingholdsubcontractor, 
    Last_Updated_Date, 
    Status
 From Table

and a result row:

 ---- ----------- --------------- -------------- ---------------- ------------------- ------------------------ ---------------- ------------------- ------------------------ --------------- ------------------ ----------------------- --------------- ------------------ ----------------------- ------------------ --------------------- -------------------------- ------------------------- -------- 
| Id | ProjectId | ProjectNumber | GlPeriodName | PALaborComment | PANonLaborComment | PASubContractorComment | PMLaborComment | PMNonLaborComment | PMSubcontractorComment | BillableLabor | BillableNonLabor | BillableSubcontractor | UnbilledLabor | UnbilledNonLabor | UnbilledSubcontractor | BillingHoldLabor | BillingHoldNonLabor | BillingHoldSubcontractor | Last_Updated_Date       | Status |
 ==== =========== =============== ============== ================ =================== ======================== ================ =================== ======================== =============== ================== ======================= =============== ================== ======================= ================== ===================== ========================== ========================= ======== 
| 1  | 1622554   | F5Y67802      | MAY-FY2022   | changed        | ewfew             | ewff                   | efewfwe        | ewfew             | NULL                   | 1198780.49    | 153208.27        | 230005.67             | 13141.34      | 394.20           | 0.00                  | 16.31            | 394.20              | 0.00                     | 2022-05-25 10:11:34.510 | NULL   |
 ---- ----------- --------------- -------------- ---------------- ------------------- ------------------------ ---------------- ------------------- ------------------------ --------------- ------------------ ----------------------- --------------- ------------------ ----------------------- ------------------ --------------------- -------------------------- ------------------------- -------- 

I want to put all the "commentModel" columns inside the commentModel object and all the calculations inside the calculations object

CodePudding user response:

I assume Dapper is having trouble mapping e.g. PMNonLaborComment to your table column name. I suggest adding this above in every model above each property that's returning nulls for you:

[Column(Name="column_name")]
public string? PropertyName {get; set;}

Can't claim anything yet as you've not shown your query result so that I can see what you're mapping with what.

CodePudding user response:

Dapper cannot automatically map one database row to multiple objects. You need to use multimapping and tell it where to split between the various types. I'm assuming you want it by ProjectId.

var projects = new Dictionary<int, WipCommentCalculation>();
var rows = await connection.QueryAsync<WipCommentCalculation, Calculations, CommentModel, WipCommentCalculation>(sqlStatement, parameters, commandType: commandType, splitOn:"Panonlaborcomment,Billablelabor" /* guessing with the last column name*/, (w, co, ca) => 
{
     WipCommentCalculation wip;
     if (!projects.TryGetValue(w.ProjectId, out wip))
     {
         projects.Add(w.ProjectId, wip = w);
     }
     wip.Comments.Add(co);
     wip.Calculations.Add(ca);
     return null;
});
// Use projects here and forget about the return of the query
  • Related