I have some code that I know could be nicer if it's done in LINQ, but I don't know how the LINQ code would look like.
I have a collection of GoodsItems, in each of this Item there is a Collection of Comments, and some of these comments I want to filter out and turn into a single string line.
Here is the code:
//-- get all comments that is of type "GoodsDescription"
ICollection<FreeText> comments = new List<FreeText>();
foreach (DSV.Services.Shared.CDM.Shared.V2.GoodsItem goodsItem in shipmentInstructionMessage.ShipmentInstruction.ShipmentDetails.GoodsItems)
{
ICollection<DSV.Services.Shared.CDM.Shared.V2.FreeText> freeTexts = goodsItem.Comments.Where(c => c.Type.ToLower() == FREETEXT_TYPE_GOODSDESCRIPTION.ToLower()).ToList();
foreach (DSV.Services.Shared.CDM.Shared.V2.FreeText freeText in freeTexts)
comments.Add(FreeText.CreateFreeTextFromCDMFreeText(freeText));
}
//-- Turn this collection of comments into a single string line
StringBuilder sb = new StringBuilder();
foreach (FreeText comment in comments)
sb.Append(comment.ToString());
contents = sb.ToString();
First Foreach loops thru all goodsitems and for each goods item I get all comments where the Type of the comment is Equale to a defined value.
Then foreach of this comment that I get I create a new Object and add to a CommentsCollection.
And the last thing is that I loop thru this commentsColletion and create all it's data into a single string line.
There must be a nicer and smart way to do this with LINQ.
Thanks...
It looks like you could do this:
var comments = from goodsItem in shipmentInstructionMessage.ShipmentInstruction.ShipmentDetails.GoodsItems
from freeText in goodsItem.Comments.Where(c => string.Equals(c.Type, FREETEXT_TYPE_GOODSDESCRIPTION, StringComparison.InvariantCultureIgnoreCase))
select FreeText.CreateFreeTextFromCDMFreeText(freeText).ToString();
string contents = string.Join("", comments);
It's probably slightly more readable, if only because you've lost most of the types (although you could also have achieved this with implicitly typed local variables).
(I also changed how the string comparison on the comment type is done – I assume you were trying to achieve a case invariant comparison. You may want to use StringComparison.CurrentCultureIgnoreCase instead, depending on what the content of the comments is.)
Related
I have an array of string
var searchString = new string[] {"1:PS", "2:PS"};
and a large result string eg;
var largeString = "D9876646|10|1:PS^CD9876647100|11|2:PS"
how do I check if any of the options in searchString exist in the largeString?
I know it can be done via loop quite easily but I am looking for an other way around since I need to append the following as search clause in linq query.
You can use LINQ for it with a simple Any() call, like this:
var hasAny = searchString.Any(sub => largeString.Contains(sub));
However, this is as slow as a foreach loop. You can find the answer faster with a regex constructed from searchString:
var regex = string.Join("|", searchString.Select(Regex.Escape));
var hasAny = Regex.IsMatch(largeString, regex);
Depending on the nature of your LINQ provider (assuming it isn't LINQ to Objects), you may want to add individual tests for each member of searchString. The best way to do this is probably using PredicateBuilder
var sq = PredicateBuilder.New<dbType>();
foreach (var s in searchString)
sq = sq.Or(r => r.largeString.Contains(s));
q = q.Where(sq);
I have a List of a Simple Struct that contains int's and strings.
Struct:
public struct ErrorType
{
public int RowNumber;
public int ColumnNumber;
public string ErrorMessage;
}
On of My methods return A list of these structs.
I want to convert each member in the list to its string form and separate it by a comma so that List is now an Array of strings. I could write a function to do this manually but I'd perfer to use linq to have a cleaner solution.
Having the array of strings I'll write it to a file using
string path = #"c:\temp\MyTest.txt";
// This text is added only once to the file.
if (!File.Exists(path))
{
// Create a file to write to.
List<ErrorType> ErrorTypesList = GetErrors();
//do some work to iterate over the members and do a string.join after
string[] ErrorListArray = ErrorTypesList.foreach( e => { });
File.WriteAllLines(path, createText);
}
does anyone have suggestions on how to fill in the foreach so that it returns each members in its ToString form followed by a comma?
The only way you'd be able to use LINQ for property iteration is if you're using reflection, and you shouldn't do that unless you absolutely have to (which it really doesn't look like you do).
The best you can do, though, still isn't too bad.
var rows = ErrorTypesList
.Select(c => "\"" + string.Join("\",\"", c.RowNumber, c.ColumnNumber, c.ErrorMessage) + "\"");
File.WriteAllLines(path, rows);
I took the liberty of putting fields in quotes as well, you may or may not want that, but it's easy to fix that code to do what you do want. You might also want to add in some escape logic, pending what sorts of contents c.ErrorMessage might have.
You could also use string.Format if you're more comfortable with that, but it doesn't really make a difference here.
var rows = ErrorTypesList
.Select(c => string.Format("\"{0}\", \"{1}\", \"{2}\"", c.RowNumber, c.ColumnNumber, c.ErrorMessage));
It seems you want something like this:
string[] ErrorListArray = ErrorTypesList
.Select(e => string.Join(",", e.RowNumber, e.ColumnNumber, e.ErrorMessage))
.ToArray();
Let's say I have a table that I can query with EF + LINQ like this:
var results = dbContext.MyTable.Where(q => q.Flag = true);
Then, I know that if I want to limit the columns returned, I can just add a select in that line like this:
var results = dbContext.MyTable
.Select(model => new { model.column2, model.column4, model.column9 })
.Where(q => q.Flag == true);
The next step that I need to figure out, is how to select those columns dynamically. Said another way, I need to be able to select columns in a table withoutknowing what they are at compile time. So, for example, I need to be able to do something like this:
public IEnumerable<object> GetWhateverColumnsYouWant(List<string> columns = new List<string{ "column3", "column4", "column999"})
{
// automagical stuff goes here.
}
It is important to keep the returned record values strongly typed, meaning the values can't just be dumped into a list of strings. Is this something that can be accomplished with reflection? Or would generics fit this better? Honestly, I'm not sure where to start with this.
Thanks for any help.
I think you want some dynamic linq, Im no expert on this but i THINK it will go something like this
public static IEnumerable<object> GetWhateverColumnsYouWant(this IQueriable<T> query, List<string> columns = new List<string{ "column3", "column4", "column999"})
{
return query.Select("new (" + String.Join(", ", columns) + ")");
}
See scott Gu's blog here http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx and this question System.LINQ.Dynamic: Select(" new (...)") into a List<T> (or any other enumerable collection of <T>)
You could probably also do this by dynamically composing an expression tree of the columns youre wanting to select but this would be substantially more code to write.
I know this as asked many times but cannot see something that works.
I am reading a csv file and then I have to remove duplicate lines based on one of the columns "CustomerID".
Basically the CSV file can have multiple lines with the same customerID.
I need to remove the duplicates.
//DOES NOT WORK
var finalCustomerList = csvCustomerList.Distinct().ToList();
I have also tried this extension method //DOES NOT WORK
public static IEnumerable<t> RemoveDuplicates<t>(this IEnumerable<t> items)
{
return new HashSet<t>(items);
}
What works for me is
I Read the CSV file into a csvCustomerList
Loop through csvCustomerList and check if a
customerExists If it doesnt I add
it.
foreach (var csvCustomer in csvCustomerList)
{
var Customer = new customer();
customer.CustomerID = csvCustomer.CustomerID;
customer.Name = csvCustomer.Name;
//etc.....
var exists = finalCustomerList.Exists(x => x.CustomerID == csvCustomer.CustomerID);
if (!exists)
{
finalCustomerList.Add(customer);
}
}
Is there a better way of doing this?
For Distinct to work with non standard equality checks, you need to make your class customer implement IEquatable<T>. In the Equals method, simply compare the customer ids and nothing else.
As an alternative, you can use the overload of Distinct that requires an IEqualityComparer<T> and create a class that implements that interface for customer. Like that, you don't need to change the customer class.
Or you can use Morelinq as suggested by another answer.
For a simple solution, check out Morelinq by Jon Skeet and others.
It has a DistinctBy operator where you can perform a distinct operation by any field. So you could do something like:
var finalCustomerList = csvCustomerList.DistinctBy(c => c.customerID).ToList();
I've been following with great interest the converstaion here:
Construct Query with Linq rather than SQL strings
with regards to constructing expression trees where even the table name is dynamic.
Toward that end, I've created a Extension method, addWhere, that looks like:
static public IQueryable<TResult> addWhere<TResult>(this IQueryable<TResult> query, string columnName, string value)
{
var providerType = query.Provider.GetType();
// Find the specific type parameter (the T in IQueryable<T>)
var iqueryableT = providerType.FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), null).FirstOrDefault();
var tableType = iqueryableT.GetGenericArguments()[0];
var tableName = tableType.Name;
var tableParam = Expression.Parameter(tableType, tableName);
var columnExpression = Expression.Equal(
Expression.Property(tableParam, columnName),
Expression.Constant(value));
var predicate = Expression.Lambda(columnExpression, tableParam);
var function = (Func<TResult, Boolean>)predicate.Compile();
var whereRes = query.Where(function);
var newquery = whereRes.AsQueryable();
return newquery;
}
[thanks to Timwi for the basis of that code]
Which functionally, works.
I can call:
query = query.addWhere("CurUnitType", "ML 15521.1");
and it's functionally equivalent to :
query = query.Where(l => l.CurUnitType.Equals("ML 15521.1"));
ie, the rows returned are the same.
However, I started watching the sql log, and I noticed with the line:
query = query.Where(l => l.CurUnitType.Equals("ML 15521.1"));
The Query generated is:
SELECT (A bunch of columns)
FROM [dbo].[ObjCurLocView] AS [t0]
WHERE [t0].[CurUnitType] = #p0
whereas when I use the line
query = query.addWhere("CurUnitType", "ML 15521.1");
The query generated is :
SELECT (the same bunch of columns)
FROM [dbo].[ObjCurLocView] AS [t0]
So, the comparison is now happening on the client side, instead of being added to the sql.
Obviously, this isn't so hot.
To be honest, I mostly cut-and-pasted the addWhere code from Timwi's (slightly different) example, so some of it is over my head. I'm wondering if there's any adjustment I can make to this code, so the expression is converted into the SQL statement, instead of being determined client-side
Thanks for taking the time to read through this, I welcome any comments, solutions, links, etc, that could help me with this. And of course if I find the solution through other means, I'll post the answer here.
Cheers.
The big problem is that you're converting the expression tree into a delegate. Look at the signature of Queryable.Where - it's expressed in expression trees, not delegates. So you're actually calling Enumerable.Where instead. That's why you need to call AsQueryable afterwards - but that doesn't do enough magic here. It doesn't really put it back into "just expression trees internally" land, because you've still got the delegate in there. It's now wrapped in an expression tree, but you've lost the details of what's going on inside.
I suspect what you want is this:
var predicate = Expression.Lambda<Func<TResult, Boolean>>
(columnExpression, tableParam);
return query.Where(predicate);
I readily admit that I haven't read the rest of your code, so there may be other things going on... but that's the core bit. You want a strongly typed expression tree (hence the call to the generic form of Expression.Lambda) which you can then pass into Queryable.Where. Give it a shot :)