I need help making my list filtering more efficient.
I have a ASP.NET MVC application where there is a view for records. I've added a filter option that has a number of dropdowns where, based on the values selected in the dropdowns, filter out the result set of the records. The way I've constructed this filter was based off another filter I've seen but I know there has to be a more effective way to do what I would like to do without taking so many steps or using up as much resource. I can tell this isn't very scalable.
Right now the process involves a number of components.
The view, where the results are displayed. Reports.cs
The viewModel, to provide data to the view. ReportViewModel.cs
The view references a controller that uses a method that references a service method. ReportController.cs using GetReportFilters()
The service method refers to another method that pulls all relevant files and filters them GetReportFilters uses RetrieveFilteredReports() located in ReportsService.cs
RetrieveFilteredReports() references RetrieveReportsForFilter() Where RetrieveReportsForFilter runs a query against the DB and pulls all needed files. Here are the corresponding code snippets. (edited to save space, change some namespaces, and DB name)
This is a section of the view Reports.cs
<form class="form" asp-action="GetReportFilters" asp-controller="REPORTS">
<div class = "row">
<div class="form-group">
<p>
Types: #Html.DropDownList("TypeDropdown", new ReportApp.Services.ReportService().GetDropDown("Type"), "select", new { id = "type" })
</p>
</div>
<div class="form-group">
<p>
Shift: #Html.DropDownList("ShiftDropdown", new ReportApp.Services.ReportService().GetDropDown("Shift"), "select", new { id = "shifts" })
</p>
</div>
</div>
</form>
<table class="table table-striped" id="myTable">
<thead>
<tr>
<th>
Type
</th>
<th>
Shift
</th>
<th>
Edit
</th>
</tr>
</thead>
#foreach (var item in Model)
{
<tr class="cost">
<td>
#Html.DisplayFor(modelItem => item.TypeId)
</td>
<td>
#Html.DisplayFor(modelItem => item.ShiftTimeFound)
</td>
<a class="anchorDetail" href="#Url.Action("GetSpecificReport", "Report", new {ReportId = item.ReportId})">
<i class="fa fa-eye" style="font-size: 30px;"></i>
</a>
</td>
</tr>
}
</tbody>
Here is the snippet from the method referenced in the controller
public ViewResult GetReportFilters(string TypeDropdown, string ShiftDropdown)
{
AppContexts.Current.Session.SetObject("TypeDropdown", TypeDropdown);
AppContexts.Current.Session.SetObject("ShiftDropdown", ShiftDropdown);
var viewModel = new ReportService().RetrieveFilteredReports(TypeDropdown, ShiftDropdown);
return View("ReportHistory", viewModel.ToPagedList(p ?? 1, s ?? 10));
}
Here is the RetrieveFilteredReports as referenced in the above method. Also here is and RetrieveReportsForFilter which is referenced in RetrieveFilteredNdrs
public List<ReportViewModel> RetrieveFilteredReports(string TypeName, string Shift)
{
var listOfReports = new ReportService().RetrieveReportsForFilter();
Dictionary<string, string> filterDictionary = new Dictionary<string, string>
{
{ "TypeName", TypeName },
{ "Shift", Shift },
};
foreach (KeyValuePair<string, string> entry in filterDictionary)
{
if (entry.Value != null)
{
switch (entry.Key)
{
case "TypeName":
listOfReports = listOfReports.Where(x => x.TypeId == entry.Value).ToList();
break;
case "Shift":
listOfNdrs = listOfNdrs.Where(x => x.ShiftTimeId == entry.Value).ToList();
break;
return listOfReports.Select(x => new ReportViewModel
{
TypeItemsId = x.TypeId ,
ShiftId = x.ShiftTimeId
}).ToList();
}
public List<ReportViewModel> RetrieveReportsForFilter()
{
var listOfAudits = new List<ReportViewModel>();
using var context = new ReportContext();//contains the formatting for fields
var dropdowns = context.DB.AsNoTracking().ToList();//replaced actually db name with just DB
var query = context.Reports.AsNoTracking().ToList();
listOfAudits = query.Select(x => new ReportViewModel
{
TypeId = x.ReportType,
ShiftTimeId = x.ShiftTimeFound,
}).ToList();
return listOfAudits;
}
As you can see the code bounces around quite a bit. As more and more entries are added to the DB it'll get continually slower until it becomes unbearable. How can I improve the efficiency of this process for scalability?
If there is any more needed information please let me know.
Related
I tried a code like this, it works quite well:
View:
#model IEnumerable<InternProject.Models.Course>
....
#Html.ActionLink("Create New", "Create")
#using (Html.BeginForm("Index", "Course", FormMethod.Get))
{
<p>
#Html.TextBox("searching")
<input type="submit" value="search" />
</p>
}
<table class="table table-striped">
<thead>
<tr>
<th>Course ID</th>
<th>Course Name</th>
<th>Major</th>
<th>Specialization</th>
<th></th>
</tr>
</thead>
#foreach (var item in Model) {
<tr>
<td>#Html.DisplayFor(modelItem => item.crs_ID)</td>
<td>#Html.DisplayFor(modelItem => item.crs_Course)</td>
<td>#Html.DisplayFor(modelItem => item.crs_Major)</td>
<td>#Html.DisplayFor(modelItem => item.crs_Specialization)</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.crs_ID }) |
#Html.ActionLink("Details", "Details", new {id = item.crs_ID }) |
#Html.ActionLink("Delete", "Delete", new { id = item.crs_ID })
</td>
</tr>
}
</table>
Controller:
public class CourseController : Controller
{
private DbCourse db = new DbCourse();
public ActionResult Index(string submit, string searching)
{
var course = from x in db.Course select x;
if (!String.IsNullOrWhiteSpace(searching))
{
return View(db.Course.Where(x => x.crs_Course.Contains(searching.Trim()) ||
x.crs_Major.Contains(searching.Trim()) ||
x.crs_Specialization.Contains(searching.Trim())).ToList());
}
else if (searching == null)
{
return View(db.Course.Where(x => x.crs_Course == searching || searching.Trim() == null).ToList());
}
else
{
return View(db.Course.ToList());
}
}
}
But the id cannot be included because it is an integer. I want to have a solution wherein I can search also in the id of my database depending on the input in the search box.
Also, is there a better code than this for a simple search functionality like this? I've noticed it's so long and it obviously violates the DRY principle.
Here is what my simple application looks like:
I'm taking my baby steps in ASP.NET MVC as a beginner.
I hope to improve my knowledge using applied coding and not just relying on tutorial videos.
Thank you very much in advance! =)
The simplest solution would be to convert the ID to a string. Your code then become the following.
return View(db.Course.Where(x => x.crs_Course.Contains(searching.Trim()) ||
x.crs_Major.Contains(searching.Trim()) ||
x.crs_Specialization.Contains(searching.Trim()) ||
x.crs_crs_ID.ToString().Contains(searching.Trim())).ToList())
This doesn't go against the DRY principle since you're using Contains() on different variables; however, what is going against the DRY principle is the repetitive searching.Trim(). I suggest you trim the string once at the top of your code.
var match = searching.Trim();
Then you can use match instead of searching.Trim() in the code below.
Not even sure if I asked the question the right way. Been looking at this for about an hour and its too simple to take to long. Trouble is I am too simple to know the answer or even how to correctly phrase a search to find the answer.
I have a history of jobs completed for a site set up.
Controller:
public async Task<IActionResult> JobSiteHistory(int id, int? page)
{
var jobs = from j in _context.Job
.Include(j => j.Site)
.Include(j=>j.WaterBody)
.Where(j=>j.Site.SiteID==id)
.OrderByDescending(j=>j.BookingDate)
select j;
int pageSize = 9;
return View(await PaginatedList<Job>.CreateAsync(jobs.AsNoTracking(), page ?? 1, pageSize));
}
This is returning the correct records all good.
I then have a view set up:
<h2> Site Jobs History</h2>
<p>
<a asp-action="Create">Add New Job</a>
</p>
<table class="table">
<thead>
<tr>
<th>Booking Date</th>
<th>Job Number</th>
<th>Waterbody</th>
<th>Job Description</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>#Html.DisplayFor(modelItem => item.BookingDate)</td>
<td>#Html.DisplayFor(modelItem => item.JobNumber)</td>
<td>#Html.DisplayFor(modelItem => item.WaterBody.WBName)</td>
<td>#item.JobDescription.Substring(0, Math.Min(item.JobDescription.Length, 30))</td>
<td>
<a asp-action="Edit" asp-route-id="#item.JobID">Edit</a> |
<a asp-action="Details" asp-route-id="#item.JobID">Details</a> |
<a asp-action="Delete" asp-route-id="#item.JobID">Delete</a> |
</td>
</tr>
}
</tbody>
</table>
This is working wellish so far.
All I want to do is add something like:
#Html.DisplayFor(ModelItem=>item.Site.SiteName)
To the <h2> element. I know this wont work as typed, thanks for thinking that.
I just cant see a way to add it. I considered ViewData, but may be using it wrong as I cant get it to populate with SiteName.
Is there a way to do this or am I thinking all ass about as usual?
The easiest change would be to use this:
<h2>#Html.DisplayFor(m => m[0].Site.SiteName);</h2>
Other options:
Is there any reason why you can't use the ViewBag?
In controller:
ViewBag.SiteName = Site.name
In view:
<h2>#ViewBag.SiteName</h2>
If you must use your model to pass the whole site object then change your view model that you pass to the view.
You are currently returning a list of jobs with the site object for each job, but it looks like you only need it once.
I would change your view model to be something like:
public class SiteJobsHistoryModel
{
public Site Site { get; set;}
public PaginatedList<Job> Jobs { get; set; }
}
Then you don't have to include the site on your query, and just retrieve it once from the database:
var site = _context.Site.Single(j => j.Site.SiteID==id);
var jobs = from j in _context.Job
//.Include(j => j.Site) -- this can be removed
.Include(j=>j.WaterBody)
.Where(j=>j.Site.SiteID==id)
.OrderByDescending(j=>j.BookingDate)
select j;
return View(new SiteJobsHistoryModel
{
Site = site,
Jobs = await PaginatedList<Job>.CreateAsync(jobs.AsNoTracking(), page ?? 1, pageSize)
});
Then for the title in <h2> tag you can use:
#Html.DisplayFor(ModelItem=>model.Site.SiteName)
And your foreach loop becomes:
#foreach (var item in Model.Jobs)
I have here a view model which has two list objects. I will display them as two tables in my view. I've already created EditorFor for them. I'll place four buttons (move one right, move all right, move one left, move all left) for the exchange operations. I've googled everywhere and no goal on how to accomplish that, because I need to move the entire object, replace all "name" and "id" tags, reorder indexes and so on, because this way my lists will be posted correctly. I'm using Datatables.net and jQuery too.
Does anybody have a clue on how to do that?
Thank you in advance. The code goes below
EDIT
Since list elements on ASP.NET MVC are indexed like "ListName_0__Code"(for Id) and "ListName[0].Code" (for name) how to properly reorder these indexes?
EditorFor
#model ViewModels.UserPermissionDetails
<tr id="#Model.Id">
<td>
#Html.HiddenFor(m => m.Code)
#Html.HiddenFor(m => m.Login)
#Html.HiddenFor(m => m.Name)
#Html.HiddenFor(m => m.IdEmpUser)
#Html.DisplayFor(m => m.Code)
</td>
<td>#Html.DisplayFor(m => m.Login)</td>
<td>#Html.DisplayFor(m => m.Nome)</td>
</tr>
View
<table id="tbBlock">
<thead>
<tr>
<th>Code</th>
<th>Login</th>
<th>Name</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(model => model.BlockList)
</tbody>
</table>
<table id="tbAllow">
<thead>
<tr>
<th>Code</th>
<th>Login</th>
<th>Name</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(model => model.AllowList)
</tbody>
</table>
Exchange method (jQuery)
function addElement(OriginTableId, DestinyTableId, OriginListName, DestinyListName) {
var originTb = $(OriginTableId).DataTable(); //initialize DataTable.Net for origin table
var destinyTb = $(DestinyTableId).DataTable(); //initialize DataTable.Net for destiny table
var objectLine = $('#' + originTb.$('tr.selected').attr('id')); //get selected line that will be moved
//Name replacing code piece
var elementsFromLine = $(objectLine.children()[0]).children().toArray();
elementsFromLine.forEach(function (item, index, array) {
$(item).attr('id', $(item).attr('id').replace(OriginListName, DestinyListName)); //Replace 'OriginListName_0' with 'DestinyListName_0'
$(item).attr('name', $(item).attr('name').replace(OriginListName, DestinyListName)); //Replace 'OriginListName[0]' with 'DestinyListName[0]'
});
//Reordering code piece here, how to?
$(DestinyTableId + ' tbody').append(objectLine.clone(true, true));
objectLine.parent().remove();
}
It will be much easier for you to calculate and set the name values with the index only before you submit the form, not for every move action.
#Html.HiddenFor(m => m.Code, new { #class = "code" } })
// same for all the inputs you send to server
$("button[type='submit']").on("click", function (e) {
e.preventDefault();
updateIndexes();
$("form").submit();
});
function updateIndexes() {
$("#tbAllow").find("tbody").children("tr").each(function (i) {
var prefix = "BlockList[" + i + "].";
var $tr = $(this);
$tr.find("input.code").attr("name", prefix + "Code");
$tr.find("input.login").attr("name", prefix + "Login");
// same for all the inputs you send to server
});
};
I have two select lists on a view. I need to populate the second select list's items according to the value of the first select list's selected item without posting the entire page. What is the best way to make this happen?
Here are the select lists in the view:
<tr>
<td>
<div class="editor-label">
#Html.LabelFor(model => model.PersonnelAreaID)
</div>
</td>
<td>
<div class="editor-field">
#Html.DropDownListFor(model => model.PersonnelAreaID, TLMS_DropDownLists.PersonnelAreas)
</div>
</td>
</tr>
<tr>
<td>
<div class="editor-label">
#Html.LabelFor(model => model.SupervisorID)
</div>
</td>
<td>
<div class="editor-field">
#Html.DropDownListFor(model => model.PersonnelAreaID, TLMS_DropDownLists.ApprovingAuthorities)
</div>
</td>
</tr>
Here are the methods I am populating them with currently:
public static List<SelectListItem> PersonnelAreas
{
get
{
List<SelectListItem> personnelAreas = new List<SelectListItem>();
personnelAreas.Add(new SelectListItem { Value = "", Text = "" });
foreach (PersonnelArea pa in PersonnelArea.GetAllPersonnelAreas())
{
personnelAreas.Add(new SelectListItem { Value = pa.AreaID.ToString(), Text = pa.AreaTitle });
}
return personnelAreas;
}
private set { }
}
public static List<SelectListItem> ApprovingAuthorities
{
get
{
List<SelectListItem> returned = new List<SelectListItem>();
returned.Add(new SelectListItem { Text = "", Value = "" });
foreach (Employee item in Employee.GetAllEmployees().Where(e => e.UserRoleID == 2 || e.UserRoleID == 5))
{
returned.Add(new SelectListItem { Text = string.Format("{0} {1}", item.FirstName, item.LastName), Value = item.ID.ToString() });
}
return returned;
}
private set { }
}
I need the ApprovingAuthorities select list to show only those applicable to the PersonnelArea.
The "best" way is probably a matter of opinion, but a good way to do it is to use AJAX to populate the 2nd dropdown.
Whereas the first dropdown can be populated with static data, the 2nd can be populated either by generating client-side JavaScript code that fills up the 2nd, or by doing an AJAX call to a Controller method that returns JSON, and then filling up the 2nd dropdown with that data.
Here is a nice article about it: "Cascading Dropdown Lists with MVC 4 and jQuery" at http://www.sidecreative.com/Blog/Entry/Cascading-dropdown-lists-with-MVC4-and-jQuery.
Good luck!
You can also use SignalR to populate the second combobox. You can follow a good tutorial here
I am new to MVC, and I want on my view two things:
1 - A list of data's (3 columns)
2 - A dropdown list that I can filter the list (filled with data from the first column)
in my controller I have the following function:
public ViewResult ListUrl()
{
var ws = new Service1();
localhost.Service1 s1 = new Service1(); // data from web services
localhost.UrlInfo[] ui = s1.GetUrlInfo();
for (int i = 0; i < ui.Length; i++)
{
var UrlItem = new UrlItem();
UrlItem.Id = Convert.ToInt32(ui[i].Id);
UrlItem.urlll = ui[i].url;
UrlItem.toontijd = ui[i].ToonTijd;
UrlItem.positie = Convert.ToInt32(ui[i].positie);
Models.ListUrl.UrlList.Add(UrlItem);
}
var urlname = from url in s1.GetUrlInfo() select url ;
ViewData["url"] = new SelectList(urlname, "Id", "url");
return View();
}
In the view :
<script type="text/javascript">
$(document).ready(function () {
// How can I filter the list (see <table> tag) when I change index of dropdown list???
});
</script>
#Html.DropDownList("SelectedItem", (SelectList)ViewData["url"], "----- all ------", new { id = "0", text = "----- all ------" })
<table>
<tr>
<th>
Url
</th>
<th>
Toontijd
</th>
<th>
Positie
</th>
</tr>
#foreach (var item in ListUrl.UrlList)
{
<tr>
<td>
#item.urlll.ToString()
</td>
<td>
#item.toontijd.ToString()
</td>
<td>
</td>
<td>
#item.positie.ToString()
</td>
</tr>
}
How to get dropdownlist change event working?
Thanks a lot.
Hicham.
Well.. You need to do some stuff for this.. Let me explain in steps..
Create a partial view for the grid
Attach onchange event for dropdown
Make one controller action method which take dropdown selection as parameter and returns the grid partial view as result
$.get('yourActionURL', { parameter: $('#yourDropdownId').val() }, function(result) {
$('#grid').html(result);
});
Filtering a WebGrid with a DropDownList in MVC4 and ASP.NET MVC Filtering results in a list/grid - these link can help you in details about this.