I have an IEnumerable
IEnumerable<Pets> pets;
It consists of
public string Name { get; set; }
public string Other { get; set; }
public decimal? Price { get; set; }
I want to iterate through this and find all prices that are duplicate and set those duplicate prices to null.
Say A cat and a Dog have same price: 10.55. I want to keep first one but remove all remaining prices.
Ways:
1) Remove duplicates (I recommend it):
var filtered = pets.GroupBy(pet => pet.Price).Select(group => group.First());
2) Sort & evalute - set null in place of duplicates as you wish (Are you sure that you want to set nulls instead of removing like in 1) ?).
var newPets = pets.OrderBy(per => pet.Price).ToList();
if (!newPets.Any()) return newPets;
var last = 0;
for (var i = 1; i < newPets.Count; i++)
{
if (newPets[i].Price == newPets[last].Price) newPets[i] = null;
else last = i;
}
return newPets;
I think that ordering is sufficient in that case: O(n * log n) + O(n) against O(n^2) in custom iterates to search duplicates for each element.
3) Classic way (without sorting, slowest)
var newPets = pets.ToList();
for (var i = 0; i < newPets.Count; i++)
{
if (newPets[i] == null) continue;
var price = newPets[i].Price;
for (var j = i + 1; j < newPets.Count; j++)
{
if (newPets[j].Price == price) newPets[j] = null;
}
}
As D Stanley has noticed (but I've missed it) you may have to set Price to null instead of whole record. Then simply change it to decimal? and then write newPets[i].Price = null; instead of null`ing whole record.
Well for starters a decimal can't be null, so I'll answer it as if you had a decimal? type so you understand the process.
Linq is for querying, not updating. You could project a new collection based on the original, but a foreach may be more appropriate:
// list to keep tack of found prices
var prices = new List<decimal>();
foreach(Pet pet in pets)
{
if(prices.Contains(pet.Price.Value))
// price was found - set this one to null
pet.Price = null;
else
// add to the list of "found" prices
prices.Add(pet.Price.Value);
}
public class Pet
{
public string Name { get; set; }
public string Other { get; set; }
public decimal? Price { get; set; }
}
Note that the Price is now nullable (decimal?)
return pets
.OrderBy(x => x.Name)
.GroupBy(x => x.Price)
.OrderBy(x => x.Key)
.SelectMany(x => (new[] { x.First() }).Union(x.Skip(1).Select(n => new Pet { Name = n.Name, Other = n.Other, Price = null })))
.ToList();
Related
I'm having a list of budget units each one containing the following properties:
DateTime Month,
int IdCurrency,
decimal Planned,
int sign, //denotes whether we have income (1) or cost (0)
etc...
Based on given year, I'd like to return a list of objects of the following structure:
public class BudgetBalances
{
public DateTime Month { get; set; }
public int IdCurrency { get; set; }
public decimal Incomes { get; set; }
public decimal Costs { get; set; }
public decimal Balance { get; set; }
}
The first part is easy - I'm getting all budget units for given day from the database, but now I do not know how to make an EF query to:
Get all incomes (sign==1) in currencies within one month, sum them and store it Incomes property
Get all costs (sign==0) and do the same as above
Substract Cost from Income and store it under Balance property
As the result I will have
Jan2022, USD, 3000, 1000, 2000
Jan2022, EUR, 5000, 2000, 3000
etc..
I can always make three level foreach structure, but that is not an effective way to do so. Could you please give me hint how to do it proper way?
That is what I got so far:
public List<BudgetBalances>GetYearlyBudget(int IdOwner, int year)
{
var budgets = _context.Budgets
.Where(_ => _.Month.Year == year && _.IdOwner == IdOwner);
List<BudgetBalances> list = budgets.GroupBy(a => a.Month)
.Select(ls => new BudgetBalances
{
Incomes = ls.Where(_ => _.IsIncome == 1).Sum(_ => _.Planned),
Costs = ls.Where(_ => _.IsIncome == 0).Sum(_ => _.Planned)
}).ToList();
return list;
}
And it calculates each month budget taking into account incomes and costs, but it does not take currencies into consideration. Also I do not know how should I obtain balance value.
Balance = Income - Costs
does not work
Reference this
code sample
using (var context = new MyContext())
{
var result = context.BudgetBalances
.Where(b => b.IdCurrency == 1);
}
Thanks, finally I got what I wanted, here's my code:
public List<BudgetBalances>GetYearlyBudget(int IdOwner, int year)
{
var budgets = _context.Budgets
.Where(_ => _.Month.Year == year && _.IdOwner == IdOwner);
List<BudgetBalances> list = budgets.GroupBy(a => new { a.Month, a.IdCurrency})
.Select(ls => new BudgetBalances
{
IdCurrency = ls.Key.IdCurrency,
CurrencySymbol = _context.Currencies.Where(_=>_.IdCurrency==ls.Key.IdCurrency).FirstOrDefault().CurrencySymbol,
Month = ls.Key.Month,
Incomes = ls.Where(_ => _.IsIncome == 1).Sum(_ => _.Planned),
Costs = ls.Where(_ => _.IsIncome == 0).Sum(_ => _.Planned),
})
.OrderBy(_=>_.Month)
.ToList();
foreach(BudgetBalances ls in list)
{
ls.Balance = ls.Incomes - ls.Costs;
ls.month = ls.Month.ToString("MM/yyyy");
}
return list;
}
The idea is that a Reference Number or Vendor can be passed to the logic and it spits back the query results for the search. I know i'm missing something.. Just can't see it..
The SQL query idea was as follows..
"SELECT ReferenceNumber, VendorName, RequisitionStatus, RequestedOn, sum(RequisitionQTY*RequisitionPrice) as Total FROM partrequisition where ReferenceNumber = 105543 Group By 'ReferenceNumber' "
Can anyone help?
I'm getting the following error:
CS0029 Cannot implicitly convert type
System.Collections.Generic.List<decimal?> to
System.Data.Entity.DbSet<PartsManagement.Models.partrequisition>
(contoller)
public ActionResult Index(RequisitionSearch searchModel)
{
var PartRequisitionInfoLogic = new PartRequisitionInfoLogic(); //This is where any business Logic goes
var Model = PartRequisitionInfoLogic.Getpartrequisitions(searchModel); //This is where it figures out what Search Query is
var RequisisionResults = Model.ToList();
}
(Business Logic)
private wsdpartsmanagementEntities9 db = new wsdpartsmanagementEntities9();
public IQueryable<partrequisition> Getpartrequisitions(RequisitionSearch searchModel)
{
var result = db.partrequisitions.AsQueryable();
if (searchModel != null)
{
if (searchModel.ReferenceNumber != 0 && searchModel.VendorID == 0)
result = result.Where(c => c.ReferenceNumber == searchModel.ReferenceNumber).GroupBy(c => c.ReferenceNumber)
.Select(g => g.Sum(item => item.RequisitionQTY * item.RequisitionPrice)) // For each group, calculate the sum
.ToList();
if (searchModel.ReferenceNumber == 0 && searchModel.VendorID != 0)
result = result.Where(c => c.VendorName == searchModel.VendorID).GroupBy(c => c.ReferenceNumber)
.Select(g => g.Sum(item => item.RequisitionQTY * item.RequisitionPrice))// For each group, calculate the sum
.ToList();
}
return result;
}
(ViewModel(s))
public class RequisitionSearch
{
public int? VendorID { get; set; }
public int? ReferenceNumber { get; set; }
public List<RequisitionResults> SearchResults { get; set; }
public RequisitionSearch()
{
this.SearchResults = new List<RequisitionResults>();
}
}
}
public class RequisitionResults
{
public int ReferenceID { get; set; }
[Display(Name = "Reference Number")]
public int ReferenceNumber { get; set; }
[Display(Name = "Vendor Name")]
public string VendorName { get; set; }
[Display(Name = "Requisition Status")]
public string RequisitionStatus { get; set; }
[Display(Name = "Requisition On")]
public DateTime RequestedOn { get; set; }
[DisplayFormat(DataFormatString = "{0:C}")]
[Display(Name = "Requisition Total")]
public decimal RequisitionTotal { get; set; }
}
Update.. The solution was to scrap the Business logic and replace it with (below) in the ActionResult. Works perfectly.
var result = db.partrequisitions.AsQueryable();
if (searchModel.ReferenceNumber != null || searchModel.VendorID != null)
{
result = result.Where(partrequisition => partrequisition.ReferenceNumber == searchModel.ReferenceNumber || partrequisition.VendorName == searchModel.VendorID);
}
else
{
result = result.Distinct();
}
var b = result.GroupBy(x => x.ReferenceNumber);
var c = b.Select(group => group.Sum(partRequistion => partRequistion.RequisitionQTY * partRequistion.RequisitionPrice));
And to access the query information, a couple of foreach loops..
Have a look at the following line carefully:
result = result.Where(c => c.VendorName == searchModel.VendorID)
.GroupBy(c => c.ReferenceNumber)
.Select(g => g.Sum(item => item.RequisitionQTY * item.RequisitionPrice))// For each group, calculate the sum
.ToList();
You are trying to return a List Of Decimal? values using the Sum method while your method's return type is IQueryable<partrequisition>. So what you need is either changing your method's return type to a List of Decimals? or change your query in order to fulfill what your method actually needs by returning an IQueryable of partrequisition.
If you had split your big query into smaller ones, and used the keyword var a lot less, then you would have seen your problem at compile time:
Alas you forgot to describe your class PartRequisition. From your code it looks similar to:
class PartRequisition
{
public int ReferenceId {get; set;}
public int ReferenceNumber {get; set;}
public decimal? RequisitionQty {get; set;}
public decimal? RequisitionPrice {get; set;}
// one of them might be not a decimal? but a decimal.
}
Your code:
IQueryable<PartRequisition> result = db.partrequisitions.AsQueryable();
result = result
.Where(partRequistion => partRequisition.ReferenceID == searchModel.ReferenceNumber)
.GroupBy(partRequistion => partRequistion.ReferenceNumber)
.Select(group => group.Sum(partRequistion =>
partRequistion.RequisitionQTY * partRequistion.RequisitionPrice))
.ToList();
Let's split this into smaller parts:
IQueryable<PartRequisition> result = ...
IQueryable<PartRequisition> a = result.Where(partRequistion => ... == ...);
IQueryable<IGrouping<int, PartRequisition>> b = a.GroupBy(
partRequistion => partRequistion.ReferenceNumber);
So b is a queryable sequence of groups. Every group has an integer property Key. Each group is also a sequence of PartRequistions, namely all PartRequisitions that have a value of ReferenceNumber equal to the Key.
In the Select, you take each group of your sequence of groups. From every element in this group (which is a PartRequisition) you multiply two decimal? properties, which gives us a decimal? result. After that you sum these results, using Sum<decimal?>
IQueryable<decimal?> c = b.Select(group => group.Sum(partRequistion =>
partRequistion.RequisitionQTY * partRequistion.RequisitionPrice)
Or in even smaller steps:
c is IQueryable>`
Every group is IGrouping<int, PartRequisition>
every partRequisition is a PartRequisition
every RequisitionQTY and RequisitionPrice are decimial?
the result of the multiplication is decimal?
the result of the Sum is decimal?
d is IQueryable
Finally:
List<decimal?> d = c.ToList();
result = d; // remember, result is an IQueryable<PartRequisition>
It is clear to see that you can't assign a List<decimal?> to an IQueryable<PartRequisition>, which was exactly what the error said:
CS0029 Cannot implicitly convert type List to DbSet
Back to your problem
You descirbed a class RequistionResult that you didn't use in method GetRequisitions.
Can it be that you don't want to return an IQueryable<PartRequistions>
but an IQueryable<RequisitionResult>,
where property RequisitionTotal is your calculated Sum?
public IQueryable<RequisitionResult> GetRequisitions(...)
{
IQueryable<PartRequisition> partRequisitions = db.partrequisitions.AsQueryable();
if (searchModel != null)
{
if (searchModel.ReferenceNumber != 0 && searchModel.VendorID == 0)
{
partRequisitions = partRequisitions.Where(...);
}
else if (searchModel.ReferenceNumber == 0 && searchModel.VendorID != 0)
{
partRequisitions = partRequisitions.Where(...);
}
// else use all PartRequisitions
}
else
{
// TODO: decide what to return if searchModel is null, for example
partRequisitions = IQueryable.Empty<PartRequisition>();
// or use all PartRequisitions
}
// from the remaining set of PartRequisitions, convert to RequisitionResults:
IQueryable<RequisitionResult> requisitionResults = partRequisitions
.GroupBy(partRequisition => partRequisition.ReferenceNumber,
// parameter ResultSelector: take each ReferenceNumber and all PartRequisitions
// with this ReferenceNumber to make one new RequisitionResult
(referenceNumber, partRequisitionsWithThisReferenceNumber) => new RequisitionResult
{
RequisitionTotal = partRequisitionsWithThisReferenceNumber
.Select(partRequisition => partRequisition.RequisitionQty * partRequisition.RequisitionPrice)
.Sum(),
// fill other properties
...
});
return requisitionResults;
}
I am attempting to replicate the following SQL query in LINQ using the lambda expression format (to keep it consistent with the code developed so far):
SELECT *
FROM Product p
WHERE p.DateObsolete IS NULL
OR p.DateObsolete > GETDATE()
OR EXISTS (
SELECT NULL
FROM dbo.Product p1
WHERE p1.Ref01 = p.Ref01
AND p1.Ref02 = p.Ref02
AND p1.Ref03 = p.Ref03
AND p1.Version = p.Version + 1
AND p1.DateApproved IS NULL
)
Having looked at other questions (Linq subquery same table using lambda was the closest I could find but but didn't show how to "or" conditions) on SO and elsewhere I thought the following would work but it just causes a stack overflow (seriously) exception and a message about a pdb file not being loaded, which I think is a bit of a red herring.
products = products
.Where(p => !p.DateObsolete.HasValue
|| p.DateObsolete > DateTime.Now
|| products.Any(p1 => p1.Ref01 == p.Ref01
&& p1.Ref02 == p.Ref02
&& p1.Ref03 == p.Ref03
&& p1.Version == p.Version + 1
&& p1.DateApproved == null));
products is an IQueryable variable.
Product is defined in this context as DbSet of:
public class Product
{
public int ProductID { get; set; }
[MaxLength(200)]
public string Name { get; set; }
[MaxLength(3)]
public string Ref01 { get; set; }
[MaxLength(3)]
public string Ref02 { get; set; }
public int Ref03 { get; set; }
public int Version { get; set; }
public DateTime? DateReceivedByGraphics { get; set; }
public DateTime? DateApproved { get; set; }
public DateTime? DateObsolete { get; set; }
public bool Deleted { get; set; }
public bool Discontinued { get; set; }
}
As you may gather I'm new to LINQ (and to posting questions on SO) so any help would be gratefully received.
You might be able to accomplish this with a Join.
DateTime date = DateTime.Today; // or .Now
var products = context.Products.Join(context.Products,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { Product = p1, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.Product.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.Product)
.ToList();
This joins Product to itself on Ref 1-3, but then selects the "left" side project, along with it's version, the "right" side's version and date approved. The Where condition isolates cases where the "right" version is 1 greater than the left and has no date approved. The result will be the "left" products that have counterparts that match those criteria.
Update:
If you have already filtered the products down to a known set of applicable products, then this will work against Objects. For example:
// given products is an IQueryable representing the filtered products...
DateTime date = DateTime.Today; // or .Now
var productList = products.ToList(); // Materialize the EF Queryable into list of entities.
productList = productList.Join(productList,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { Product = p1, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.Product.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.Product)
.ToList();
If your goal is to try and keep this as an IQueryable scoped to EF then I'd suspect that if it's possible, it might not be worth the complexity/time. Worst-case if you did want to preserve the IQueryable, use the above to select Product IDs into a list, then apply that list as a filter against the IQueryable.
var productList = products.ToList(); // Materialize the EF Queryable into list of entities.
// Fetch a list of applicable product IDs.
var productIds = productList.Join(productList,
p1 => new { p1.Ref01, p1.Ref02, p1.Ref03 },
p2 => new { p2.Ref01, p2.Ref02, p2.Ref03 },
(p1, p2) => new { ProductId = p1.ProductId, DateObsolete = p1.DateObsolete, p1.Version, JoinedVersion = p2.Version, JoinedDateApproved = p2.DateApproved } )
.Where(x=> x.DateObsolete > date && x.JoinedVersion == x.Version+1 && !x.JoinedDateApproved.HasValue)
.Select(x=>x.ProductId)
.ToList();
// Filter the original IQueryable.
products = products.Where(x => productIds.Contains(x.ProductId));
It was as Aleks Andreev and Ivan Stoev suggested that assigning the expression to a new variable sorted out the problem. I'm not sure why this didn't work the first time but my guess is that, after completing the query I tried to re-assign the result back to the original variable - in order not to have to change the variable name in all the code that followed my change.
i have a problem with filtering data in LINQ , here is my Model :
public class CoursePlan
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Semester { get; set; }
public string ModuleCode { get; set; }
public string ModuleName { get; set; }
public string Credits { get; set; }
public string OrderNumber { get; set; }
public string ModuleStatus { get; set; }
}
and here is my data Json
the problem here some modules having same OrderNumber which mean they are optional , student must study one of them and if student already study one of them , i should ignore other modules in same order number.
in other way to describe the question
i want to return a list of CoursePlan and on this list if there is two items having same OrderNumber check the ModuleStatus for each one of them and if any one is Completed remove other modules on that order otherwise return them all .
here is my code
var coursePlanList = await _sqLiteAsyncConnection.Table<CoursePlan>().ToListAsync();
var groupedData = coursePlanList.OrderBy(e => e.Semester)
.GroupBy(e => e.OrderNumber)
.Select(e => new ObservableGroupCollection<string, CoursePlan>(e))
.ToList();
for now im solving this by this algorithm and not sure if it's the best
var coursePlanList = await _sqLiteAsyncConnection.Table<CoursePlan>().ToListAsync();
List<CoursePlan> finalList = new List<CoursePlan>();
var counter = 0;
foreach (var itemPlan in coursePlanList)
{
if (counter > 0 && counter < coursePlanList.Count)
if (itemPlan.OrderNumber == coursePlanList[counter - 1].OrderNumber)
{
if (itemPlan.ModuleStatus == "Completed")
{
finalList.RemoveAll(a => a.OrderNumber == itemPlan.OrderNumber);
finalList.Add(itemPlan);
}
Debug.WriteLine(itemPlan.ModuleName + "With -->" + coursePlanList[counter - 1].ModuleName);
}
else
finalList.Add(itemPlan);
counter++;
}
var groupedData = finalList.OrderBy(e => e.ModuleStatus)
.ThenBy(e => e.Semester)
.GroupBy(e => e.Semester)
.Select(e => e)
.ToList();
CoursePlanViewList.BindingContext = new ObservableCollection<IGrouping<string, CoursePlan>>(groupedData);
Any advise or guidance would be greatly appreciated
Let me rephrase your requirement: you want to show all plans per OrderNumber that meet the condition: none of the plans in their group should be "Completed" or the plans themselves should be "Completed". All this grouped by Semester:
var plansQuery =
from p in _sqLiteAsyncConnection.Table<CoursePlan>()
group p by p.Semester into sem
select new
{
PlansInSemester =
from p in sem
group p by p.OrderNumber into gp
select new
{
PlansInOrderNumber =
gp.Where(p => !gp.Any(p1 => p1.ModuleStatus == "Completed")
|| p.ModuleStatus == "Completed")
}
};
This gives you an IQueryable that produces the course plans you want to select, but grouped in two levels, so the final result is obtained by flattening the query twice:
var coursePlanList = await plansQuery
.SelectMany(x => x.PlansInSemester
.SelectMany(y => y.PlansInOrderNumber)).ToListAsync()
I have a class like this:
class item
{
public string itemID { get; set; }
public string itemName { get; set; }
public decimal itemPrice{ get; set; }
}
I have a lists like this:
List<item> myItems = new List<item>();
I want to get the id of the item with minimum price.
I get the minimum price like this:
var minprice = myItems.Min(itemobj => itemobj.itemPrice);
How can I get the Item ID of the object which is having minprice? Or in other words itemID of the object which is:
itemPrice == minprice;
Try this :
Try to get the whole item
var minPriceItem = myItems.OrderBy(i => i.itemPrice).FirstOrDefault();
Then just use it's properties:
decimal price = minPriceItem.itemPrice;
string id = minPriceItem.itemID;
I would probably do it like this:
var minprice = myItems.Min(itemobj => itemobj.itemPrice);
// use .First instead of .Single in case there are 2 or more items with the same min price
// use .FirstOrDefault instead of .First in case the sequence is empty
var itemWithMinPrice = myItems.FirstOrDefault(itemobj => itemobj.itemPrice == minprice);
If you want all the items with the minimum price:
var itemsWithMinPrice = myItems.Where(itemobj => itemobj.itemPrice == minprice);
You can also go advanced with the aggregate function
var itemWithMinPrice = myItems.Aggregate((cheapestItem, nextItem) => (cheapestItem == null || cheapestItem.itemPrice > nextItem.itemPrice) ? nextItem : cheapestItem);
Or you can use MoreLinq (I can recommend this, MoreLinq is fantastic)
var itemWithMinPrice = myItems.MinBy(itemobj => itemobj.Price);