Extracting text from an IEnumerable<T> - linq

I have a IEnumerable<T> collection with Name, FullName and Address.
The Address looks like this:
Street1=54, Street2=redfer street, Pin=324234
Street1=54, Street2=fdgdgdfg street, Pin=45654
Street1=55, Street2=tryry street, Pin=65464
I want to loop through this collection and print only those Names, FullNames whose Street1=54
How can i do it in LINQ?
Ok I was able to do this to extract Street1 of the Address
coll.Address.Split(",".ToCharArray())[0]returns me Street1=54 .
Now how do I add this to the condition and print only those Name, FullName whose Street1=54

Based on your update, you can adapt Jared Par's code this way:
var result = collection.Where(x => x.Address.Contains("Street1=54"));
foreach ( var cur in result ) {
Console.WriteLine(string.Format("{0}, {1}", cur.Name, cur.FullName));
}
If you want to be able to plug in your Street1 value with a variable, then do this:
var street1 = "54";
var result = collection.Where(x => x.Address.Contains("Street1=" + street1 ));
foreach ( var cur in result ) {
Console.WriteLine(string.Format("{0}, {1}", cur.Name, cur.FullName));
}
BTW, you really should update your question or add a comment to a specific answer rather than adding a new answer that isn't.

Try this
var result = collection.Where(x => x.Address.Street1==54);
foreach ( var cur in result ) {
Console.WriteLine(var.Name);
}

Select the correct list of :
IList<T> matches = myListOfEnumerables.Where(m => m.Street1 == 54).ToList();
Then loop and print.

Actually the record looks like this:
{Name="Jan" FullName="Kathy Jan" Address="Street1=54, Street2=redfer street, Pin=324234"}
I have to loop through this collection and print only those Names, FullNames whose Street1=54

If the updated information is accurate, you should change the way you store the data.
It looks like you've packed the address information into a string. Why not store it as an object. In fact, why not just as more fields in the same object as the Name and FullName? (and why duplicate the first-name information?)
public class Person
{
public string FirstName, LastName, Street1, Street2, Pin;
}
IEnumerable<Person> persons = GetAllPersonsSomehow();
foreach (Person person in persons.Where(p => p.Street1 == "54"))
Console.WriteLine(person.LastName + ", " + person.FirstName);
Assuming you have to keep the address information in a string, you need a parser for it.
public static IDictionary<string, string> GetAddressFields(string address)
{
return address.Split(',').ToDictionary(
s => s.Substring(0, s.IndexOf('=')).Trim(),
s => s.Substring(s.IndexOf('=') + 1).Trim());
}
foreach (Person person in persons.Where(p =>
GetAddressFields(p.Address)["Street1"] == "54"))
Console.WriteLine(person.LastName + ", " + person.FirstName);

So, what you could do is that you could write a generator for parsing the Address field and then enumerating properties of that. This is a fairly common thing in the functional programming world.
To be fair you would want this code to be lazy in that it would only compute a minimal set. I'm gonna suggest some code from the BCL but you can (and probably should) rewrite the same helper methods with generators.
public static IEnumerable<KeyValuePair<string,string>> NameValueSplit( this string s )
{
foreach (var x in s.Split(','))
{
var y = x.Split(new char[] { '=' }, 2, StringSplitOptions.None);
yield return new KeyValuePair<string, string>(y[0].TrimStart(), y[1].TrimEnd());
}
}
With that helper function you can write code like this
var result = collection.Where(x => x.Address
.NameValueSplit().Any(x => x.Key == "Street1" && x.Value == "54"));
foreach ( var item in result )
{
Console.WriteLine(item.Name);
}
Now this code will not run on your SQL Server if you were thinking of that, but you could write a WHERE clause where you would search the Address field for a sub string %Street1=54%. I like lazy evaluation for string operations and think that's a lacking feature in the BCL. That is why I suggested that kind of solution.

Related

LINQ GroupBy on single property

