Using LINQ in my Business Logic, but getting an error - c#

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;
}

Related

Check if string has any of List<string> values

I have a class that has list of strings as a property:
public class FiltersDto
{
public int? MinPrice { get; set; }
public int? MaxPrice { get; set; }
public int? BathroomCount { get; set; }
public int? BedroomCount { get; set; }
public string Amenities { get; set; }
public List<string> Neighborhoods { get; set; }
}
In this method I try to check if the word "Neighborhood" is in the list
var sales = await
_propertyRepository
.GetAll()
.Include(x => x.PropertyType)
.WhereIf(input.Neighborhoods != null, x => x.Neighborhood.Contains(???????))
.WhereIf(input.MinPrice.HasValue && input.MaxPrice.HasValue, x => input.MinPrice <= x.Price && x.Price <= input.MaxPrice)
.WhereIf(input.BedroomCount.HasValue, x => x.BedroomCount == input.BedroomCount)
.WhereIf(input.BathroomCount.HasValue, x => x.BathroomCount == input.BathroomCount)
.Where(x => x.PropertyTypeId == 1)
.ToListAsync();
I need to check it in this row .WhereIf(input.Neighborhoods != null, x => x.Neighborhood.Contains(?))
How I can do this?
Hard to tell exactly form what is described, but if you have an instance of FiltersDto called myFiltersDto and it has a .Neighborhoods list like is like { "Red", "Blue", "Green"} and your x's .Neighborhood string is "Blah Red Sand"
x => myFiltersDto.Neighbourhoods.Any(n => x.Neighborhood.Contains(n))
i.e. you're asking "do any of these Neighborhoods n appear within the string held in x.Neighborhoods"
If you don't intend for the Contains to be a within-string string search, and e.g. x.Neighborhood will merely be "Red" and you're seeking an exact match in the collection of strings represented by Neighborhoods then it's
x => myFiltersDto.Neighbourhoods.Contains(x.Neighborhood))
I'd recommend to try and avoid using the word "Contains" when describing this, because it generates confusion between string.Contains (substring search) and list.Contains (element search)

How to merge object properties of the same class together via LINQ

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.

EF Core: Async nested select within IGrouping causing ArgumentException

Updated with other relevant classes
public class TrendItem
{
public string ItemTitle { get; set; }
public IEnumerable<string> ItemValues { get; set; }
}
public class TrendValue
{
public int Id { get; set; }
public TrendResultType TrendType { get; set; }
public string Value { get; set; }
public Trend Trend { get; set; } // contains DateRecorded property
}
Please see the function below that leverages on EF Core (2.1):
public async Task<List<TrendItem>> GetTrends(
int companyId,
TrendResultType type,
DateTimeOffset startDate,
DateTimeOffset endDate,
RatingResultGroup group
)
{
var data = _dataContext.TrendValues.Where(rr =>
rr.Trend.CompanyId == companyId &&
rr.TrendType == type &&
rr.Trend.DateRecorded >= startDate &&
rr.Trend.DateRecorded <= endDate);
return await data.GroupBy(rr => new { rr.Trend.DateRecorded.Year, rr.Trend.DateRecorded.Month })
.Select(g => new TrendItem() { ItemTitle = $"{g.Key.Year}-{g.Key.Month}", ItemValues = g.Select(rr => rr.Value) })
.ToListAsync();
}
I'm getting problems, specifically with the portion g.Select(rr => rr.Value), where I intended to select a collection of values (strings).
Whenever I try to change that to something else like g.Sum(rr => int.Parse(rr.Value)), it works fine. It's the retrieval of the collection that seems to be a problem.
I always get ArgumentException: Argument types do not match.
Is this due to the async function?
Hmm, I'd start by looking to simplify the data retrieval into an anonymous type to perform the grouping, then transform into the view model. It may be that EF is getting a bit confused working with the grouped items values.
It looks like you want to group by date (year-month) then have a list of the values in each group:
var query = _dataContext.TrendValues
.Where(rr =>
rr.Trend.CompanyId == companyId
&& rr.TrendType == type
&& rr.Trend.DateRecorded >= startDate
&& rr.Trend.DateRecorded <= endDate)
.Select(rr => new
{
TrendYear = rr.Trend.DateRecorded.Year,
TrendMonth = rr.Trend.DateRecorded.Month,
rr.Value
}).ToListAsync();
var data = query.GroupBy(rr => new { rr.TrendYear, rr.TrendMonth })
.Select(g => new TrendItem() { ItemTitle = $"{g.Key.Year}-{g.Key.Month}", ItemValues = g.ToList().Select(rr => rr.Value).ToList() })
.ToList();
return data;
which could be simplified to:
var query = _dataContext.TrendValues
.Where(rr =>
rr.Trend.CompanyId == companyId &&
rr.TrendType == type &&
rr.Trend.DateRecorded >= startDate &&
rr.Trend.DateRecorded <= endDate)
.Select(rr => new
{
TrendDate = rr.Trend.DateRecorded.Year + "-" + rr.Trend.DateRecorded.Month,
rr.Value
}).ToListAsync();
var data = query.GroupBy(rr => rr.TrendDate)
.Select(g => new TrendItem() { ItemTitle = $"{g.Key}", ItemValues = g.ToList().Select(rr => rr.Value).ToList() })
.ToList();
return data;
The selecting of the TrendDate in the query may need some work if Trend.Year and Trend.Month are numeric fields.

LINQ how to Filter the data based on the value of the properties

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()

Divide small number by large gives 0 in Linq Query object Initializer

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.

Categories