MVC LINQ Sort / OrderBy a Model - c#

the for each loop works properly setting the PickupDistanceSort column correctly, but then I can't get the model to sort so that I can display the rows in ascending order based on the newly set PickupDistanceSort values. PickupDistanceSort is a data type of long. The model displays in the view, it's just not sorted. How do you sort a model before it's sent to the view?
public ActionResult JobsDistanceSorted()
{
var model = from j in db.Jobs select j;
foreach (var item in model)
{
item.PickupDistanceSort = ICN.CustomMethods.
GetDistance(34.180046081543, -118.309028625488,
item.PickupLatitude, item.PickupLongitude);
}
model = model.OrderBy(s => s.PickupDistanceSort);
return View("JobHeadings", model);
}

You have to convert it to List which stores items locally, and then you can call OrderBy on local list. Calling OrderBy on IQueryable will result in new database query, in which the values are not stored.
public ActionResult JobsDistanceSorted()
{
var model = db.Jobs.ToList();
foreach (var item in model)
{
item.PickupDistanceSort = ICN.CustomMethods.
GetDistance(34.180046081543, -118.309028625488,
item.PickupLatitude, item.PickupLongitude);
}
return View("JobHeadings", model.OrderBy(s => s.PickupDistanceSort));
}

If PickupDistanceSort is not a .Net type you need to imlement an IEqualityComparer for your type and use OrderBy like this (PickupDistanceComparer is the name of your custom comparer):
model = mode.OrderBy(s => s.PicupDistanceSort, new PickupDistanceComparer());

Related

Add data to View from additional Table

I would like to call a Create View from the "Buchungen" controller.
To do this, I would like to add some data from another table (ArWoo) to the view so that it is already filled out in advance.
The two tables are not linked.
I give the appropriate ID when calling the "Buchungen" controller.
// GET: Buchungen/Create_AR
public IActionResult Create_AR(int? id)
{
var AR = _context.ArWoo
.Where(n => n.Id == id);
ViewData["Bestellnummer"] = AR.Bestellnummer;
return View("Create");
}
How can I now transfer a value from AR (e.g. "Bestellnummer") to the view?
I thought that would just go along with ViewData["Bestellnummer"] = AR.Bestellnummer;
But this doesn´t work.
If I set a breakpoint at return View ("Create"), I see that the variable AR is correctly assigned.enter image description here
Fix the action by adding FirstOrdefault to a query
public IActionResult Create_AR(int? id)
{
var AR = _context.ArWoo
.Where(n => n.Id == id).FirstOrDefault();
ViewData["Bestellnummer"] = AR.Bestellnummer;
// or return View("Create",AR.Bestellnummer);
return View("Create");
}

Using for loop on where() method

So I have a search-input and checkboxes that passes the values to the controller when there are inputs. And I want to use these values to get something back from the database. The search-input is a string and it works and intended. Here is the code for the search-input:
public async Task<ViewResult> Index(string searchString, List<int> checkedTypes)
{
var products = from p in _db.Products select p;
ViewData["CurrentFilter"] = searchString;
if (!string.IsNullOrEmpty(searchString))
{
products = products.Where(p => p.Name.ToLower().Contains(searchString));
}
return View(products);
}
However the checkboxes values are stored in a list. So basically I want to do the same as the code above, but with a list. So basically an idea is like this:
if(checkedTypes != null)
{
foreach (var i in checkedTypes)
{
products = products.Where(p => p.TypeId == i));
}
}
If I do it like the code above, I just get the last (i) from the loop. Another solution I did was this:
if(checkedTypes != null)
{
var temp = new List<Product>();
foreach (var i in checkedTypes)
{
temp.AddRange(products.Where(p => p.TypeId == i));
}
products = temp.AsQueryable();
}
But when I did it like that I get this error:
InvalidOperationException: The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IAsyncQueryProvider can be used for Entity Framework asynchronous operations.
So anyone have a solution that I can use? Or is there a better way to handle checkboxes in the controller?
Assuming you are using EF Core (also the same is true for linq2db) - it supports translating filtering with local collection, i.e. Where(x => checkedTypes.Contains(x.SomeId)).
If you have "and" logic to filter by searchString and checkedTypes than you can conditionally add Where clause:
if (!string.IsNullOrEmpty(searchString))
{
products = products.Where(p => p.Name.ToLower().Contains(searchString));
}
if(checkedTypes != null)
{
products = products.Where(p => checkedTypes.Contains(p.TypeId));
}
P.S.
Also you should be able to change your first line to:
var products = _db.Products.AsQueryable();

C# using aggregate method on nopcommerce shipment items list not working

