jQuery datatable in MVC (server-side) - c#

https://datatables.net/usage/server-side
On the page above, there are parameters that you need to receive to make server-side datatable work.
I have a helper class
public class TableParameter
{
public string sEcho { get; set; }
public int iDisplayStart { get; set; }
public int iDisplayLength { get; set; }
public int iSortingCols { get; set; }
}
But in order to sort columns I need to receive
string sSortDir_(int)
How do I do that? I know (int) represents column ID that needs to be sorted, but I just can't catch it in my controller.

The datatable will post one or more sSortDir_x parameters to your controller, depending on how many columns are sorted on simultaneously in the table.
The specific columns that the table is sorted by are sent in the iSortCol_ parameters (again, one or more).
public class TableParameter
{
public string sEcho { get; set; }
public int iDisplayStart { get; set; }
public int iDisplayLength { get; set; }
public int iSortingCols { get; set; }
public int iSortCol_0 { get; set; } // the first (and usually only) column to be sorted by
public string sSortDir_0 { get; set; } // the direction of the first column sort (asc/desc)
public int iSortCol_1 { get; set; } // the second column to be sorted by
public string sSortDir_1 { get; set; } // the direction of the second column sort
// etc
}

For receiveing a column name in action, that is used for one-column sorting:
public ActionResult SomeMethod(FormCollection coll)
{
var sortingColumnNumber = Convert.ToInt32(coll["iSortCol_0"]);
var sortingColumnName = coll[string.Format("mDataProp_{0}", sortingColumnNumber)];
var propertyInfo = typeof(SomeObject).GetProperty(sortingColumnName);
//..get List<SomeObject> sortedObjects
sortedObjects = sortedObjects.OrderBy(x => propertyInfo.GetValue(x, null)).ToList();
//...
}

Related

Filtering on the Collection Navigation property

I would like to filter my 'TranslationSet' entities, based on their 'Translations' Collection Navigation Property.
E.g.
If a 'Translation' has a 'LanguageId' of 5 (Italian), then the 'TranslationSet' that contains this 'Translation' should be removed from the result.
Here are my Entity classes:
public class Language
{
public int LanguageId { get; set; }
public string NationalLanguage { get; set; }
//Make table multi tenanted.
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public List<Translation> Translation { get; set; } = new List<Translation>();
}
public class Translation
{
public int TranslationId { get; set; }
public string TranslatedText { get; set; }
public int LanguageId { get; set; }
public Language Language { get; set; }
//Make table multi tenanted.
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public int TranslationSetId { get; set; }
public TranslationSet TranslationSet {get; set;}
}
public class TranslationSet
{
public int TranslationSetId { get; set; }
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public IEnumerable<Translation> Translations { get; set; }
}
Here is my attempt
From the image you can see that the query fails because a Translation exists with LanguageId of 5.
I have tried many many attempts to resolve this but I can't even get close the LINQ which returns my query correctly.
Please let me know if any further clarification is needed and thanks in advance to anybody who offers help.
My rule of the thumb that nearly always work is: start by querying the entities you want. That will prevent duplicates as you see in your query result. Then add predicates to filter the entities, using navigation properties. That will be:
var sets = TranslationSets // start the query here
.Where(ts => ts.Translations.All(t => t.LanguageId != 5)); // Filter
Or if you like this better:
var sets = TranslationSets // start the query here
.Where(ts => !ts.Translations.Any(t => t.LanguageId == 5)); // Filter
EF will translate both queries as WHERE NOT EXISTS.

How can I handle mismatch between schema and model after transformation?

