Using linq to query a nullable field - c#

This is my first post, I hope the phrasing of my question makes sense, let me know if not.
I am struggling to write a linq query where the field I'm evaluating is nullable.
I have a collection of objects which has a nullable enum field and an id field (filteredCollection). I also have a collection of enums in a list (SearchCriterionQualificationUnitLevels).
I want to get a subset of the objects collection where the enum field matches one of the values in the list.
At the moment I have the following code which is working but I'd be interested in finding out how to achieve the same result without having to jump through so many hoops.
Dictionary<int, QualificationLevel> unitsWithLevels = filteredCollection.Where(x => x.QualificationLevel != null).ToDictionary(k => k.QualificationUnitID, v => v.QualificationLevel.Value);
var matchingUnits = from unitWithLevel in unitsWithLevels
join unitLevels in SearchCriterionQualificationUnitLevels on unitWithLevel.Value equals unitLevels
select unitWithLevel.Key;
filteredCollection = from unit in filteredCollection
join matchingUnit in matchingUnits on unit.QualificationUnitID equals matchingUnit
select unit;
Thanks in advance

This can be achieved in singe statement (new lines added for readability)
Dictionary<int, QualificationLevel> unitsWithLevels =
filteredCollection
.Where(x => x.QualificationLevel != null
&& SearchCriterionQualificationUnitLevels.Any(unitLevel => unitLevel.Equals(x.QualificationLevel.Value)));

var subset=filteredCollection.Where(i=>SearchCriterionQualificationUnitLevels.Any(l=>l==i.QualificationLevel));

Related

SQL Selection to Array in C#