Ok so I'm trying to get all items which have been shipped and add them to a list if the shipment item is not already in that list. But if the shipment item is found in the list then I want to combine those two items in the list.
Here is the code I'm working with:
var shippedItems = _orderService.GetOrderById(shipment.OrderId).Shipments.Where(x => x.ShippedDateUtc != null && x.OrderId == shipment.OrderId && x.Id != shipment.Id).ToList();
List<ShipmentItem> shipmentItemsList = new List<ShipmentItem>();
for (int i = 0; i <= shippedItems.Count - 1; i++)
{
var si = shippedItems[i];
var sii = si.ShipmentItems.ToList();
foreach (var item in sii)
{
if (!shipmentItemsList.Contains(item))
{
shipmentItemsList.Add(item);
}
else
{
var foundId = shipmentItemsList.Select(x => x.Id == item.Id);
shipmentItemsList.Aggregate((foundId, item) => foundId + item);
}
}
}
For these two variables (foundId, item) i get errors:
A local variable named the variable name cannot be declared in this
scope because that name is used in an enclosing local scope to define
a local or parameter
UPDATE
I also thought I could try the following, but it's not joining the results.
if (i == 0)
{
shipmentItemsList = si.ShipmentItems.ToList();
}
else
{
shipmentItemsList.Concat(si.ShipmentItems.ToList());
}
Anyone able to point me on the right track.
Cheers
Thanks for the clarification. Essentially, the way that I understand your problem is that you need to take an object map that is grouped by Shipment and look at it from the point of Item instead. Linq can deal with this for you by using SelectMany to flatten the list and the GroupBy to shape the flattened list into your new groupings. I've made some assumptions about property names for the nopCommerce objects, but the following code sample should get you close enough to tweak with the correct property names:
var shipmentItemsList = shippedItems // This is logically grouped by shipment id
.SelectMany(s => s.ShipmentItems) // First flatten the list
.GroupBy(i => i.ItemId) // Now group it by item id
.Select(g => new
{
ItemId = g.Key,
Quantity = g.Sum(item => item.Quantity)
}) // now get the quantity for each group
.ToList();

LINQ select where EF Navigation property value in array

I have a Web API method that is used to search a EF object named 'Patients'. The patient has a navigation property named Referrals (one patient can have many referrals) and each Referral has an integer property named 'ConsultantID'.
I would like to return all patients who have referrals whose ConsultantID value is within a defined array named 'consultants' but i can't get my head around the logic needed. I have the below at the moment but it isn't working as i expected, the Referrals.Any call seems to be performing an 'Exists' call rather than the join behaviour i was expecting.
public List<HelperCode.DTO.SearchResult> SearchPatients(string firstname, string surname, [FromUri] int[] consultants)
{
IQueryable<Patient> results = db.Patients;
List<HelperCode.DTO.SearchResult> output = new List<HelperCode.DTO.SearchResult>();
List<int> inputConsultants = consultants.OfType<int>().ToList();
if (!String.IsNullOrEmpty(firstname)) { results = db.Patients.Where(c => c.FirstName.ToLower().Contains(firstname.ToLower())); }
if (!String.IsNullOrEmpty(surname)) { results = results.Where(c => c.Surname.ToLower().Contains(surname.ToLower())); }
if (consultants.Length > 0) {
results = results.Where(c => c.Referrals.Any(r => inputConsultants.Contains(r.ConsultantID ?? default(int))));
}
results = results.OrderBy(i => i.Surname);
foreach (Patient p in results) {
output.Add(new HelperCode.DTO.SearchResult(p));
}
return output;
}
Developer's a tool, code works fine but input values were incorrect. Hopefully will be of use to someone down the line.

How to modify only one or two field(s) in LINQ projections?

I have this LINQ query:
List<Customers> customers = customerManager.GetCustomers();
return customers.Select(i => new Customer {
FullName = i.FullName,
Birthday = i.Birthday,
Score = i.Score,
// Here, I've got more fields to fill
IsVip = DetermineVip(i.Score)
}).ToList();
In other words, I only want one or two fields of the list of the customers to be modified based on a condition, in my business method. I've got two ways to do this,
Using for...each loop, to loop over customers and modify that field (imperative approach)
Using LINQ projection (declarative approach)
Is there any technique to be used in LINQ query, to only modify one property in projection? For example, something like:
return customers.Select(i => new Customer {
result = i // telling LINQ to fill other properties as it is
IsVip = DetermineVip(i.Score) // then modifying this one property
}).ToList();
you can use
return customers.Select(i => {
i.IsVip = DetermineVip(i.Score);
return i;
}).ToList();
Contrary to other answers, you can modify the source content within linq by calling a method in the Select statement (note that this is not supported by EF although that shouldn't be a problem for you).
return customers.Select(customer =>
{
customer.FullName = "foo";
return customer;
});
You "can", if you create a copy constructor, which initializes a new object with the values of an existing object:
partial class Customer
{
public Customer(Customer original)
{
this.FullName = original.FullName;
//...
}
}
Then you can do:
return customers.Select(i => new Customer(i) { IsVip = DetermineVip(i.Score)})
.ToList()
But the downfall here is you will be creating a new Customer object based on each existing object, and not modifying the existing object - this is why I have put "can" in quotes. I do not know if this is truly what you desire.
No, Linq was designed to iterate over collections without affecting the contents of the source enumerable.
You can however create your own method for iterating and mutating the collection:
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach(T item in enumeration)
{
action(item);
}
}
You can then use as follows:
return customers.ToList()
.ForEach(i => i.IsVip = DetermineVip(i.Score))
.ToList();
Note that the first ForEach will clone the source list.
As customers already is a List, you can use the ForEach method:
customers.ForEach(c => c.IsVip = DetermineVip(c.Score));

Categories

Resources