I am just not understanding the LINQ non-query syntax for GroupBy.
I have a collection of objects that I want to group by a single property. In this case Name
{ Id="1", Name="Bob", Age="23" }
{ Id="2", Name="Sally", Age="41" }
{ Id="3", Name="Bob", Age="73" }
{ Id="4", Name="Bob", Age="34" }
I would like to end up with a collection of all the unique names
{ Name="Bob" }
{ Name="Sally" }
Based on some examples I looked at I thought this would be the way to do it
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
But I ended up with a collection with one item. So I though maybe I was over complicating things with the projection. I tried this
var uniqueNameCollection = Persons.GroupBy(x => x.Name).ToList();
Same result. I ended up with a single item in the collection. What am I doing wrong here? I am just looking to GroupBy the Name property.
var names = Persons.Select(p => p.Name).Distinct().ToList()
If you just want names
LINQ's GroupBy doesn't work the same way that SQL's GROUP BY does.
GroupBy takes a sequence and a function to find the field to group by as parameters, and return a sequence of IGroupings that each have a Key that is the field value that was grouped by and sequence of elements in that group.
IEnumerable<IGrouping<TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> sequence,
Func<TSource, TKey> keySelector)
{ ... }
So if you start with a list like this:
class Person
{
public string Name;
}
var people = new List<Person> {
new Person { Name = "Adam" },
new Person { Name = "Eve" }
}
Grouping by name will look like this
IEnumerable<IGrouping<Person>> groups = people.GroupBy(person => person.Name);
You could then select the key from each group like this:
IEnumerable<string> names = groups.Select(group => group.Key);
names will be distinct because if there were multiple people with the same name, they would have been in the same group and there would only be one group with that name.
For what you need, it would probably be more efficient to just select the names and then use Distinct
var names = people.Select(p => p.Name).Distinct();
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
Appears valid to me. .net Fiddle showing proper expected outcome: https://dotnetfiddle.net/2hqOvt
Using your data I ran the following code statement
var uniqueNameCollection = people.GroupBy(x => x.Name).Select(y => y.Key).ToList();
The return results were List
Bob
Sally
With 2 items in the List
run the following statement and your count should be 2.
people.GroupBy(x => x.Name).Select(y => y.Key).ToList().Count();
Works for me, download a nugget MoreLinq
using MoreLinq
var distinctitems = list.DistinctBy( u => u.Name);

Complex foreach loop possible to shorten to linq?

I have a cluttery piece of code that I would like to shorten using Linq. It's about the part in the foreach() loop that performs an additional grouping on the result set and builds a nested Dictionary.
Is this possible using a shorter Linq syntax?
var q = from entity in this.Context.Entities
join text in this.Context.Texts on new { ObjectType = 1, ObjectId = entity.EntityId} equals new { ObjectType = text.ObjectType, ObjectId = text.ObjectId}
into texts
select new {entity, texts};
foreach (var result in q)
{
//Can this grouping be performed in the LINQ query above?
var grouped = from tx in result.texts
group tx by tx.Language
into langGroup
select new
{
langGroup.Key,
langGroup
};
//End grouping
var byLanguage = grouped.ToDictionary(x => x.Key, x => x.langGroup.ToDictionary(y => y.PropertyName, y => y.Text));
result.f.Apply(x => x.Texts = byLanguage);
}
return q.Select(x => x.entity);
Sideinfo:
What basically happens is that "texts" for every language and for every property for a certain objecttype (in this case hardcoded 1) are selected and grouped by language. A dictionary of dictionaries is created for every language and then for every property.
Entities have a property called Texts (the dictionary of dictionaries). Apply is a custom extension method which looks like this:
public static T Apply<T>(this T subject, Action<T> action)
{
action(subject);
return subject;
}
isn't this far simpler?
foreach(var entity in Context.Entities)
{
// Create the result dictionary.
entity.Texts = new Dictionary<Language,Dictionary<PropertyName,Text>>();
// loop through each text we want to classify
foreach(var text in Context.Texts.Where(t => t.ObjectType == 1
&& t.ObjectId == entity.ObjectId))
{
var language = text.Language;
var property = text.PropertyName;
// Create the sub-level dictionary, if required
if (!entity.Texts.ContainsKey(language))
entity.Texts[language] = new Dictionary<PropertyName,Text>();
entity.Texts[language][property] = text;
}
}
Sometimes good old foreach loops do the job much better.
Language, PropertyName and Text have no type in your code, so I named my types after the names...

Entity Framework - LINQ selection in POCO generic List property

