1

I have a table injected from the DB that I have set up for CRUD.

ID EID      Task         entrydate  Level 
1   1   Demographics    2017-02-23    2 
2   0   Demographics    2017-02-23    2 
3   1   Progress Notes  2017-03-06    2 
4   1   Demographics    2017-03-06    3 
5   1   Assessments     2017-03-06    3 
6   1   Assessments     2017-01-25    1

However, to display the data in a way that will make sense to the clients, I need to list the level data in columns by entrydate.

ID EId      Task         25 Jan 2017    23 Feb 2017 06 Mar 2017
1   1   Demographics        NULL            2          NULL
2   0   Demographics        NULL            2          NULL
3   1   Progress Notes      NULL          NULL          2
4   1   Demographics        NULL          NULL          3
5   1   Assessments         NULL          NULL          3
6   1   Assessments          1            NULL         NULL

I've been working on using LINQ in both the controller and in the view to create the correct table in the view.

Controller:

public IActionResult Index(int id)
    {
        HttpContext.Session.SetInt32("EmployeeID", id);
        var model = db.EmployeeTask
            .Where(h => h.EmployeeId == id)
            .OrderByDescending(h => h.Entrydate);

        return View(model);
    }

View page:

<table class="table">
<thead>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.KeyTask)
        </th>
@foreach (var group in Model.GroupBy(item => item.Entrydate))
{
        <th>
            @group.Key.Date
        </th>
}
        <th></th>
    </tr>
</thead>
<tbody>
@foreach (var group in Model.GroupBy(item => item.KeyTask)) {
    <tr>
        <td>
            @group.Key
        </td>
        @foreach (var item in group)
        {
            <td>
                @Html.DisplayFor(modelItem => item.Acceptable)
            </td>

        }
    </tr>
}
</tbody>
</table>

By my results, I am almost there.

View from MVC

I am missing something as the data from acceptance is not matching with the entrydate columns. Am I on the right path or have I gone as far as LINQ will take me and need to try another approach?

2
  • Your group by s are different for the column and the body - meaning you are not working on the same groupby results which should be by EntryDate Commented Mar 6, 2017 at 21:20
  • @JS_GodBlessAll: That lines up the numbers to the dates in rows, but the rows should be the tasks. The biggest issue here I think is that there are no real nulls or zeros to serve as place holders for tasks that have no acceptance for some dates. I tried building the rows one by one, but since the date columns are dynamic, I can't build a model that will accurately reflect what I need to capture. I also tried using a SQL method I found here, but I can't seem to make it work yet. Commented Mar 8, 2017 at 13:39

2 Answers 2

0

After much searching, reading, trial and error, I found a way to make this work.

First, I realized that the LINQ expressions were not the way I needed to go. I needed to build the rows individually. Since the data has dynamic columns, a DBSet didn't work. So I built a Pivot query in SQL;

DECLARE @cols NVARCHAR (MAX),
    @StartDate date = '03/01/2017',
    @EndDate date = '03/20/2017',
    @Id nvarchar(10) = 1

SELECT @cols = COALESCE (@cols + ',[' + CONVERT(NVARCHAR, entrydate, 106) + 
    ']', 
    '[' + CONVERT(NVARCHAR, entrydate, 106) + ']')
    FROM    (SELECT DISTINCT entrydate 
             FROM EmployeeTask 
             WHERE entrydate >= @StartDate AND entrydate <= @EndDate 
             AND EmployeeId = @Id) PV  
    ORDER BY entrydate DESC

DECLARE @query NVARCHAR(MAX)
SET @query = '          
          SELECT * FROM (SELECT KeyTask, acceptable, entrydate 
                         FROM EmployeeTask 
                         WHERE EmployeeId = ('+ @Id +')
                         Group by KeyTask, acceptable, entrydate) x
          PIVOT (SUM(acceptable) FOR entrydate IN (' + @cols + ')) p
             '     
EXEC SP_EXECUTESQL @query

That gives me the table I want to display, but how to get it to display in MVC 6?

