Convert IEnumerable<T> to string[] - c#

i have an entity called Product
class Product
{
public Id { get; set; }
public Name { get; set; }
}
and i have a list of all products:
IEnumerable<Product> products = _productRepository.GetAll()
i want to get an array of strings from this list of products this array will contains the product Id + Product Name, so when i try to cast it using the following code:
string[] s = products.Cast<string>().ToArray();
i got the following exception:
Unable to cast object of type 'Product' to type 'System.String'
the exception really makes alot fo scence, so if i had a method
string ProductToString(Product p)
{
return p.Name;
}
or an override to ToString() for the product object so how i can use this method to get the list of string[] from IEnumerable ?

Well, given that method you can use1:
string[] s = products.Select<string>(ProductToString).ToArray();
However, it would be more idiomatic to do this without a separate method, usually, using a lambda expression:
// Matches ProductToString, but not your description
string[] s = products.Select(p => p.Name).ToArray();
I'd only use a separate method if it was going to be called from various places (ensuring consistency) or did a lot of work.
EDIT: I've just noticed that your description (wanting ID + name) doesn't actually match the ProductToString method you've given (which just gives the name). For the ID + name I'd use:
string[] s = products.Select(p => p.ID + " " + p.Name).ToArray();
or
string[] s = products.Select(p => string.Format("{0} {1}", p.ID, p.Name))
.ToArray();
Or you could just change your ProductToString method, of course.
Alternatively, you could override ToString() in Product, if this is usually how you want to convert a Product to a string. You could then either use a method group conversion or a lambda expression to call ToString.
1 It's possible that you don't need to specify the type argument explicitly - that:
string[] s = products.Select(ProductToString).ToArray();
will work fine - the rules for type inference and method group conversions always confuse me and the compiler behaviour has changed slightly over time. A quick test just now looks like it does work, but there could be subtleties in slightly different situations.

string[] s = products.Select(p => p.Name).ToArray();
Or, if you need Id + Name:
string[] s = products.Select(p => p.Id + ' ' + p.Name).ToArray();

use
string[] s = (from p in products select p.Id.ToString() + " " + p.Name.ToString()).ToArray();

This worked for me:
String.Join(";", mi_customer.Select(a => a.Id).Cast<string>().ToArray());
Where mi_customer must be your object, in my case is a table.

Related

GroupBy with different parameters

I have one query with groupBy parameters depends on the user input. If the condition is true, query will be grouped with DOB attribute. else, it doesnt need to.
Here is my code
var userList = user.GroupBy(x => new { x.Name, x.Age});
if (isBaby)
{
userList = user.GroupBy(x => new { x.Name, x.Age, x.DOB });
}
but i got an error which the parameters is not same. The latter code is to select from this query like this.
var allList= userList.Select({
...
}).ToList();
I dont want to create two select list because it is easier to manage if only use one select list.
Edited:
This is the error
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<SystemLinq.IGrouping<<anonymous type: string name, string age>, Domains.User>>' to 'System.Collections.Generic.IEnumerable<SystemLinq.IGrouping<<anonymous type: string name, string age, string DOB>, Domains.User>>'
enter code here
Assuming DOB is a DateTime:
var userList = user.GroupBy(x => new { x.Name, x.Age, dob = isBaby ? x.DOB : DateTime.MinValue });
That way the expression is always the same type, but won't cause an additional tier of grouping when isBaby is false.
Well you are constructing two different results in the group by...
Var implicitly tries to assume the type, and the first assignment is an anon type with only Name and Age. The second one adds DOB which is a different anon type.
Something like
new { x.Name, x.Age, DateOfBirth = isBaby ? x.DOB : DateTime.MinValue }
Would fix it

LINQ conversion to List object

