How to get IEnumerable<Foo> instead of IEnumerable<string> from LINQ? - c#

I am stuck on a LINQ query where I have being trying to return a list records from an SQL Table using EntityFramework 6, instead of getting that list, I keep end up getting an IEnumerable<string[]>.
This is what I have made to get IEnumerable<string[]>. I have an column in the table that needs to be split and then I test to see if a list contains those strings : The value in the table column columnOne can be something like this "a-b" or "b-c" etc, hence the need to use this s.columnOne.Split('-')
list<string> checkList = new list<string>();
checkList.add("a")
checkList.add("b")
checkList.add("c")
List<Foo> fooList = dbContext.Foos.ToList();
IEnumerable<string[]> items = fooList.Select(s => s.columnOne.Split('-'));
var result = items.SelectMany(x => x)
.Where(s => checkList.Contains(s)).ToList();
The above works as it should, but in the end it returns a list of string, which is not what I need.
I tried this below:
List<Foo> fooList = dbContext.Foos.ToList();
var test = fooList.Where(s => s.columnOne.Split('-'));
And this is where I run into the error and can go no further as I am ultimately trying to return a list of <Foo>, not <string>
Now I know that the Where clause needs to return a bool value such as fooList.Where(s => s.columnOne == "someString");, but I am at a loss as to how to go about structuring the LINQ query to get the results I am trying to get.
Any shove in the right direction would be great.

The where clause needs to contain the check against the checkList
var test = fooList.Where(foo => foo.columnOne.Split('-').Any(str => checkList.Contains(str)));