I am querying in C# for the first time, so please forgive my ignorance. I want to query a table, then place the results in an array/dict/dataframe to then be accessed later. I am unable to run the final code on my end, so this is more of an exercise in setting up the queries for when the final code (a chatbot) works.
Here is the code that should work to get boiling points and melting points seperately. Assume that casnumber is declared in advance (let's just call it str '753')
boiling_point = (from cdls in ADVISORCHEMICALS
where cdls.casnumber == casnumber
select cdls.boiling_point).FirstOrDefault();
melting_point = (from cdls in ADVISORCHEMICALS
where cdls.casnumber == casnumber
select cdls.metling_point).FirstOrDefault();
How would I get the results of the query to an array/dict/dataframe instead?
dict = (from cdls in ADVISORCHEMICALS
where cdls.casnumber == casnumber
select cdls.boiling_point,
cdls.melting_point).FirstOrDefault();
Ideally, I would want {(boiling_point : 200F), (melting_point : 100F)} as output, or something similar in a table/df/array. There are 30+ attributes in the table, so a way to assign key-value pairs or create a dataframe from the query for each attribute queried would be ideal.
Get a list of Tuples like this
var tuples = (from cdls in ADVISORCHEMICALS
where cdls.casnumber == casnumber
select (cdls.boiling_point, cdls.melting_point))
.ToList();
tuples will be a list of tuples (ex. List<(string boiling_point, string melting_point)>)
for (var tuple in tuples)
{
var boiling_point = tuple.boiling_point;
var melting_point= tuple.melting_point;
}

C# Linq Group by Object

I have an issue of using group by in LINQ to SQL statement.
The cod I have is
var combinedItems = (from article in articles
join author in authors
on article.AuthorId equals author.Id into tempAuthors
from tempAuthor in tempAuthors.DefaultIfEmpty()
select new { article , author = tempAuthor});
var groups1 = (from combinedItem in combinedItems
group combinedItem by combinedItem.article into g
select g.Key).ToList();
var groups2 = (from combinedItem in combinedItems
group combinedItem by combinedItem.article.Id into g
select g.Key).ToList();
I tried to group in two different ways. The first way, I group by an object and the second way I just group by a field in one of the objects.
When I run groups1, I got an error saying need to evaluate in client side, while when I use groups2, it works all good. Can I ask what could be wrong? If I want to group by object, is there any way to do it?
In case you want to group by object, as you've not overridden Equals and GetHashCode() in your Article class or implemented IEqualityComparer<Article> you're just getting the default comparison, which checks if the references are equal. So what you need is something like this:
class GroupItemComparer : IEqualityComparer<Article>
{
public bool Equals(Article x, Article y)
{
return x.Id == y.Id &&
x.Name == y.Name;
}
public int GetHashCode(Article obj)
{
return obj.Id.GetHashCode() ^
obj.Name.GetHashCode();
}
}
And then you need to change your query to lambda expression:
var groups1 = combinedItems.GroupBy(c => c.article , new GroupItemComparer())
.Select(c => c.Key).ToList();
In case you got any exception regarding translation your method to SQL, you can use AsEnumerable or ToList methods before your GroupBy method, with this methods after data is loaded, any further operation is performed using Linq to Objects, on the data already in memory.
As others have pointed out, the GroupBy is using reference equality by default, and you could get around it by specifying one or more properties to group by. But why is that an error?
The whole point of the query is to translate your Linq query into SQL. Since object reference equality on the client can't be easily translated to SQL, the translator doesn't support it and gives you an error.
When you provide one or more properties to group by, the provider can translate that to SQL (e.g. GROUP BY article.Id), and thus the second method works without error.

LINQ to EF returning all fields not just those in Select()

This is the gist of my query which I'm testing in LinqPad using Linq to Entity Framework.
In my mind the resultant SQL should begin with something like SELECT TableA.ID AS myID. Instead, the SELECT includes all fields from all of the tables. Needless to say this incurs a massive performance hit among other problems. How can I prevent this?
var AnswerList = this.Answers
.Where(x=>
..... various conditions on x and related entities...
)
.GroupBy(x => new {x.TableA,x.TableB,x.TableC})
.Select(g=>new {
myID = g.Key.TableA.ID,
})
AnswerList.Dump();
In practice I'm using a new type instead of an anonymous one but the results are the same either way.
Let me know if you need me to fill in more of the ...'s.
UPDATE
I've noticed I can prevent this problem by explicitly specifying the fields I want returned in the GroupBy method, e.g. new {x.TableA.ID ... }
But I still don't understand why it doesn't work just using the Select method (which DOES work when doing the equivalent in Linq to SQL).
Hi,
Could you please try below....?
var query = from SubCat in mySubCategory
where SubCat.CategoryID == 1
group 1 by SubCat.CategoryID into grouped
select new { Catg = grouped.Key,
Count = grouped.Count() };
Thank you,
Vishal Patel

Identify items in one list not in another of a different type

I need to identify items from one list that are not present in another list. The two lists are of different entities (ToDo and WorkshopItem). I consider a workshop item to be in the todo list if the Name is matched in any of the todo list items.
The following does what I'm after but find it awkward and hard to understand each time I revisit it. I use NHibernate QueryOver syntax to get the two lists and then a LINQ statement to filter down to just the Workshop items that meet the requirement (DateDue is in the next two weeks and the Name is not present in the list of ToDo items.
var allTodos = Session.QueryOver<ToDo>().List();
var twoWeeksTime = DateTime.Now.AddDays(14);
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime).List();
var matches = from wsi in workshopItemsDueSoon
where !(from todo in allTodos
select todo.TaskName)
.Contains(wsi.Name)
select wsi;
Ideally I'd like to have just one NHibernate query that returns a list of WorkshopItems that match my requirement.
I think I've managed to put together a Linq version of the answer put forward by #CSL and will mark that as the accepted answer as it put me in the direction of the following.
var twoWeeksTime = DateTime.Now.AddDays(14);
var subquery = NHibernate.Criterion.QueryOver.Of<ToDo>().Select(t => t.TaskName);
var matchingItems = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime &&
w.IsWorkshopItemInProgress == true)
.WithSubquery.WhereProperty(x => x.Name).NotIn(subquery)
.Future<WorkshopItem>();
It returns the results I'm expecting and doesn't rely on magic strings. I'm hesitant because I don't fully understand the WithSubquery (and whether inlining it would be a good thing). It seems to equate to
WHERE WorkshopItem.Name IS NOT IN (subquery)
Also I don't understand the Future instead of List. If anyone would shed some light on those that would help.
I am not 100% sure how to achieve what you need using LINQ so to give you an option I am just putting up an alternative solution using nHibernate Criteria (this will execute in one database hit):
// Create a query
ICriteria query = Session.CreateCriteria<WorkShopItem>("wsi");
// Restrict to items due within the next 14 days
query.Add(Restrictions.Le("DateDue", DateTime.Now.AddDays(14));
// Return all TaskNames from Todo's
DetachedCriteria allTodos = DetachedCriteria.For(typeof(Todo)).SetProjection(Projections.Property("TaskName"));
// Filter Work Shop Items for any that do not have a To-do item
query.Add(SubQueries.PropertyNotIn("Name", allTodos);
// Return results
var matchingItems = query.Future<WorkShopItem>().ToList()
I'd recommend
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime)
var allTodos = Session.QueryOver<ToDo>();
Instead of
var allTodos = Session.QueryOver<ToDo>().List();
var workshopItemsDueSoon = Session.QueryOver<WorkshopItem>()
.Where(w => w.DateDue <= twoWeeksTime).List();
So that the collection isn't iterated until you need it to be.
I've found that it's helpfull to use linq extension methods to make subqueries more readable and less awkward.
For example:
var matches = from wsi in workshopItemsDueSoon
where !allTodos.Select(it=>it.TaskName).Contains(wsi.Name)
select wsi
Personally, since the query is fairly simple, I'd prefer to do it like so:
var matches = workshopItemsDueSoon.Where(wsi => !allTodos.Select(it => it.TaskName).Contains(wsi.Name))
The latter seems less verbose to me.

Linq to update a collection with values from another collection?

I have IQueryable<someClass> baseList
and List<someOtherClass> someData
What I want to do is update attributes in some items in baseList.
For every item in someData, I want to find the corresponding item in baselist and update a property of the item.
someOtherClass.someCode == baseList.myCode
can I do some type of join with Linq and set baseList.someData += someOtherClass.DataIWantToConcantenate.
I could probably do this by iteration, but is there a fancy Linq way I can do this in just a couple lines of code?
Thanks for any tips,
~ck in San Diego
To pair elements in the two lists you can use a LINQ join:
var pairs = from d in someData
join b in baseList.AsEnumerable()
on d.someCode equals b.myCode
select new { b, d };
This will give you an enumeration of each item in someData paired with its counterpart in baseList. From there, you can concatenate in a loop:
foreach(var pair in pairs)
pair.b.SomeData += pair.d.DataIWantToConcantenate;
If you really meant set concatenation rather than +=, take a look at LINQ's Union, Intersect or Except methods.
LINQ is for querying - not for updating. That means it'll be fine to use LINQ to find the corresponding item, but for the modification you should be using iteration.
Admittedly you might want to perform some appropriate query to get baseList into an efficient form first - e.g. a Dictionary<string, SomeClass> based on the property you'll be using to find the corresponding item.
You can convert the IQueryable<SomeClass> into a List<SomeClass>, use the ForEach method to loop over it and update the elements, then convert back to IQueryable:
List<SomeClass> convertedList = baseList.ToList();
convertedList.ForEach(sc =>
{
SomeOtherClass oc = someData.First(obj => obj.SomeCode == sc.MyCode);
if (oc != null)
{
sc.SomeData += oc.DataIWantToConcatenate;
}
});
baseList = convertedList.AsQueryable(); // back to IQueryable
But it may be more efficient during this using non-LINQ constructs.
As mentioned before, it should be a combination of loop and LINQ
foreach (var someDataItem in someData)
{
someDataItem.PropertyToUpdate = (baseList.FirstOrDefault(baseListItem => baseListItem .key == someDataItem.key) ?? new SomeClass(){OtherProperty = "OptionalDefaultValue"}).OtherProperty;
}
You can't simply find objects that are in one list but not the other, because they are two different types. I'll assume you're comparing a property called OtherProperty that is common to the two different classes, and shares the same type. In that case, using nothing but Linq queries:
// update those items that match by creating a new item with an
// updated property
var updated =
from d in data
join b in baseList on d.OtherProperty equals b.OtherProperty
select new MyType()
{
PropertyToUpdate = d.PropertyToUpdate,
OtherProperty = d.OtherProperty
};
// and now add to that all the items in baseList that weren't found in data
var result =
(from b in baseList
where !updated.Select(x => x.OtherProperty).Contains(b.OtherProperty)
select b).Concat(updated);

Categories