First, I found this SO post and the answer by Petr Vobornik was just what I needed. I had to modify it and added it to my controller:

private static List<Dictionary<string, object>> LoadData(string sqlSelect, 
    params object[] sqlParameters)
    {
        var table = new List<Dictionary<string, object>>();
        using (var ctx = new DBContext())
        {
            ctx.Database.GetDbConnection();
            ctx.Database.OpenConnection();
            using (var cmd = ctx.Database.GetDbConnection().CreateCommand())
            {
                cmd.CommandText = sqlSelect;
                foreach (var param in sqlParameters)
                    cmd.Parameters.Add(param);
                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var row = new Dictionary<string, object>();
                        for (int i = 0; i < reader.FieldCount; i++)
                            row[reader.GetName(i)] = reader[i];
                        table.Add(row);
                    }

                    if (table == null)
                    {
                        var row = new Dictionary<string, object>();
                        for (int i = 0; i < 1; i++)
                            row[reader.GetName(i)] = "None";
                        table.Add(row);
                    }
                }
            }
        }
        return table;
    }

The if statement is used to add a blank row so that the list is still generated, but it contains nothing. That way, if the query returns nothing, an empty view can still be loaded (see the View below).

I added the SQL connection in my controller:

public IActionResult Index(int id, string datestart, string dateend)
    {
        HttpContext.Session.SetInt32("EmployeeID", id);

        if(datestart == null)
        {
            var setdate = DateTime.Now;
            datestart = setdate.AddMonths(-3).ToString(); 
        }

        if(dateend == null)
        {
            dateend = DateTime.Now.ToString();
        }

        var start = new SqlParameter("@StartDate", datestart);
        var end = new SqlParameter("@EndDate", dateend);
        var EmpId = new SqlParameter("@Id", id);
        var tasks = LoadData("EXECUTE dbo.TaskView @StartDate, @EndDate, 
                    @Id",start,end,EmpId);

        ViewBag.start = DateTime.Parse(datestart);
        ViewBag.end = DateTime.Parse(dateend);
        return View(tasks);
    }

Next, I modified the View:

@model List<Dictionary<string, object>>

@{
    ViewData["Title"] = "Employee Tasks";
}

<h2>Employee Tasks</h2>

<div class="panel panel-default">
<div class="panel-heading">Search by dates</div>
<div class="panel-body">
    <form asp-controller="EmployeeTasks" asp-action="Index">
        <label>Start Date</label>
        <input type="date" name="datestart" id="start" [email protected]   
            />
        <label>End Date</label>
        <input type="date" name="dateend" id="end" [email protected] />
        <input type="submit" value="Search" />
    </form>
   </div>
</div>

<p>
    <a asp-action="Create">Add New Employee Task</a>
</p>

@if (Model.FirstOrDefault() != null)
{
    <table class="table table-bordered">
    @foreach (var row in Model.FirstOrDefault())
    {
        <th>@row.Key</th>
    }
    @foreach (var row in Model)
    {
        <tr>
        @foreach (var column in row)
        {
            <td>@column.Value</td>  
        }
        </tr>
    }
</table>
}

<div>
    <a asp-controller="Employees" asp-action="Index">Back to Employee 
        Search</a>
</div>

And the final result!

enter image description here

If anyone has an easier method, please post it as an answer so I can try it out, but this works great in .NET Core 1.1.

Sign up to request clarification or add additional context in comments.

Comments

0

There is a much better way of handling this in Asp.Net core code first approach, for that you just need to create the corresponding view in Sql server like,

CREATE VIEW VIEWNAME AS SELECT ENTRYDATE,COL1,COL2 FROM TABLES GROUP BY ENTRYDATE

and then you simply need to create a ViewModel class by the same name as VIEWNAME in the web application , and then you need to call this line in your dbcontext class

public DbQuery<VIEWNAME> VIEWNAME { get; set; }

now instead of any joins, since you have already put all the joins the database view you created, you just need to call this line from your controller for fetching data.

var data=dbContext.VIEWNAME.tolist();

thats it....cheers

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.