I am using the following code to return an IList:
FileName = Path.GetFileName(files[i]);
IList<DataX> QueryListFromFTP = DataX.GetListFromFTP(FileName);
QueryListFromFTP = (IList<DataX>)QueryListFromFTP
.Select(x => new { x.user_id, x.date, x.application_ID })
.ToList()
.Distinct();
However I keep getting this error:
Unable to cast object of type 'd__7a1[<>f__AnonymousType03[System.String,System.String,System.String]]' to type 'System.Collections.Generic.IList`1[DataXLibrary.DataX]'.
What am I doing wrong?
If what you want is a List < DataX > than all you need is:
IList<DataX> QueryListFromFTP = DataX.GetListFromFTP(FileName).Distinct().ToList();
// Use QueryListFromFTP here.
If you want a List of a different type of object as a result of your .Select, than you need to store the result in a List of object of that type i.e. anonymous if that's what you want.
The following line creates an anonymous type in c# which is not correspondent to the type Datax:
new { x.user_id, x.date, x.application_ID })
You should alter it to something like this:
Select(x => new Datax(){User_id = x.user_id, Date = x.date, Application = x.application_ID })
There are two problems in your code:
You're converting the List of DataX objects to an "anonymous type object" (the new { x.user_id, x.date, x.application_ID }). This object is not the same type as DataX, and it can't be coerced back to a DataX object automatically.
Trying to read between the lines a little, it looks like you want a distinct list of DataX objects, where distinctness is determined by a subset of the properties of a DataX object. So you have to answer the question, what will you do with duplicates (by this definition) that have different data in other properties? You have to discard some of them. Distinct() is not the right tool for this, because it only applies to the entire object of the IEnumerable it is applied to.
It's almost like you need a DistinctBy with one parameter giving the properties to calculate distinctness with, and a second parameter giving some logic for deciding which of the non-distinct "duplicates" to select. But this can be achieved with multiple IEnumerable methods: GroupBy and a further expression to select an appropriate single itemd from each resulting group. Here's one possible solution:
FileName = Path.GetFileName(files[i]);
IList<DataX> QueryListFromFTP = DataX.GetListFromFTP(FileName)
.GroupBy(datax => new { datax.user_id, datax.date, datax.application_ID })
.Select(g => g.First()); // or another expression to choose one item per group
.ToList();
If, for example, there were a version field and you wanted the most recent one for each "duplicate", you could:
.Select(g => g.OrderByDescending(datax => data.version).First())
Please note, however, that if you just want distinctness over all the properties of the object, and there is no need to select one particular value (in order to get its additional properties after throwing away some objects considered duplicates), then it may be as simple as this:
IList<DataX> QueryListFromFTP = DataX.GetListFromFTP(FileName)
.Distinct()
.ToList();
I would furthermore advise that you use IReadOnlyCollection where possible (that's .ToList().AsReadOnly()) and that, depending on your data, you may want to make the GetListFromFTP function perform the de-duplication/distinctness instead.
To answer any concerns that GroupBy isn't the right answer because it may not perform well enough, here is an alternate way to handle this (though I wholeheartedly disagree with you--until tests prove it's slow, it's a perfectly fine answer).
// in a static helper class of some kind
public static IEnumerable<T> DistinctBy<T, TKey>(
this IEnumerable<T> source,
Func<T, TKey> keySelector
) {
if (source == null) {
throw new ArgumentNullException("source", "Source enumerable cannot be null.");
}
if (keySelector == null) {
throw new ArgumentNullException("keySelector", "keySelector function cannot be null. To perform a generic distinct, use .Distinct().");
}
return DistinctByImpl(source, keySelector);
}
private static IEnumerable<T> DistinctByImpl<T, TKey>(
this IEnumerable<T> source,
Func<T, TKey> keySelector
) {
HashSet<TKey> keys = new HashSet<TKey>();
return source.Where(s => keys.Add(keySelector(s)));
}
It is used like this:
public class Animal {
public string Name { get; set; }
public string AnimalType { get; set; }
public decimal Weight { get; set; }
}
IEnumerable<Animal> animals = new List<Animal> {
new Animal { Name = "Fido", AnimalType = "Dog", Weight = 15.0M },
new Animal { Name = "Trixie", AnimalType = "Dog", Weight = 15.0M },
new Animal { Name = "Juliet", AnimalType = "Cat", Weight = 12.0M },
new Animal { Name = "Juliet", AnimalType = "Fish", Weight = 1.0M }
};
var filtered1 = animals.DistinctBy(a => new { a.AnimalType, a.Weight });
/* returns:
Name Type Weight
Fido Dog 15.0
Juliet Cat 12.0
Juliet Fish 1.0
*/
var filtered2 = animals.DistinctBy(a => a.Name); // or a simple property
/* returns:
Name Type Weight
Fido Dog 15.0
Trixie Dog 15.0
Juliet Cat 12.0
*/

An expression tree may not contain a call or invocation that uses option arguments in C# Linq

I am trying to do a case statement for one of the properties when selecting an anonymous type in the first part and then convert it to a list of my return type (retList). In the retList part at the bottom when I set QuarterName = p.QuarterName I get the following error on the DatePart functions from the section above:
An expression tree may not contain a call or invocation that uses
optional arguments
public static IEnumerable<Product> GetProducts(int categoryId)
{
using (var context = new DbContext())
{
var pList = (from p in context.Products
where (p.CategoryId == proformaId)
select new
{
Id = p.Id,
ProductName = p.ProductName,
QuarterName = pa.Quarter != "ExtraQuarter" ? "Q" + DateAndTime.DatePart(DateInterval.Quarter, p.PurchaseDate) +
"-" + DateAndTime.DatePart(DateInterval.Year, p.PurchaseDate) :
"<b><i>" + p.Quarter + "</i></b>"
}).ToList();
var retList = from p in pList
select new ProformaAssumption()
{
Id = pa.Id,
ProductName = p.ProformaId,
QuarterName = p.QuarterName
};
return retList;
}
The DatePart methods have additional, optional parameters. C# doesn't allow Expression Trees to leverage the optional parameters, so you'll need to provide the whole parameter list to each of these method calls.
According to the documentation, FirstDayOfWeek.Sunday and FirstWeekOfYear.Jan1 are the values that would be used if you didn't provide a value for the optional parameters.
QuarterName = pa.Quarter != "ExtraQuarter"
? "Q" +
DateAndTime.DatePart(DateInterval.Quarter, p.PurchaseDate,
FirstDayOfWeek.Sunday, FirstWeekOfYear.Jan1) +
"-" + DateAndTime.DatePart(DateInterval.Year, p.PurchaseDate,
FirstDayOfWeek.Sunday, FirstWeekOfYear.Jan1)
: "<b><i>" + p.Quarter + "</i></b>"

Aggregating anonymous types using lambda

I'm trying to print out a list of phone numbers (\n for each number) in a format {type}:{number}. So from a list of 2 numbers I would print out:
Home: 111-111-1111
Cell: 222-222-2222
So far I can select into an anonymous type but when I go to aggregate it is leterally printing out the whole anonymous type on the screen looking exactly like below:
new { phoneType = Home + ": ", phoneNumber = 111-111-1111 }
Should I even be using aggregate?
This is what I'm working with:
PhoneNumbers.Select(x => new { phoneType = x.PhoneType, phoneNumber = x.PhoneNumber1 }).Aggregate(
(p, x) => new { phoneType = p.phoneType + ": ", x.phoneNumber });
If you just want the phone numbers as strings, you can create that string in a Select call:
var x = PhoneNumbers.Select(x => string.Format("{0}: {1}", x.PhoneType, x.PhoneNumber));
Your Aggregate call is basically a bug (assuming it even compiles) because you're combining the "current" phone number with the previous phone type. It's also unnecessary for combining the phone numbers as text because of existing string methods:
var phoneNumberStrings = PhoneNumbers.Select(x => string.Format("{0}: {1}", x.PhoneType, x.PhoneNumber));
var text = string.Join(Environment.NewLine, phoneNumberStrings);
While you could do this with IEnumerable<string>.Aggregate, I feel it's cleaner if you just use string.Join. Someone reading your code will be able to more easily get the meaning out if they see "join these strings with newline."
string.Join(
Environment.NewLine,
PhoneNumbers.Select(x =>
string.Format("{0}: {1}", x.PhoneType, x.PhoneNumber));

Working with Lists, Anonymous Types and Suchlike

Following on from an earlier question, I now have a collection of an anonymous type
[User:
Username (as forename.surname,
UserId
].
This collection of 'users' will ultimately need to be bound to a dropdown list. This is fine, but what I need to do is sort them by Surname and forename. but this is complicated by the fact that the username format is forename.surname.
At a high-level, this will involve a Split on the string to separate the name components, then ToTitleCase() both parts, then store the new values in another object within a List which I can then sort using List<T>.OrderBy(...).ThenBy(...)
It occurred to me that all this fancy new syntax I'm attempting to learn might include a way of performing this process in a couple of lines of terse code. Can anyone confirm or deny this?
Thanks.
EDIT 3:
I think I've cracked it:
var salesusers = (
from s in lstReport
group s by new { s.SalesUserId,s.Username}
into g
select new
{
Username = g.Key.Username.Split('.')[1].ToTitleCase() + " " + g.Key.Username.Split('.')[0].ToTitleCase(),
Surname = g.Key.Username.Split('.')[1].ToTitleCase(),
Forename = g.Key.Username.Split('.')[0].ToTitleCase(),
UserId = g.Key.SalesUserId
}
).OrderBy(a=> a.Surname).ThenBy(a=> a.Forename);
I need to create the separate forename and surname fields from the Username for sorting purposes and the Username for binding to a dropdownlist. It seems insane but it works so nicely I'm sticking with it for now. Would appreciate your comments.
EDIT2:
So I got as far as this. Now I'm wondering if the syntax will allow me to combine the Group by operation from my earlier question with this step..
var sortedUsers = from u in salesusers
orderby u.UserName.Split('.')[1], u.UserName.Split('.')[0]
select new {UserName = u.UserName.Replace(".", " ").ToTitleCase(), UserId = u.UserId.Value};
Anybody ...?
EDIT: I managed to do it all most of it myself, in case anyone is ever needing, but converting the name components ToTitleCase during the ordering operation is proving difficult.
This:
var sortedUsers = from u in salesusers
orderby u.UserName.Split('.')[1], u.UserName.Split('.')[0]
select u;
seems to do the trick everything I need apart from the ToTitleCaseing. But of course there may be an even quicker/terser/more elegant method so I'll leave this open for a day or two to see what turns up ;-)
A little less code can be achieved using lambdas rather than expression syntax as so
var sorted = salesusers.OrderBy(u => u.UserName.Split('.')[1]).ThenBy(u => u.UserName.Split('.')[0]).ToList();
although it is less readable but once you are used to the syntax I find it easier than the expression syntax to read.
EDIT: changes for edit 3
Your code converted to Lambdas is as follows
var salesusers = (l.GroupBy(s => new { SalesUserId = s.SalesUserId, Username = s.Username }).Select(g =>new {
Username = g.Key.Username.Split('.')[1].ToTitleCase() + " " + g.Key.Username.Split('.')[0].ToTitleCase(),
Surname = g.Key.Username.Split('.')[1].ToTitleCase(),
Forename = g.Key.Username.Split('.')[0].ToTitleCase(),
UserId = g.Key.SalesUserId
})).OrderBy(a => a.Surname).ThenBy(a => a.Forename);
The only thing is that the bigger the expression gets the harder it gets to read!
Another way you could do it which reads cleaner is to define the details object rather than using a dynamic object.
internal class UserDetails
{
public UserDetails(User u)
{
this.Forename = u.Username.Split('.')[0].ToTitleCase();
this.Surname = u.Username.Split('.')[1].ToTitleCase();
this.UserId = u.SalesUserId;
this.Username = u.Username;
}
public string Username { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public int UserId { get; set; }
}
then you can do
var salesusers = (l.GroupBy(s => new { SalesUserId = s.SalesUserId, Username = s.Username })
.OrderBy(u => u.Username.Split('.')[1].ToTitleCase())
.ThenBy(u => u.Username.Split('.')[0].ToTitleCase())
.Select(g => new UserDetails(g)));
but this is more code which I'm not sure you want.
LATEST EDITS:
your Code doesn't require the group by statement so you could reduce that by doing
var salesusers = (
from s in l
select new
{
Username = s.Username.Split('.')[1].ToTitleCase() + " " + s.Username.Split('.')[0].ToTitleCase(),
Surname = s.Username.Split('.')[1].ToTitleCase(),
Forename = s.Username.Split('.')[0].ToTitleCase(),
UserId = s.SalesUserId
}
).OrderBy(a => a.Surname).ThenBy(a => a.Forename);
Or using Lambdas becomes
var salesusers = l.Select(g =>new {
Username = g.Username.Split('.')[1].ToTitleCase() + " " + g.Username.Split('.')[0].ToTitleCase(),
Surname = g.Username.Split('.')[1].ToTitleCase(),
Forename = g.Username.Split('.')[0].ToTitleCase(),
UserId = g.SalesUserId
}).OrderBy(a => a.Surname).ThenBy(a => a.Forename);
Other than that the only way I can see to reduce that call further is with a defined class as above, thats not to say it can't be done! but I can't see how!
HTH
OneShot
Here's the final solution I came up with - it sorts, ToTitleCases and formats. Rather nifty if I say so myself. Took me all morning tho :-(
var salesusers = (
from s in lstReport
group s by new { s.SalesUserId,s.Username}
into g
select new
{
Username = g.Key.Username.Split('.')[1].ToTitleCase() + " " + g.Key.Username.Split('.')[0].ToTitleCase(),
Surname = g.Key.Username.Split('.')[1].ToTitleCase(),
Forename = g.Key.Username.Split('.')[0].ToTitleCase(),
UserId = g.Key.SalesUserId
}
).OrderBy(a=> a.Surname).ThenBy(a=> a.Forename);

Categories