Model:
public class myMaster
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Column(Order = 0)]
public int myKey1 { get; set; }
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Column(Order = 1)]
public int myKey2 { get; set; }
public string MyMasterName { get; set; }
}
public class myDetail
{
[Key]
[Column(Order = 0)]
[ForeignKey("myMaster")]
public int myKey1 { get; set; }
[Key]
[Column(Order = 1)]
[ForeignKey("myMaster")]
public int myKey2 { get; set; }
public virtual myMaster myMaster { get; set; }
[Key]
[Column(Order = 2)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Ordinal { get; set; }
public string MyDetailName { get; set; }
}
public class MyTableDBContext : DbContext
{
public DbSet myMaster { get; set; }
public DbSetmyDetail { get; set; }
}
Create controllers using scaffolding.
Add partial view, like on the pictures:


In that partial view (in my case Detail.cshtml) write code like:
@model IEnumerable<MasterDetailDataTables.Models.myDetail>
<table id="invoiceDetails" class="display dataTable">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.myKey1) @*name of the table, remove it if you do not want name to be visible*@
</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th>
@Html.DisplayNameFor(model => model.myKey1) @*name of the table, remove it if you do not want name to be visible*@
</th>
</tr>
</tfoot>
</table>
Index.cshtml should look like:
@model IEnumerable<MasterDetailDataTables.Models.myMaster>
@section scripts {
@Styles.Render("~/Content/themes/base/css")
@Scripts.Render("~/bundles/table")
<script src="/../../Scripts/myDataTables.js" type="text/javascript"></script>
}
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table id="myDataTable" class="display dataTable">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.MyMasterName) @*name of the table, remove it if you do not want name to be visible*@
</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th>
@Html.DisplayNameFor(model => model.MyMasterName) @*name of the table, remove it if you do not want name to be visible*@
</th>
</tr>
</tfoot>
</table>
@Html.Action("Detail")
Create methods like:
public ActionResult Detail()
{
return PartialView();
}
public ActionResult MasterJson(
jQueryDataTableParamModel param
)
{
var myResult = Json(new
{
sEcho = param.sEcho,
iTotalRecords = db.myMaster.Count(),
iTotalDisplayRecords = db.myMaster.Count(),
aaData = db.myMaster.OrderBy(x => x.MyMasterName).Skip(param.iDisplayStart).Take(param.iDisplayLength).ToList()
},
JsonRequestBehavior.AllowGet
);
return myResult;
}
public ActionResult DetailJSON(
jQueryDataTableParamModel param
)
{
var myResult = Json(new
{
sEcho = param.sEcho,
iTotalRecords = db.myDetail.Count(),
iTotalDisplayRecords = db.myDetail.Count(),
aaData = db.myDetail.Where(x => ((x.myKey1 == param.myKey1) & (x.myKey2 == param.myKey2))).OrderBy(x => x.myKey1).Skip(param.iDisplayStart).Take(param.iDisplayLength).ToList()
//aaData = db.Invoices.ToList()
},
JsonRequestBehavior.AllowGet
);
return myResult;
}
Line:
aaData = db.myDetail.Where(x => ((x.myKey1 == param.myKey1) & (x.myKey2 == param.myKey2))).OrderBy(x => x.myKey1).Skip(param.iDisplayStart).Take(param.iDisplayLength).ToList()
Mean that entity framework will page the data. Taken from here.
DetailJson and MasterJson will be used later in ajax call.
jQueryDataTableParamModel should look like:
public class jQueryDataTableParamModel
{
///
/// Request sequence number sent by DataTable,
/// same value must be returned in response
///
public string sEcho { get; set; }
///
/// Text used for filtering
///
public string sSearch { get; set; }
///
/// Number of records that should be shown in table
///
public int iDisplayLength { get; set; }
///
/// First record that should be shown(used for paging)
///
public int iDisplayStart { get; set; }
///
/// Number of columns in table
///
public int iColumns { get; set; }
///
/// Number of columns that are used in sorting
///
public int iSortingCols { get; set; }
///
/// Comma separated list of column names
///
public string sColumns { get; set; }
public int myKey1 { get; set; }
public int myKey2 { get; set; }
}
Where
public int myKey1 { get; set; }
public int myKey2 { get; set; }
are added by me,as additional parameter sent from datatables
Download datatables and add media folder to your project, as I already described it here.
To \App_Start\BundleConfig.cs add code like:
bundles.Add(new ScriptBundle("~/bundles/table").Include(
"~/Scripts/media/js/jquery.dataTables.js"));
bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
"~/Scripts/media/css/jquery.ui.core.css",
"~/Scripts/media/css/jquery.ui.resizable.css",
"~/Scripts/media/css/demo_table.css"));
Finaly, javascript:
var oTable;
var oInvoiceDetail;
var iMark = 0;
var iNumber = 0;
$(document).ready(function () {
invoiceDetailsClick();
oTable = $('#myDataTable').dataTable({
"bProcessing": true,
"bServerSide": true,
"iDisplayLength": 10,
"sAjaxSource": "MasterDetail/MasterJson",
"fnServerData": function (sUrl, aoData, fnCallback, oSettings) {
oSettings.jqXHR = $.ajax({
"type": "POST",
"url": sUrl,
"data": aoData,
"success": fnCallback,
"dataType": "json",
"cache": false
});
},
"aoColumns": [
{ "sWidth": '200px', "mData": "MyMasterName" }
]
});
});
function invoiceDetailsClick() {
oInvoiceDetail = $('#invoiceDetails').dataTable({
"bProcessing": true,
"bServerSide": true,
"iDisplayLength": 10,
"sAjaxSource": "MasterDetail/DetailJson",
"bFilter": false, //hides search field
"bPaginate": false, //hides pagination
"bInfo": false, //hides info
"fnServerData": function (sUrl, aoData, fnCallback, oSettings) {
/* Add some extra data to the sender */
aoData.push({ "name": "myKey1", "value": iMark });
aoData.push({ "name": "myKey2", "value": iNumber });
oSettings.jqXHR = $.ajax({
"type": "POST",
"url": sUrl,
"data": aoData,
"success": fnCallback,
"dataType": "json",
"cache": false
});
},
"aoColumns": [
{ "sWidth": '200px', "mData": "myKey1" }
]
});
}
$("#myDataTable tbody").click(function (event) {
$(oTable.fnSettings().aoData).each(function () {
$(this.nTr).removeClass('row_selected');
});
$(event.target.parentNode).addClass('row_selected');
var anSelected = fnGetSelected(oTable);
var iRow = oTable.fnGetPosition(anSelected[0]);
var data = oTable.fnGetData(iRow);
iMark = data.myKey1;
iNumber = data.myKey2;
oInvoiceDetail.fnReloadAjax(); //data.Mark, data.Number
});
$.fn.dataTableExt.oApi.fnReloadAjax = function (oSettings, sNewSource, fnCallback, bStandingRedraw) {
if (sNewSource !== undefined && sNewSource !== null) {
oSettings.sAjaxSource = sNewSource;
}
// Server-side processing should just call fnDraw
if (oSettings.oFeatures.bServerSide) {
this.fnDraw();
return;
}
this.oApi._fnProcessingDisplay(oSettings, true);
var that = this;
var iStart = oSettings._iDisplayStart;
var aData = [];
this.oApi._fnServerParams(oSettings, aData);
oSettings.fnServerData.call(oSettings.oInstance, oSettings.sAjaxSource, aData, function (json) {
/* Clear the old information from the table */
that.oApi._fnClearTable(oSettings);
/* Got the data - add it to the table */
var aData = (oSettings.sAjaxDataProp !== "") ?
that.oApi._fnGetObjectDataFn(oSettings.sAjaxDataProp)(json) : json;
for (var i = 0; i < aData.length; i++) {
that.oApi._fnAddData(oSettings, aData[i]);
}
oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
that.fnDraw();
if (bStandingRedraw === true) {
oSettings._iDisplayStart = iStart;
that.oApi._fnCalculateEnd(oSettings);
that.fnDraw(false);
}
that.oApi._fnProcessingDisplay(oSettings, false);
/* Callback user function - for event handlers etc */
if (typeof fnCallback == 'function' && fnCallback !== null) {
fnCallback(oSettings);
}
}, oSettings);
};
function fnGetSelected(oTableLocal) {
var aReturn = new Array();
var aTrs = oTableLocal.fnGetNodes();
for (var i = 0; i < aTrs.length; i++) {
if ($(aTrs[i]).hasClass('row_selected')) {
aReturn.push(aTrs[i]);
}
}
return aReturn;
}