I'm having some issues setting a generic list property of a POCO object when from an EF context. For instance I have a very simple object that contains the following:
public class foo
{
public string fullName;
public Entity entity;
public List<SalesEvent> eventList;
}
My code to populate this object from looks something like this:
.Select(x => new foo()
{
fullName = x.vchFirstName + " " + x.vchLastName,
entity = new EntityVo()
{
address1 = x.vchAddress1,
entityId = x.iEntityId,
emailAddress = x.vchEmailAddress,
firstName = x.vchFirstName,
lastName = x.vchLastName,
city = x.vchCity,
state = x.chState,
workNumber = x.vchWorkNumber,
mobileNumber = x.vchMobileNumber,
siteId = x.iSiteId
}
eventList = _context.Events
.Where(e => e.iEntityId == x.iEntityId
&& e.iStatusId >= eventStatusMin
&& e.iStatusId <= eventStatusMax)
.Select(e => new List<SalesEventMatchVo>
{
new SalesEventMatchVo()
{
vehicleName = _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchMake + " " + _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchModel,
eventId = e.iEventId,
salesPerson = e.chAssignedTo,
eventStatusDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iStatusId).FirstOrDefault().vchParameterDesc,
eventStatusId =(int)e.iStatusId,
eventSourceDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iSourceId).FirstOrDefault().vchParameterDesc,
createDate = e.dtInsertDate
}
}).FirstOrDefault()
}).ToArray();
This issue I'm having is that I'm unable to populate the eventList property with all of the events, it's only grabbing the first record(which makes sense looking at the code). I just cant seem to figure out to populate a the entire list.
Is there a reason simply removing the FirstOrDefault at the end isn't the solution here? I feel like I might be misunderstanding something.
EDIT:
I think I see what you are trying to do. The issue is that you are creating a list in the select statement, when the select statement works only over one thing at a time. It is basically mapping an input type to a new output type.
Try something like this instead:
eventList = _context.Events.Where(e => e.iEntityId == x.iEntityId && //FILTER EVENTS
e.iStatusId >= eventStatusMin &&
e.iStatusId <= eventStatusMax)
.Select(e => new SalesEventMatchVo() //MAP TO SALESEVENT
{
vehicleName = _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchMake + " " + _context.Quotes.Select(q=>q).Where(q=>q.iEventId == e.iEventId).FirstOrDefault().vchModel,
eventId = e.iEventId,
salesPerson = e.chAssignedTo,
eventStatusDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iStatusId).FirstOrDefault().vchParameterDesc,
eventStatusId =(int)e.iStatusId,
eventSourceDesc=_context.RefDefinitions.Select(r=>r).Where(r=>r.iParameterId==e.iSourceId).FirstOrDefault().vchParameterDesc,
createDate = e.dtInsertDate
})
.ToList() //CONVERT TO LIST
As a side note, unless you actually need a List for some reason, I would store foo.eventList as IEnumerable<SalesEvent> instead. This allows you to skip the List conversion at the end, and in some scenarios enables neat tricks like delayed and/or partial execution.
Also, I'm not sure what the point of your .Select(q=>q) statements are in several lines of the SalesEventMatchVo initializer, but I'm pretty sure you can chop them out. If nothing else, you should Select after Where, as Where can reduce the work performed by all following statements.

IN and NOT IN with Linq to Entities (EF4.0)

