I'm following a YouTube series which teachs ASP.NET MVC. In the tutorial the teacher shows how to make a simple search functionality however in my case it's different.
I have search criteria: Studies (Dropdown), Country (Dropdown), Status (Dropdown) and Keyword (Input).
My question is how do I query the database to show the results depending on the search criteria that was chosen?
To be more clear:
If the User has chosen Studies and Country only then the code should use values from Studies and Country to search the respective database column.
Click here for the UI Design
Table: Students
[StudentID] INT IDENTITY (1, 1) NOT NULL,
[StudentName] VARCHAR (50) NOT NULL,
[StudentStudiesID] INT NOT NULL,
[StudentCountry] VARCHAR (50) NOT NULL,
[StudentCity] VARCHAR (50) NOT NULL,
[StudentStatus] VARCHAR (50) NOT NULL,
CONSTRAINT [PK_Students] PRIMARY KEY CLUSTERED ([StudentID] ASC),
CONSTRAINT [FK_Students_Studies] FOREIGN KEY ([StudentStudiesID]) REFERENCES [dbo].[Studies] ([StudiesID])
SearchController.cs
public class SearchController : Controller
{
public ActionResult Index()
{
DatabaseEntitiesModel db = new DatabaseEntitiesModel();
int Studies;
int.TryParse(Request.QueryString["Studies"], out Studies);
var Country = Request.QueryString["Country"];
var Status = Request.QueryString["Status"];
var Keyword = Request.QueryString["Keyword"];
IQueryable <Student> SearchQuery = db.Students;
List<SearchViewModel> SVM = SearchQuery.Select(x => new SearchViewModel
{
StudentID = x.StudentID,
StudentName = x.StudentName,
StudentCountry = x.StudentCountry,
StudentCity = x.StudentCity,
StudiesName = x.Study.StudiesName,
StudentStatus = x.StudentStatus
}).OrderByDescending(x => x.StudentID).ToList();
return View( SVM );
}
}
Reuse SearchQuery (items are lazy-loaded, until you call ToList()) and add as many specific Where() clauses/calls as you need:
// the type (IQueryable<Student>) should be defined explicitly
// details: https://stackoverflow.com/questions/21969154/cannot-implicitly-convert-type-system-linq-iqueryable-to-system-data-entity-d
IQueryable<Student> query = db.Students;
if(viewModel.Filter1 != null) {
query = query.Where(i => i.SomeStudentProperty1 == viewModel.Filter1);
}
if(viewModel.Filter2 != null) {
query = query.Where(i => i.SomeStudentProperty2 == viewModel.Filter2);
}
var result = query.ToList();
The easiest way to do this would be to test each condition and if it meets what you want, add a Where clause. Something like this:
int.TryParse(Request.QueryString["Studies"], out Studies);
var Country = Request.QueryString["Country"];
var Status = Request.QueryString["Status"];
var Keyword = Request.QueryString["Keyword"];
IQueryable<Student> SearchQuery = db.Students;
if(Studies > 0)
{
SearchQuery = SearchQuery.Where(s => s.StudiesID == Studies);
}
if(!string.IsNullOrEmpty(Country))
{
SearchQuery = SearchQuery.Where(s => s.StudentCountry == Country);
}
...More conditions can go here
Because of Lazy Loading, the actual query isn't executed until you call .ToList(), or iterate over the collection. Hopefully, this gets you started on the right track.
Edit
In my haste, I changed your IQueryable to a var. Fixed.
Also, as Erik pointed out, using Request.QueryString is not the way to go. You'll instead want to pass these values in to the action method. So, something like:
public ActionResult Index(int studies, string status, string country, string keyword)
Related
I'm trying to create a list of orders in a custom Controller in a NopCommerce/MVC application and i want the list to be sorted by creationDate and contain total orders for that date and convert these values to string format.
The thing is i don't want an ActionResult displaying a grid in the view like in Admin/Orders. All i want is a List of all paid orders between model.StartDate and model.EndDate that contains two parameters "CreationDateUtc" and TotalOrders". i simply just need a list containing the data of orders sorted by creationdate.
The if i choose StartDate 2014-03-29 and EndDate 2014-04-02 the output i want would look something like this:
List OrdersTotalList with parameters CreationDateUtc and TotalOrders
CreationDateUtc "2014-03-29"
TotalOrders "562"
CreationDateUtc "2014-03-30"
TotalOrders "485"
CreationDateUtc "2014-03-31"
TotalOrders "733"
CreationDateUtc "2014-04-01"
TotalOrders "729"
CreationDateUtc "2014-04-02"
TotalOrders "681
"
I'm trying to access the data by an implementations of OrderList from OrderController in my CustomController. Problem is this method always returns 10 objects when infact the total number of orders within this timespace is 58. When debugging Total = orders.TotalCount are actually showing 58 orders as one int value). Also a gridmodel is used here but i really don't need a gridmodel, i just need the data from the database:
public List OrderList(GridCommand command, OrderListModel model, OrderModel Omodel)
{
DateTime S = new DateTime(2014, 3, 29); //-- Dates for testing
DateTime E = new DateTime(2014, 4, 02);
model.StartDate = S;
model.EndDate = E;
DateTime? startDateValue = (model.StartDate == null) ? null
: (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone);
DateTime? endDateValue = (model.EndDate == null) ? null
: (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1);
OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null;
PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null;
ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null;
//load orders
var orders = _orderService.SearchOrders(startDateValue, endDateValue, orderStatus,
paymentStatus, shippingStatus, model.CustomerEmail, model.OrderGuid, command.Page - 1, command.PageSize);
var gridModel = new GridModel<OrderModel>
{
Data = orders.Select(x =>
{
var customerCurrency = _currencyService.GetCurrencyByCode(x.CustomerCurrencyCode);
var totalInCustomerCurrency = _currencyService.ConvertCurrency(x.OrderTotal, x.CurrencyRate);
return new OrderModel()
{
Id = x.Id,
OrderTotal = _priceFormatter.FormatPrice(totalInCustomerCurrency, true, customerCurrency),
OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext),
PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext),
ShippingStatus = x.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext),
CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc)
};
}),
Total = orders.TotalCount <-- Returns all orders (58) but as an integer
};
var reportSummary = _orderReportService.GetOrderAverageReportLine
(orderStatus, paymentStatus, shippingStatus, startDateValue, endDateValue, model.CustomerEmail);
var profit = _orderReportService.ProfitReport
(orderStatus, paymentStatus, shippingStatus, startDateValue, endDateValue, model.CustomerEmail);
var aggregator = new OrderModel()
{
aggregatorprofit = _priceFormatter.FormatPrice(profit, true, false),
aggregatortax = _priceFormatter.FormatPrice(reportSummary.SumTax, true, false),
aggregatortotal = _priceFormatter.FormatPrice(reportSummary.SumOrders, true, false)
//aggregatordates =
};
List<Order> TotalProductsSold = new List<Order>();
foreach (var o in orders)
{
TotalProductsSold.Add(o);
}
return TotalProductsSold.ToList(); //<-- returns 10 orders containing all order info
}
If i understand correct in order to archive this i have to first search through orders and if their PaymentStatus is Paid. Then create a List in the Method from above. A foreach loop could iterate through orders and add orders to the List, all though i need to specify i only want CreationDate and TotalOrders for that date as parameters in the List.
I know this isn't right but i emagine something similar. The thing is i need a list of order objects and not one object with one value:
List<OrderModel> OrdersTotalList = new List<OrderModel>();
foreach (var o in orders)
{
OrderModel OM = new OrderModel(OM.OrderTotal, OM.CreatedOn);
OrdersTotalList.Add(OM);
}
return OrdersTotalList; //--
Am i completely of or is this the right aproach? I was hoping someone more familiar with NopCommerce knows more about this.
Sorry for all the text
Thank you
Solved.
In order to get a full list of orders you can create a new constructor in IOrderService/OrderService that is of type List instead of IPagedList. The method used for searching orders are called "SearchOrders" and is of type IPagedList. IPagedList contains the property PageSize wich results in only 10 orders.
You can create a new method with same implementation as SearchOrders and change IPagedList to List, remove "int pageIndex" and "int pageSize".
Then use:
_orderService.YourNewConstructor(DateTime? startTime, DateTime? endTime,
OrderStatus? os, PaymentStatus? ps, ShippingStatus? ss, string billingEmail,
string orderGuid)
{
some code...
}
This will give you access to all orders.
I have an table called Contents. There's one to many relationship on Contents, so each content can have a parent and children. I'm using EF Code First, so I have an entity Content which has Id, ParentId, Parent and Children properties.
Now, I'm building an ajax based tree of Contents. I have a simple action that returns a JSON of one level of Contents, based on parentId:
public JsonResult GetContents(int? parentId = null)
{
return Json(db.Contents
.Where(p => p.ParentId == parentId)
.Select(p => new
{
id = p.Id,
name = p.Name
});
}
The next thing I want to do is to automatically select some value. The problem is that the value can be deep inside the hierarchy of the tree, so for each content I'll need to know whether or not the selected value is a child or grandchild and so forth, of it.
public JsonResult GetContents(int? parentId = null, int selectedValue)
{
return Json(db.Contents
.Where(p => p.ParentId == parentId)
.Select(p => new
{
id = p.Id,
name = p.Name
isSelectedValueUnderThisHierarchy: // How can I efficiently implement this?
});
}
It's easy to implement with a lot of queries, but I'm trying to make things as efficient as possible, and EF doesn't provide any Recursive methods as far as I know, so I really have no clue where to start.
You could first build a list of all the ParentIds from the selected value. Depending on the size of your Contents table, you could first load the data, then loop through without making extra queries to the database.
db.Contents.Load();
var selectedItem = db.Contents.Find(selectedValue);
var parents = new List<int>();
while (selectedItem.ParentId != null)
{
parents.Add(selectedItem.ParentId.Value);
selectedItem = selectedItem.Parent;
}
Alternatively, you could use CTE (Common Table Expression).
var parents = db.Database.SqlQuery<int>("sql statement");
Once you have a list of parents, you can use Contains.
return Json(db.Contents
.Where(p => p.ParentId == parentId)
.Select(p => new
{
id = p.Id,
name = p.Name
isSelectedValueUnderThisHierarchy = p.ParentId.HasValue && parents.Contains(p.ParentId.Value)
});
UPDATE: CTE Example
You'd probably want to use a stored procedure, but this code should work.
var sql = #"with CTE as
(
select ParentId
from Contents
where Id = {0}
union all
select Contents.ParentId
from Contents
inner join CTE on Contents.Id = CTE.ParentId
)
select *
from CTE
where ParentId is not null";
var parents = db.Database.SqlQuery<int>(string.Format(sql, selectedItem)).ToList();
I am trying to get the result of the user logged in but receiving this error :
"Cannot compare elements of type 'System.Linq.IQueryable`1'. Only
primitive types, enumeration types and entity types are supported. "
Here is the query I'm applying in my index action:
var viewModel = new PointsViewModel();
viewModel.Point = db.Point.ToList();
viewModel.Redeem = db.Redeem.ToList();
TempData["UserPoints"] = null;
var usrname = (from a in db.Instructors
where a.Email == User.Identity.Name
select new { a.PersonID });
if (usrname.Count().Equals(0))
{
TempData["UserPoints"] = "You have not earn any points yet.";
return View();
}
viewModel.instructor = db.Instructors
.Where(i => i.PersonID.Equals(usrname))// if I directly insert id here then it works properly but I don't want direct inserts
.Single();
PopulateAssignedPointData(viewModel.instructor);
return View(viewModel);
Please help me with this please...I am unable to find any solution on google
It's because you're passing usrname as a parameter to another query. usrname is a query, not a value, so you need to retrieve a value from the query (in this case, by using First(), but you could just as easily use Single() if you like) before you can use it as a parameter in another query. I would recommend the following changes:
if (!usrname.Any())
{
TempData["UserPoints"] = "You have not earn any points yet.";
return View();
}
var personId = usrname.First();
viewModel.instructor = db.Instructors
.Where(i => i.PersonID.Equals(personId))
.Single();
I also changed usrname.Count().Equals(0) to !usrname.Any() as it is more idiomatic (it will use the exists keyword in SQL, rather than count)
Try to use this:
viewModel.instructor = db.Instructors
.Where(i => usrname.Any(u => u.PersonID == i.PersonID))
.Single();
[HttpPost]
public ActionResult Playlist(String ID)
{
long playid=Convert.ToInt64(ID);
var q = from client in Clients
join m in db.Playlists on client.ClientId equals m.ClientId
join meta in db.ContentMetaDatas on m.PlaylistId equals meta.PlaylistId
where m.PlaylistId.Equals(playid)
orderby m.PlaylistId descending
select new SimpleViewModel
{
ID = m.PlaylistId,
Live = false,
Expired = meta.ContentMetaDataExpiryDateTime != null,
Details = m.PlaylistShortDescription,
ImageUrl = meta.ContentMetaDataImage,
Title = m.PlaylistTitle
};
return Json(q.ToPage(p, "ID desc"), JsonRequestBehavior.DenyGet);
}
As shown in the above code of controller I want to fetch data those are returned in JSON data format & used in view.
But As I have to convert the string into long datatype,
what can I do for getting data. Is there anything wrong in the above code??
Hi i'm looking for some help in how to append rows to an existing LINQ object. In the controller method below I have two result sets, i'm looping the Sites and want to add a record to the 'results' object for each record in the Sites object.
I've tried concat etc but not getting anywhere, just need s small example to assist, many thanks in advance, J
public IQueryable<UsersToSite> FindAllUsersToSites(int userId,SystemType obj)
{
var results = (from usersToSite in this._db.UsersToSites
where usersToSite.UserId == userId &&
usersToSite.SystemTypeId == obj
orderby usersToSite.Site.SiteDescription
select usersToSite);
// Now for each remaining Site append a record thats not physically in the database. From the view the user will be able to click these records to ADD new
// I'll then build in a search
var sites = (from site in this._db.Sites
where !(from o in _db.UsersToSites where (o.UserId == userId && o.SystemTypeId == obj) select o.SiteId).Contains(site.SiteId)
orderby site.SiteDescription
select site);
foreach (var site in sites)
{
// HERE I want to create the new ROW in results object
//results = new[results] { new { UsersToSiteId = null, AccessTypeId = null } }.Concat(sites);
//SiteId=site.SiteId,
//UsersToSiteId = 0,
//AccessTypeId = 0,
//UserId = userId
}
return results;
}
I don't think you can, if you want to have keep queryable.
However, if you materialize the results with ToList(), then you can:
var sites = (from site in this._db.Sites
where !(from o in _db.UsersToSites where (o.UserId == userId && o.SystemTypeId == obj) select o.SiteId).Contains(site.SiteId)
orderby site.SiteDescription
select site)
.ToList();
sites.Add(new Site { UsersToSiteId = null, etc });
If it was LINQ to Objects, you could do Concat.
The problem here that it can't do ConcatLINQ query that will have one part from SQL and another from objects. You need to materialize results first and then concat to object.