dbContext.Foos
.ToList()
.Where(foo => foo.columnOne.Split('-')
.Any(x => checkList.Contains(x))
I hope you don't ever have very many Foos because that ToList function will read them all into memory. You'll need that, though, because I don't think Linq can understand how to translate that where statement. There are a few ways to get around that, if it's a problem to you.
Also, consider using a Set<string> instead of a List<string> for your checklist variable, as it'll perform better for membership checks like this one.

Related

Getting List<List<int>> but want just List<int>

I have the following variable,
var actionIds = formFields.Select(x => x.GridDefinition.Actions.ToList().Select(y =>y.DFActionID).ToList()).ToList();
It is giving me the result as List<List<int> but I want the result as List<int>
How should I go about it?
Later I need to check IDs from the above list with a single ID like the below.
var removedFormFieldsActions = existingFormFieldsActions.Where(x => !actionIds.Contains(x.DFActionID));
Which is currently giving an error.
There are a couple of problems in your code. First of all, why convert Actions to a list if you are going to map each action right after that? So, the first fix is to remove that ToList() call:
var actionIds = formFields
.Select(x => x.GridDefinition.Actions.Select(y => y.DFActionID).ToList())
.ToList();
Then the second improvement comes, which stems from the fundamental question - what do you want to get in the end? Are all those items in all the separate lists the same, and should be placed into a single list? If so, then you need to use the SelectMany() operator, which, again, does not require the intermediate ToList() since the final ToList() will collect all the items anyway:
var actionIds = formFields
.SelectMany(x => x.GridDefinition.Actions.Select(y => y.DFActionID))
.ToList();

LINQ TO Entities OrderBy Conundrum

I am having trouble trying to understand how to perform an order by in a LINQ to Entities call to return data organized in the desired order. The database used is postgresql. The order by in postgres is:
SELECT
*
FROM
part
ORDER BY
split_part(partnumber, '-',1)::int
, split_part(partnumber, '-',2)::int
Partnumber is a string field which is formated into 2-3 segments which are numeric separated by '-'. eg:
1-000235
10-100364
9-123456
etc.
I would want the sorted result to return:
1-000235
9-123456
10-100364
I have a test VB.Net app I am trying to figure out how to do this:
Using ctx As New EFWeb.MaverickEntities
Dim myparts = ctx.parts.
OrderBy(Function(e) e.partnumber).
ToList()
For Each pt As part In myparts
Console.WriteLine("{0} - {1}", pt.partnumber, pt.description)
Next
End Using
I tried doing: CInt(e.partnumber.Split("-")(0)) to force sorting for the first segment of the partnumber, but errored out because of the the compiler did not like the array referrence for the result of the Split() call.
If anybody knows of a good current reference for LINQ to Entities ... that would be appreciated.
You didn't share your Linq code. Anyway I would get the data to client side and then do the ordering. In C#:
var result = ctx.Parts.AsEnumerable()
.Select(p => new {p, pnSplit = p.PartNumber.Split('-')})
.OrderBy(x => int.Parse(x.pnSplit[0]))
.ThenBy(x => int.Parse(x.pnSplit[1]))
.Select(x => x.p);
In VB it should be:
Dim result = ctx.Parts.AsEnumerable()
Select(Function(p) New With {p, .pnSplit = p.PartNumber.Split("-"c)}).
OrderBy(Function(x) Integer.Parse(x.pnSplit(0))).
ThenBy(Function(x) Integer.Parse(x.pnSplit(1))).
Select(Function(x) x.p)
Note the integer.Parse. Otherwise it would be alphabetic sort.

Linq return all records from entity where a field contains one or more words from a list

Given a List<string> How to return all records in an entity that has a field containing one or more words from the list.
I tried the below which does not work and I'm starting to go around in circles a bit:
List<string> searchwords = new List<string>() {"word1","word2"};
var results = context.activities
.Where(a => a.Title.Contains(searchwords.Any().ToString())).ToList();
The problem with your current code:
var results = context.activities.Where(a =>
a.Title.Contains(searchwords.Any().ToString())).ToList();
is that you have your needle and haystack backwards. The fact that you needed to call .ToString() on Any() should have tipped you off. Any() returns a bool, which you're casting to a string, so you're just checking whether Title contains the string "True". Definitely not what you want. You want something closer to:
var results = context.activities
.Where(a => searchwords.Any(searchWord => a.Title.Contains(searchWord)));

Select multiple types in LINQ Select

Can this be turned into a select statement?
foreach (var gf in CreateGenericFieldsOnInspection(model))
{
simpleInspection.GenericFields.Add(gf.GenericFieldDefinition.Name,
new LC360Carrier.Domain.Models.Import.GenericField
{
GenericFieldType = GenericFieldValueType.Text,
Value = gf.Value
});
}
It looks like GenericFields is a Dictionary<string, GenericFieldOrSomething>. You could contort this into something really weird for the sake of using LINQ. But the purpose of LINQ is to query one or more IEnumerables to either get a result or transform them into something else.
It's just like SQL. You query it to either get a set of records or some value like the sum of some numbers.
In your case you've already got a result set - whatever CreateGenericFieldsOnInspection(model) returns. It makes sense to do what you're already doing - foreach through the results and perform some action on each one of them.
LINQ would be handy if you needed to query that set. For example,
var filteredProperties = CreateGenericFieldsOnInspection(model)
.Where(property => property.Name.StartsWith("X"));
But even then, once you had that collection, it would still make sense to use a foreach loop.
You'll see this sometimes - I did it when I first learned LINQ:
CreateGenericFieldsOnInspection(model).ToList()
.ForEach(property => DoSomethingWith(property));
We convert something to a List because then we can use .ForEach. But there's no benefit to it. It's just foreach with different syntax and an extra step.
I have an extension method that permits add. I was just having trouble w the syntax. Bagus Tesa, above was also helpful. Thanks.
simpleInspection.GenericFields = simpleInspection.GenericFields.Union(CreateGenericFieldsOnInspection(model).Select(x => new KeyValuePair<string, object>(x.GenericFieldDefinition.Name, new LC360Carrier.Domain.Models.Import.GenericField
{
GenericFieldType = GenericFieldValueType.Text,
Value = x.Value
}))).ToDictionary(x => x.Key, x => x.Value);

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.

Categories