Exploring ML.Net and I want to predict employee turnover. I have a dataset available, with a mix between numeric and string values.
This is all just purely exploration in my attempt in getting to know ML.net. So my approach was to, simply step by step explore the options, so I really would understand each and every step as good as possible.
Load the data
Prepare the dataset and do a categorical transform on the string features
Display the dataset after applying the transformations
Then split the dataset into a train and test dataset
Train the model with a classification algorithm
Evaluate against the test dataset
Output the feature weights of the model
Do some cool stuff with it
The model is as follows and based on the open source attrition dataset from IBM. https://www.kaggle.com/pavansubhasht/ibm-hr-analytics-attrition-dataset
The model:
public class Employee
{
[LoadColumn(0)]
public int Age { get; set; }
[LoadColumn(1)]
//[ColumnName("Label")]
public string Attrition { get; set; }
[LoadColumn(2)]
public string BusinessTravel { get; set; }
[LoadColumn(3)]
public int DailyRate { get; set; }
[LoadColumn(4)]
public string Department { get; set; }
[LoadColumn(5)]
public int DistanceFromHome { get; set; }
[LoadColumn(6)]
public int Education { get; set; }
[LoadColumn(7)]
public string EducationField { get; set; }
[LoadColumn(8)]
public int EmployeeCount { get; set; }
[LoadColumn(9)]
public int EmployeeNumber { get; set; }
[LoadColumn(10)]
public int EnvironmentSatisfaction { get; set; }
[LoadColumn(11)]
public string Gender { get; set; }
[LoadColumn(12)]
public int HourlyRate { get; set; }
[LoadColumn(13)]
public int JobInvolvement { get; set; }
[LoadColumn(14)]
public int JobLevel { get; set; }
[LoadColumn(15)]
public string JobRole { get; set; }
[LoadColumn(16)]
public int JobSatisfaction { get; set; }
[LoadColumn(17)]
public string MaritalStatus { get; set; }
[LoadColumn(18)]
public int MonthlyIncome { get; set; }
[LoadColumn(19)]
public int MonthlyRate { get; set; }
[LoadColumn(20)]
public int NumCompaniesWorked { get; set; }
[LoadColumn(21)]
public string Over18 { get; set; }
[LoadColumn(22)]
public string OverTime { get; set; }
[LoadColumn(23)]
public int PercentSalaryHike { get; set; }
[LoadColumn(24)]
public int PerformanceRating{ get; set; }
[LoadColumn(25)]
public int RelationshipSatisfaction{ get; set; }
[LoadColumn(26)]
public int StandardHours{ get; set; }
[LoadColumn(27)]
public int StockOptionLevel{ get; set; }
[LoadColumn(28)]
public int TotalWorkingYears{ get; set; }
[LoadColumn(29)]
public int TrainingTimesLastYear{ get; set; }
[LoadColumn(30)]
public int WorkLifeBalance{ get; set; }
[LoadColumn(31)]
public int YearsAtCompany{ get; set; }
[LoadColumn(32)]
public int YearsInCurrentRole{ get; set; }
[LoadColumn(33)]
public int YearsSinceLastPromotion{ get; set; }
[LoadColumn(34)]
public int YearsWithCurrManager { get; set; }
}
The string properties are then transformed (as explained here https://learn.microsoft.com/en-us/dotnet/machine-learning/how-to-guides/prepare-data-ml-net#work-with-categorical-data)
var categoricalEstimator = mlContext.Transforms.Categorical.OneHotEncoding("Attrition")
.Append(mlContext.Transforms.Categorical.OneHotEncoding("BusinessTravel"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("EducationField"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("Gender"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("JobRole"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("MaritalStatus"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("Over18"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("OverTime"));
ITransformer categoricalTransformer = categoricalEstimator.Fit(dataView);
IDataView transformedData = categoricalTransformer.Transform(dataView);
Now I want to inspect what has changed (https://learn.microsoft.com/en-us/dotnet/machine-learning/how-to-guides/inspect-intermediate-data-ml-net#convert-idataview-to-ienumerable). The challenge I have now is that after applying a transformation on the string properties, the schema has changed and now contains the expected vectors.
So the following is happening. The Employee model schema does not match the schema from the transformedData object anymore and tries to fit a Vector property into a String property and throws the following error "Can't bind the IDataView column 'Attrition' of type 'Vector' to field or property 'Attrition' of type 'System.String'."
IEnumerable<Employee> employeeDataEnumerable =
mlContext.Data.CreateEnumerable<Employee>(transformedData, reuseRowObject: true);
The CreateEnumerable also has a SchemaDefinition argument, so my first guess was to extract the Schema from the transformedData, and supply that to the CreateEnumerable. However it expects a Microsoft.ML.DataViewSchema and the schema produced by the transform is a Microsoft.ML.Data.SchemaDefinition. So that didn't work either.
I hope someone can advice me on this. Should I do something different?
Full Controller Action:
public ActionResult Turnover()
{
MLContext mlContext = new MLContext();
var _appPath = AppDomain.CurrentDomain.BaseDirectory;
var _dataPath = Path.Combine(_appPath, "Datasets", "WA_Fn-UseC_-HR-Employee-Attrition.csv");
// Load data from file
IDataView dataView = mlContext.Data.LoadFromTextFile<Employee>(_dataPath, hasHeader: true);
// 0. Get the column name of input features.
string[] featureColumnNames =
dataView.Schema
.Select(column => column.Name)
.Where(columnName => columnName != "Label")
.ToArray();
// Define categorical transform estimator
var categoricalEstimator = mlContext.Transforms.Categorical.OneHotEncoding("Attrition")
.Append(mlContext.Transforms.Categorical.OneHotEncoding("BusinessTravel"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("EducationField"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("Gender"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("JobRole"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("MaritalStatus"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("Over18"))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("OverTime"));
ITransformer categoricalTransformer = categoricalEstimator.Fit(dataView);
IDataView transformedData = categoricalTransformer.Transform(dataView);
// Inspect (fails because Employee (35 cols) cannot be mapped to new schema (52 cols)
IEnumerable<Employee> employeeDataEnumerable =
mlContext.Data.CreateEnumerable<Employee>(transformedData, reuseRowObject: true, schemaDefinition : transformedData.Schema);
// split the transformed dataset into training and a testing datasets
DataOperationsCatalog.TrainTestData dataSplit = mlContext.Data.TrainTestSplit(transformedData, testFraction: 0.2);
IDataView trainData = dataSplit.TrainSet;
IDataView testData = dataSplit.TestSet;
return View();
}
I ran into this recently and as a quick workaround, I simply created a new class that matches the transformed data schema. For example, you can create EmoloyeeTransformed class with the correct properties (i.e. vector instead of string) and use that as follows:
CreateEnumerable<EmployeeTransformed>
This isnt optimal if you are going to create various transformed schemas, but it works.
Hope that helps.
For debugging purposes you can also call transformedData.Preview() and look at the data and the resulting Schema.

How do I iteratively assign to a nested list within an object in C#?

I've been asked to replicate a complex xml structure we use within our internal systems, against data retrieved from another system. Unfortunately the xml structure is improvised and we have no specification for it.
I've been mapping out it's structure in C# classes so that I can assign those properties with values from the database and ultimately serialise it as xml.
I've hit a bit of a road block in terms of iteratively adding new list items to a nested list within an object that's already being intialised as a list and being looped through. To make matters more complicated I need to use a value from the iteration to filter down the dataset being used to instantiate the second loop round.
Sorry for the poor explanation - open to rewording... hopefully however the example I've written will demonstrate what I'm trying to do:
using System;
using System.Collections.Generic;
using System.Data;
public class TransactionModel
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime SysDate { get; set; }
public List<TransactionItemModel> Trade { get; set; } = new List<TransactionItemModel>();
}
public class TransactionItemModel
{
public int ItemId { get; set; }
public int TransactionId { get; set; }
public string ItemDescription { get; set; }
public decimal ItemNetAmount { get; set; }
}
public class Program
{
public void Main()
{
DataTable tranResultSet = MethodToReturnResultsFromTranQuery();
DataTable itemResultSet = MethodToReturnResultsFromItemQuery();
var transactions = new List<TransactionModel>();
foreach (DataRow tran in tranResultSet.Rows)
{
transactions.Add(
new TransactionModel() {
Id = (dynamic)tran["Id"],
Description = (dynamic)tran["Description"],
SysDate = (dynamic)tran["SysDate"],
//Trade = <Stuck Here>
// Need to iterate through itemResultSet, adding to TransactionModel.Trade
// where item["TransactionId"] = tran["Id"]
}
);
}
}
}
This approach doesn't set the Trade collection initially, but populates it once you go through the Items. There's likely a lot of optimization that can be added, but this might get you started.
public class TransactionModel
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime? SysDate { get; set; }
public List<TransactionItemModel> Trade { get; set; }
public TransactionModel(DataRow row)
{
if(row == null)
throw new ArgumentNullException(nameof(row));
Id = row.Field<int>("Id");
Description = row.Field<string>("Description");
SysDate = row.Field<DateTime?>("SysDate");
Trade = new List<TransactionItemModel>();
}
}
public class TransactionItemModel
{
public int ItemId { get; set; }
public int TransactionId { get; set; }
public string ItemDescription { get; set; }
public decimal? ItemNetAmount { get; set; }
public TransactionItemModel(DataRow row)
{
if(row == null)
throw new ArgumentNullException(nameof(row));
ItemId = row.Field<int>("Id");
TransactionId = row.Field<int>("TransactionId");
ItemDescription = row.Field<string>("ItemDescription");
ItemNetAmount = row.Field<decimal?>("ItemNetAmount");
}
}
public static class Program
{
private static void Main()
{
DataTable tranResultSet = MethodToReturnResultsFromTranQuery();
DataTable itemResultSet = MethodToReturnResultsFromItemQuery();
var transactions = tranResultSet.AsEnumerable()
.Select(r => new TransactionModel(r));
foreach(TransactionModel transaction in transactions)
{
var items = itemResultSet.AsEnumerable()
.Where(r => r.Field<int>("TransactionId") == transaction.Id)
.Select(r => new TransactionItemModel(r));
transaction.Trade.AddRange(items);
}
}
}
It's likely going to be ideal to query your ItemResultSet based on the current TransactionId instead of grabbing them all up front. You could implement a DataReader, or use Dapper.

ASP.Net MVC forms/model binding and delete records?

I have a model that I'm loading into a table within a form. The records are retrieved from an Oracle DB using EF6 and loaded into the model.
I also want the user to be able to select records to delete from the database via a checkbox in each row in the form.
The function to retrieve the Attendees:
public List<WebinarAttendeesList> getAttendees(string webinarKey)
{
string connectionString = "Password=password;User Id=user;Data Source=Oracle";
List<WebinarAttendeesList> r = null;
using (webinarAttendeesListDbContext context = new webinarAttendeesListDbContext(connectionString))
{
var result = from w in context.WebinarAttendeesList
where w.webinarKey == webinarKey
orderby w.FirstPollCount, w.SecondPollCount
select w;
r = result.ToList();
}
return r;
}
Here is the model:
[Table("WEBINARATTENDEESLIST")]
public class WebinarAttendeesList {
[Key, Column("WAL_ID")]
public int wa_id { get; set; }
[Column("WAL_CLI_RID")]
public int ParticipantID { get; set; }
[Column("WAL_FULLNAME")]
public string FullName { get; set; }
[Column("WAL_EMAIL")]
public string Email { get; set; }
[Column("WAL_JOINTIME")]
public string JoinTime { get; set; }
[Column("WAL_TIMEINSESSION")]
public string TimeInSession { get; set; }
[Column("WAL_LEAVETIME")]
public string LeaveTime { get; set; }
[Column("WAL_FIRSTPOLLCOUNT")]
public int FirstPollCount { get; set; }
[Column("WAL_SECONDPOLLCOUNT")]
public int SecondPollCount { get; set; }
[Column("WAL_ATTENDEDWEBINAR")]
public int AttendedWebinar { get; set; }
[Column("WAL_MAKEUP")]
public int Makeup { get; set; }
[Column("WAL_COMMENTS")]
public string Comments { get; set; }
[Column("WAL_REGISTRANTKEY")]
public string RegistrantKey { get; set; }
[Column("WAL_WEBINARKEY")]
public string webinarKey { get; set; }
}
When the form is submitted, I am passing the model to a function to store the records in EF6.
public ActionResult PostAttendees(ICollection<WebinarAttendeesList> attendees)
{
foreach (WebinarAttendeesList attendee in attendees)
{
UpdateAttendee(attendee);
}
}
How would I edit the model to allow this delete the records that are selected and update the ones that don't have the checkbox selected?
If I put an int delete property on the model that has no Column attribute I get this exception:
ORA-00904: "Extent1"."delete": invalid identifier
I found this tutorial but I'm NOT using any helpers in the creation of the form and do not have any ViewModels and it also doesn't explain how to handle doing different things to the different records based on the checkbox: http://johnatten.com/2014/01/05/asp-net-mvc-display-an-html-table-with-checkboxes-to-select-row-items/
Is there a better way to do this?
Yes. All models properties in EF are suppose to be column. You should use NotMapped attribute if you don't want property to be treated as a 'column' in database.

Web Api Server Side Processing and DataTables parameters

I'm trying to create an object that represents the values that datatables supplies to my web api call, which I'll then route to another api that actually returns the values (to separate the datatables nonsense from the api interface).
After researching a bit on the datatables wiki I ended up with the following objects defined:
public class DataTableParameters
{
public int draw { get; set; }
public int start { get; set; }
public int length { get; set; }
public order[] order { get; set; }
public column[] columns { get; set; }
}
public class order
{
public int column { get; set; }
public string dir { get; set; }
}
public class column
{
public string data { get; set; }
public string name { get; set; }
public bool searchable { get; set; }
public bool orderable { get; set; }
public search search { get; set; }
}
public class search
{
public string value { get; set; }
public bool regex { get; set; }
}
However, when I try to use them as arguments to the DataTables api controller, it comes out null:
public DataTableResult Get(DataTableParameters parameters) //parameters is null!
{
return new DataTableResult();
}
As far as I understood it, model binding should be reading the result and applying it to my object. This is an example call to the api from the front end:
Key Value
Request GET /MVC/api/DataTables?action=Get&draw=1&columns%5B0%5D%5Bdata%5D=0&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=1&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=2&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=3&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=true&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false&_=1440437669357 HTTP/1.1
Why is my parameters object null, and how can I fix it?
Edit: I also attempted this:
public DataTableResult Get(int draw, int start, int length, column[] columns, order[] order)
{
return new DataTableResult();
}
But I get "Can't bind multiple parameters ('columns' and 'order') to the request's content."
UGH less than 10 minutes later, I need a [FromUri] attribute on the parameters.
public DataTableResult Get([FromUri]DataTableParameters parameters)
{
return new DataTableResult();
}

Categories