Handling null array in a linq query - c#

I have a linq query with a contains with an array of values. The issue I am having is if the array is empty/null it breaks my query. How to I handle this within the linq query. My other option was to check the length of the array before executing the linq query but was wondering if its possible to include this check within the query.
Code
// Properties
public string BusinessUnit { get; set; }
public string Category { get; set; }
public string[] SubCategory { get; set; }
var entities = await _DbContext.MasterProductView
.Where(e => e.BusinessUnit == query.BusinessUnit &&
e.Category == query.Category &&
query.SubCategory.Contains(e.SubCategory))
.Select(e => new OptionDto() { Value = e.Brand, Label = e.Brand })
.Distinct()
.OrderBy(e => e.Label)
.ToListAsync(cancellationToken);

Try this:
var entities = await _DbContext.MasterProductView
.Where(e => query.SubCategory.Any() ? e.BusinessUnit == query.BusinessUnit &&
e.Category == query.Category &&
query.SubCategory.Contains(e.SubCategory) : e.BusinessUnit == query.BusinessUnit &&
e.Category == query.Category)
.Select(e => new OptionDto() { Value = e.Brand, Label = e.Brand })
.Distinct()
.OrderBy(e => e.Label)
.ToListAsync(cancellationToken);

You probably don't have problem with empty array, only null ones. You can use the null-conditional operator ?. in conjunction with the null-coalescing operator ??
// Properties
public string BusinessUnit { get; set; }
public string Category { get; set; }
public string[] SubCategory { get; set; }
var entities = await _DbContext.MasterProductView
.Where(e => e.BusinessUnit == query.BusinessUnit &&
e.Category == query.Category &&
query.SubCategory?.Contains(e.SubCategory) ?? false)
.Select(e => new OptionDto() { Value = e.Brand, Label = e.Brand })
.Distinct()
.OrderBy(e => e.Label)
.ToListAsync(cancellationToken);

Related

Get first item from each category

I have 5 categories:
Book, DVD, Rent, Buy, Sell
I use the following query to select the latest count of items.
public async Task<IList<Post>> GetRecentPostsAsync(int count)
{
return await _db.Posts
.Where(e => e.Published.HasValue && e.Published.Value <= DateTime.Today)
.OrderByDescending(e => e.Published)
.Take(count)
.ToListAsync();
}
My Post class looks like this:
public class Post
{
#region Properties
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
[Required]
[StringLength(25)]
public string Category { get; set; }
public DateTime? Published { get; set; }
#endregion
}
This returns my count of items. I want to make a new function where I get the most recently published item from the first 4 categories, so excluding the category Sell. It should return 4 items back.
How can I achieve this?
This should give you back a List<Post> with the last published Post in each category except "Sell":
return await _db.Posts
.Where(x => x.Category != "Sell" && x.Published.HasValue && x.Published.Value <= DateTime.Today)
.GroupBy(x => x.Category)
.Select(x => x.OrderByDescending(x => x.Published).Take(1))
.SelectMany(x => x)
.ToListAsync();
Didn't immediately understand the question. So, try that.
public async Task<IList<Post>> GetRecentPostsAsync()
{
List<string> categories = new List<string> { "Book", "DVD", "Rent", "Buy" };
List<Post> result = new List<Post>();
foreach (string category in categories)
{
Post post = db.Posts.Where(e => e.Published.HasValue && e.Published.Value <= DateTime.Today && e.Category == category)
.OrderByDescending(e => e.Published)
.Take(1).First();
result.Add(post);
}
return result;
}
Try this. This will give you the count by Category excluding Sell
public class PostCategory
{
public string Category { get; set; }
public int Count { get; set; }
}
public async Task<IList<PostCategory>> GetRecentPostsAsync()
{
return await _db.Posts
.Where(e => e.Published.HasValue && e.Published.Value <= DateTime.Today && e.Category != "Sell")
.GroupBy(x => x.Category)
.Select(y => new { Category = y.Key, Count = y.Count() })
.ToListAsync();
}

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.

Cannot implicitly convert type decimal? to OrdersList, are you missing a cast

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

How can I make a LINQ expression return into a class?

I have the following LINQ:
var questionIds = _questionsRepository.GetAll()
.Where(m => m.Problem != null &&
m.Problem.SubTopic != null &&
m.Problem.SubTopic.Topic != null &&
m.Problem.SubTopic.Topic.SubjectId == 1)
.Select(m => m.QuestionId)
.ToList();
Currently it does a select and returns a list of questionIds.
How can I change this LINQ so it returns a List
public class QuestionId
{
public int QuestionId { get; set; }
}
Create an instance of QuestionId class and set QuestionId property inside Select with this:
Select(m => new QuestionId { QuestionId = m.QuestionId })

Normalize objects using linq

I have an IEnumerable<RuleSelection> with these properties:
public class RuleSelection{
public int RuleId { get; set;}
public int? CriteriaId { get; set; }
public int? CriteriaSourceId{ get; set; }
}
RuleId in RuleSelection is not unique.
Can I write a linq query to normalize these into IEnumerable<Rule> which would be:
public class Rule{
public int RuleId { get; set; }
public IEnumerable<int> Criteria { get; set; }
public IEnumerable<int> CriteriaSource { get; set; }
}
Rule.RuleId would be unique and the properties Criteria and CriteriaSource would include all the CriteriaId's and CriteriaSourceId's for the RuleId respectively.
It sounds like you want something like:
var rules = selections.GroupBy(rs => rs.RuleId)
.Select(g => new Rule {
RuleId = g.Key,
Criteria = g.Select(rs => rs.CriteriaId)
.Where(c => c != null)
.Select(c => c.Value)
.ToList(),
CriteriaSource = g.Select(rs => rs.CriteriaSourceId)
.Where(c => c != null)
.Select(c => c.Value)
.ToList(),
});
Using my FullOuterGroupJoin extension method
LINQ - Full Outer Join
you could:
theRules.FullOuterGroupJoin(theRules,
r => r.RuleId,
r => r.RuleId,
(crit, critSource, id) => new Rule {
RuleId = id,
Criteria = crit
.Where(r => r.CriteriaId.HasValue)
.Select(r => r.CriteriaId.Value),
CriteriaSource = critSource
.Where(r => r.CriteriaSourceId.HasValue)
.Select(r => r.CriteriaSourceId.Value),
}
);
To write this:
var rules =
from sel in selections
group sel by sel.RuleId into rg
select new Rule {
RuleId = rg.Key,
Criteria = rg.Select(r => r.CriteriaId).FilterValues(),
CriteriaSource = rg.Select(r => r.CriteriaSourceId).FilterValues(),
};
I created the following FilterValues extension (to eliminate duplication):
public static IEnumerable<T> FilterValues<T>(
this IEnumerable<T?> items)
where T : struct
{
// omitting argument validation
return
from item in items
where item.HasValue
select item.Value;
}
I set out to provide essentially a pure query-syntax version of JonSkeet's answer. I gave up on that in effort to remove duplication for the property assignments and wound up with this combination extension & query-syntax approach.

Categories