This has been ruining my life for a few days now, time to ask...
I am using Entity Framework 4.0 for my app.
A Location (such as a house or office) has one or more facilities (like a bathroom, bedroom, snooker table etc..)
I want to display a checkbox list on the location page, with a checkbox list of facilities, with the ones checked that the location currently has.
My View Model for the facilities goes like this...
public class FacilityViewItem
{
public int Id { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
So when im passing the Location View Model to the UI, i want to pass a List<T> of facilities where T is of type FacilityViewItem.
To get the facilities that the location already has is simple - i make a query using Location.Facilities which returns an EntityCollection where T is of type Facility. This is because Facilities is a navigation property....
var facs = from f in location.Facilities
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name,
Checked = true
};
So here is where my problem lies - i want the rest of the facilities, the ones that the Location does not have.
I have tried using Except() and Any() and Contains() but i get the same error.
Examples of queries that do not work...
var restOfFacilities = from f in ctx.Facilities
where !hasFacilities.Contains(f)
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name
};
var restOfFacilities = ctx.Facilities.Except(facilitiesThatLocationHas);
var notFacs = from e in ctx.Facilities
where !hasFacilities.Any(m => m.FacilityId == e.FacilityId)
select new FacilityViewItem()
{
Id = e.FacilityId,
Name = e.Name
};
And the error i get with every implementation...
System.NotSupportedException was unhandled
Message=Unable to create a constant value of type 'Chapter2ConsoleApp.Facility'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
What am i overlooking here?
ironically enough i solved it in a matter of hours after i posted the question on here, after days of suffering.
The error is basically saying 'i dont know how to calculate what items are not included by comparing strongly typed objects. Give me a list of Ints or some simple types, and i can take care of it'.
So, first you need to get a list of the primary keys, then use that in the contains clause...
//get the primary key ids...
var hasFacilityIds = from f in hasFacilities
select f.FacilityId;
//now use them in the contains clause...
var restOfFacilities = from f in ctx.Facilities
where !hasFacilityIds.Contains(f.FacilityId)
select new FacilityViewItem()
{
Id = f.FacilityId,
Name = f.Name
};
The first query seems fine, but you need to compare the Ids:
var restOfFacilities = from f in ctx.Facilities
where !facs.Select(fac => fac.Id).Contains(f.Id)
select f;
I wanna see what's hasFacilities, anyway, as L2E shows, "Only primitive types ('such as Int32, String, and Guid') are supported in this context", so I suppose you must retrieve first the data and put into a collection of FacilityViewItem.
var restOfFacilities = ctx
.Facilities
.Where(f => !hasFacilities.Contains(f))
.Select(f => new { f.FacilityId, f.Name })
.ToList()
.Select(f => new FacilityViewItem {
Id = f.FacilityId,
Name = f.Name
});
var notFacs = ctx
.Facilities
.Where(e => !hasFacilities.Any(m => m.FacilityId == e.FacilityId))
.Select(e => new { e.FacilityId, e.Name })
.ToList()
.Select(e => new FacilityViewItem {
Id = e.FacilityId,
Name = e.Name
});
hope it helps

How to dynamically add OR operator to WHERE clause in LINQ

I have a variable size array of strings, and I am trying to programatically loop through the array and match all the rows in a table where the column "Tags" contains at least one of the strings in the array. Here is some pseudo code:
IQueryable<Songs> allSongMatches = musicDb.Songs; // all rows in the table
I can easily query this table filtering on a fixed set of strings, like this:
allSongMatches=allSongMatches.Where(SongsVar => SongsVar.Tags.Contains("foo1") || SongsVar.Tags.Contains("foo2") || SongsVar.Tags.Contains("foo3"));
However, this does not work (I get the following error: "A lambda expression with a statement body cannot be converted to an expression tree")
allSongMatches = allSongMatches.Where(SongsVar =>
{
bool retVal = false;
foreach(string str in strArray)
{
retVal = retVal || SongsVar.Tags.Contains(str);
}
return retVal;
});
Can anybody show me the correct strategy to accomplish this? I am still new to the world of LINQ :-)
You can use the PredicateBuilder class:
var searchPredicate = PredicateBuilder.False<Songs>();
foreach(string str in strArray)
{
var closureVariable = str; // See the link below for the reason
searchPredicate =
searchPredicate.Or(SongsVar => SongsVar.Tags.Contains(closureVariable));
}
var allSongMatches = db.Songs.Where(searchPredicate);
LinqToSql strange behaviour
I recently created an extension method for creating string searches that also allows for OR searches. Blogged about here
I also created it as a nuget package that you can install:
http://www.nuget.org/packages/NinjaNye.SearchExtensions/
Once installed you will be able to do the following
var result = db.Songs.Search(s => s.Tags, strArray);
If you want to create your own version to allow the above, you will need to do the following:
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms)
{
if (!searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
}
Alternatively, take a look at the github project for NinjaNye.SearchExtensions as this has other options and has been refactored somewhat to allow other combinations
There is another, somewhat easier method that will accomplish this. ScottGu's blog details a dynamic linq library that I've found very helpful in the past. Essentially, it generates the query from a string you pass in. Here's a sample of the code you'd write:
Dim Northwind As New NorthwindDataContext
Dim query = Northwind.Products _
.Where("CategoryID=2 AND UnitPrice>3") _
.OrderBy("SupplierId")
Gridview1.DataSource = query
Gridview1.DataBind()
More info can be found at scottgu's blog here.
Either build an Expression<T> yourself, or look at a different route.
Assuming possibleTags is a collection of tags, you can make use of a closure and a join to find matches. This should find any songs with at least one tag in possibleTags:
allSongMatches = allSongMatches.Where(s => (select t from s.Tags
join tt from possibleTags
on t == tt
select t).Count() > 0)

Resources