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;
}