I have the following get method:
public ActionResult InstructorEventsAttended()
{
//Populate the list
var instructors = from s in db.Instructors
orderby db.InstructorEvents.Where(x => x.InstructorId == s.InstructorId && x.AttendanceId == 4).Count() descending
select s;
var viewModel = instructors.Select(t => new StatsInstructorEventsAttendedViewModel
{
Name = t.FirstName + " " + t.LastName,
EventsAttendedF = db.InstructorEvents.Where(x => x.InstructorId == t.InstructorId && x.AttendanceId == 4 && x.Event.EventTypeId == 1).Count(),
EventsAttendedFPer = (db.InstructorEvents.Where(x => x.InstructorId == t.InstructorId && x.AttendanceId == 4 && x.Event.EventTypeId == 1).Count() / db.Events.Where(x => x.EventDate >= t.Joined && x.EventTypeId == 1 && x.EventStatusId == 2).Count()) * 100,
});
return PartialView("_InstructorEventsAttended", viewModel);
}
The view model is:
public class StatsInstructorEventsAttendedViewModel
{
public int InstructorId { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "Fundraising")]
public decimal EventsAttendedF { get; set; }
[Display(Name = "Fundraising")]
public decimal EventsAttendedFPer { get; set; }
}
However the Initialiser for EventsAttendedPer calculates as zero because the first count is smaller than the second. I'm aware that the way to fix this is to convert the numbers to decimal but when I try Convert.ToDecimal I get an error:
System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Decimal ToDecimal(Int32)' method, and this method cannot be translated into a store expression.
How can I convert the Counts to Decimal to allow the correct division result?
All of that code boils down to "I want 3 / 2 to return 1.5", right? Cast to a floating point data type, for example double:
(double)myCount1 / (double)myCount2 * 100.0
Entity Framework still does not recognize the Convert.* methods. They are to be avoided anyway. They are a code smell. Usually, a normal cast is the right answer because it is simple.
Related
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;
}
Say I have a class, I want to select multiple objects of it but create one unified object in the end. This is because of the requirement for the collection properties of the object to be combined.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
using Nozomi.Base.Core;
namespace Nozomi.Data.Models.Currency
{
public class Currency : BaseEntityModel
{
public Currency(ICollection<Currency> currencies)
{
if (currencies.Any())
{
var firstCurr = currencies.FirstOrDefault();
if (firstCurr != null)
{
// Doesn't matter...
Id = firstCurr.Id;
CurrencyTypeId = firstCurr.Id;
CurrencyType = firstCurr.CurrencyType;
Abbrv = firstCurr.Abbrv;
Name = firstCurr.Name;
CurrencySourceId = firstCurr.CurrencySourceId;
CurrencySource = firstCurr.CurrencySource;
WalletTypeId = firstCurr.WalletTypeId;
PartialCurrencyPairs = currencies
.SelectMany(c => c.PartialCurrencyPairs)
.DefaultIfEmpty()
.ToList();
}
}
}
[Key]
public long Id { get; set; }
public long CurrencyTypeId { get; set; }
public CurrencyType CurrencyType { get; set; }
public string Abbrv { get; set; } // USD? MYR? IND?
public string Name { get; set; }
public long CurrencySourceId { get; set; }
public Source CurrencySource { get; set; }
// This will have a number if it is a crypto pair to peg to proper entities
public long WalletTypeId { get; set; } = 0;
public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }
public bool IsValid()
{
return !String.IsNullOrEmpty(Abbrv) && !String.IsNullOrEmpty(Name) && CurrencyTypeId > 0 && CurrencySourceId > 0;
}
}
}
Here's what a PartialCurrencyPair is:
namespace Nozomi.Data.Models.Currency
{
/// <summary>
/// Partial currency pair.
/// </summary>
public class PartialCurrencyPair
{
public long CurrencyId { get; set; }
public long CurrencyPairId { get; set; }
public bool IsMain { get; set; } = false;
public CurrencyPair CurrencyPair { get; set; }
public Currency Currency { get; set; }
}
}
So basically, if you want to make EURUSD, you'll have to take two currencies to form a pair. A CurrencyPair is made up of two PartialCurrencyPairs. The reason why we can have many EUR or many USDs is that they come from different sources.
Here's what a CurrencyPair is:
public class CurrencyPair : BaseEntityModel
{
[Key]
public long Id { get; set; }
public CurrencyPairType CurrencyPairType { get; set; }
/// <summary>
/// Which CPC to rely on by default?
/// </summary>
public string DefaultComponent { get; set; }
public long CurrencySourceId { get; set; }
public Source CurrencySource { get; set; }
// =========== RELATIONS ============ //
public ICollection<CurrencyPairRequest> CurrencyPairRequests { get; set; }
public ICollection<WebsocketRequest> WebsocketRequests { get; set; }
public ICollection<PartialCurrencyPair> PartialCurrencyPairs { get; set; }
public bool IsValid()
{
var firstPair = PartialCurrencyPairs.First();
var lastPair = PartialCurrencyPairs.Last();
return (CurrencyPairType > 0) && (!string.IsNullOrEmpty(APIUrl))
&& (!string.IsNullOrEmpty(DefaultComponent))
&& (CurrencySourceId > 0)
&& (PartialCurrencyPairs.Count == 2)
&& (firstPair.CurrencyId != lastPair.CurrencyId)
&& (!firstPair.IsMain == lastPair.IsMain);
}
}
I have an IQueryable to combine into one single currency.
Code with comments (The comments basically tells you what I'm trying to achieve.
var query = _unitOfWork.GetRepository<Currency>()
.GetQueryable()
// Do not track the query
.AsNoTracking()
// Obtain the currency where the abbreviation equals up
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
// Something here that will join the PartialCurrencyPair collection together and create one single Currency object.
.SingleOrDefault();
How do I come about it? Thank you so much in forward! Here's the
progress I've made so far and it works, but I'm pretty LINQ has a beautiful way to make this better and optimised:
var combinedCurrency = new Currency(_unitOfWork.GetRepository<Currency>()
.GetQueryable()
// Do not track the query
.AsNoTracking()
// Obtain the currency where the abbreviation equals up
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
.Include(c => c.PartialCurrencyPairs)
.ThenInclude(pcp => pcp.CurrencyPair)
.ThenInclude(cp => cp.CurrencyPairRequests)
.ThenInclude(cpr => cpr.RequestComponents)
.ThenInclude(rc => rc.RequestComponentDatum)
.ThenInclude(rcd => rcd.RcdHistoricItems)
.ToList());
return new DetailedCurrencyResponse
{
Name = combinedCurrency.Name,
Abbreviation = combinedCurrency.Abbrv,
LastUpdated = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.SelectMany(cp => cp.CurrencyPairRequests)
.SelectMany(cpr => cpr.RequestComponents)
.OrderByDescending(rc => rc.ModifiedAt)
.FirstOrDefault()?
.ModifiedAt ?? DateTime.MinValue,
WeeklyAvgPrice = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.Where(cp => cp.CurrencyPairRequests
.Any(cpr => cpr.DeletedAt == null && cpr.IsEnabled))
.SelectMany(cp => cp.CurrencyPairRequests)
.Where(cpr => cpr.RequestComponents
.Any(rc => rc.DeletedAt == null && rc.IsEnabled))
.SelectMany(cpr => cpr.RequestComponents
.Where(rc =>
rc.ComponentType.Equals(ComponentType.Ask) ||
rc.ComponentType.Equals(ComponentType.Bid)))
.Select(rc => rc.RequestComponentDatum)
.SelectMany(rcd => rcd.RcdHistoricItems
.Where(rcdhi => rcdhi.CreatedAt >
DateTime.UtcNow.Subtract(TimeSpan.FromDays(7))))
.Select(rcdhi => decimal.Parse(rcdhi.Value))
.DefaultIfEmpty()
.Average(),
DailyVolume = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.Where(cp => cp.CurrencyPairRequests
.Any(cpr => cpr.DeletedAt == null && cpr.IsEnabled))
.SelectMany(cp => cp.CurrencyPairRequests)
.Where(cpr => cpr.RequestComponents
.Any(rc => rc.DeletedAt == null && rc.IsEnabled))
.SelectMany(cpr => cpr.RequestComponents
.Where(rc => rc.ComponentType.Equals(ComponentType.VOLUME)
&& rc.DeletedAt == null && rc.IsEnabled))
.Select(rc => rc.RequestComponentDatum)
.SelectMany(rcd => rcd.RcdHistoricItems
.Where(rcdhi => rcdhi.CreatedAt >
DateTime.UtcNow.Subtract(TimeSpan.FromHours(24))))
.Select(rcdhi => decimal.Parse(rcdhi.Value))
.DefaultIfEmpty()
.Sum(),
Historical = combinedCurrency.PartialCurrencyPairs
.Select(pcp => pcp.CurrencyPair)
.SelectMany(cp => cp.CurrencyPairRequests)
.SelectMany(cpr => cpr.RequestComponents)
.Where(rc => componentTypes != null
&& componentTypes.Any()
&& componentTypes.Contains(rc.ComponentType)
&& rc.RequestComponentDatum != null
&& rc.RequestComponentDatum.IsEnabled
&& rc.RequestComponentDatum.DeletedAt == null
&& rc.RequestComponentDatum.RcdHistoricItems
.Any(rcdhi => rcdhi.DeletedAt == null &&
rcdhi.IsEnabled))
.ToDictionary(rc => rc.ComponentType,
rc => rc.RequestComponentDatum
.RcdHistoricItems
.Select(rcdhi => new ComponentHistoricalDatum
{
CreatedAt = rcdhi.CreatedAt,
Value = rcdhi.Value
})
.ToList())
};
Here's the end result I want on that single object: A DetailedCurrencyResponse object.
public class DistinctiveCurrencyResponse
{
public string Name { get; set; }
public string Abbreviation { get; set; }
public DateTime LastUpdated { get; set; }
public decimal WeeklyAvgPrice { get; set; }
public decimal DailyVolume { get; set; }
}
A historical datum is basically a kvp, where the Key (ComponentType) is an enum.
public class DetailedCurrencyResponse : DistinctiveCurrencyResponse
{
public Dictionary<ComponentType, List<ComponentHistoricalDatum>> Historical { get; set; }
}
public class ComponentHistoricalDatum
{
public DateTime CreatedAt { get; set; }
public string Value { get; set; }
}
The query you have outlined will attempt to return you a single Currency object, but given you are looking for any with a given abbreviation, if multiple currency objects share an abbreviation, the SingleOrDefault could error due to multiple returns.
It sounds like you want to define a structure to represent the currency pairs. That structure is not a Currency entity, but a different data representation. These are commonly referred to as ViewModels or DTOs. Once you've defined what you want to return, you can use .Select() to populate that from the Currency and applicable abbreviations.
For instance, if I create a CurrencySummaryDto which will have the currency ID, Abbrevation, and a string containing all of the applicable pairs:
public class CurrencySummaryDto
{
public long CurrencyId { get; set; }
public string Abbreviation { get; set; }
public string Pairs { get; set;}
}
... then the query...
var currencySummary = _unitOfWork.GetRepository<Currency>()
.GetQueryable()
.AsNoTracking()
.Where(c => c.Abbrv.Equals(abbreviation, StringComparison.InvariantCultureIgnoreCase)
&& c.DeletedAt == null && c.IsEnabled)
.Select( c => new {
c.Id,
c.Abbrv,
Pairs = c.PartialCurrencyPairs.Select(pc => pc.PairName).ToList() // Get names of pairs, or select another annonymous type for multiple properties you care about...
}).ToList() // Alternatively, when intending for returning lots of data use Skip/Take for paginating or limiting resulting data.
.Select( c => new CurrencySummaryDto
{
CurrencyId = c.Id,
Abbreviation = c.Abbrv,
Pairs = string.Join(", ", c.Pairs)
}).SingleOrDefault();
This is if you want to do something like combine data from the currency pairs into something like a string. If you're happy to leave them as a collection of simplified data, then the extra anonymous type and .ToList() are not required, just select directly into the Dto structure. This example combines the data into a string where string.Join() is not supported in EF expressions so we have to get our data out into objects to hand over to Linq2Object for the final mapping.
Edit: Ok, you're requirement/example just got a whole lot more complicated with the pair structure, but you should be able to leverage this into the query rather than selecting the entire graph of entities by moving the selection of those values up into the main query... However...
Given the complexity of the data relationships, the approach I would recommend using since this would be assumed to be a read-only result, would be to construct a View in the database to flatten these averages and totals, then bind a simplified entity to this view rather than attempting to manage this with EF Linq. I believe it can be done with linq, but it will be quite onerous to look at, and a view-based summary entity would be a lot cleaner while keeping the execution of this logic to be executed in the database.
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 did a lot of research on stack overflow and none of the answers helped me, i have the following code
public IEnumerable<OrdersList> GetOrdersList(string code)
{
return Repository.Find<OrdersList>(x => x.ProductTitle != "" && x.Code == code);
}
and it works perfectly but now because i have a view in my MSSQL 2014 database that is being used my multiple functions i cant really do much in that view and so i have to do some transforming with LINQ, what i need is to filter out orders that have the highest price and group them by ProductTitle and Code.
The data i have:
when i try the following LINQ syntax:
public IEnumerable<OrdersList> GetOrdersList(string code)
{
return Repository.Find<OrdersList>(x => x.ProductTitle != "" && x.Code == code)
.GroupBy(x => x.MaxPrice);
}
it instantly gives me the following error:
Cannot implicitly convert type decimal? to OrdersList, are you missing a cast
what I'm thinking is that after i do a GroupBy it returns me only the MaxPrice as a single record and that's why it gives me the error, what i need to achieve is this:
I tried adding a GroupBy(x => x.MaxPrice).Select(s => s) and it still throws the same error at design time, Any input on how i can achieve my result would be welcome, thank you in advance.
Entity Framework Generated Model:
class OrdersList
{
public decimal? MaxPrice { get; set; }
public string? Supplier { get; set; }
public string ProductTitle { get; set; }
public string? Code { get; set; }
}
If you want to find a maximum price within the orders having the same Title and Code:
from o in orders
where o.Supplier != null &&
o.ProductTitle != null &&
o.Code != null &&
o.MaxPrice != null
group o by new { o.ProductTitle, o.Code } into g
select new
{
ProductTitle = g.Key.ProductTitle,
Code = g.Key.Code,
MaxPrice = g.Max(x => x.MaxPrice)
};
What as extension methods chain looks like this:
orders.Where(o => o.Supplier != null &&
o.ProductTitle != null &&
o.Code != null &&
o.MaxPrice != null)
.GroupBy(g => new { o.ProductTitle, o.Code })
.Select(g => new
{
ProductTitle = g.Key.ProductTitle,
Code = g.Key.Code,
MaxPrice = g.Max(x => x.MaxPrice)
});
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();