JamesRLamar
JamesRLamar

Reputation: 908

How can I maintain type when using LINQ .Select in C#?

I'm trying to return a Distinct set of results that are first trimmed using Substring, but when I call "Select" it converts it to an anonymous type. I can't seem to get this to maintain the type "List". I only need to return the fields specified in the Select method.

public List<Facility> GetFacilities() {
    var facilities = new List<Facility>(); 
    facilities = _facilityRepository.GetAll().ToList();
    var facReturnList = 
        facilities.Where(x => x.Fac_Name = "Something")
                  .OrderBy(x => x.Fac_Name).ToList();

    var facReturnList2 = 
        facReturnList.Select(x => 
            new { ID = x.Fac_Name.Substring(0, 6), 
                  Fac_Name = x.Fac_Name.Substring(0, 3) })
            .Distinct().ToList();
    return facReturnList2;
}

I tried adding List<Facility> after new, but it says those properties (ID and Fac_Name) aren't defined in Facility.

Upvotes: 1

Views: 1641

Answers (4)

Servy
Servy

Reputation: 203836

It sounds like what you really want is a DistinctBy method. You want to specify some means of indicating that an object is distinct, but you don't want the result to be a collection of that selection, you want the end result to be the starting object. LINQ has no DistinctBy built in, but implementing one is easy enough:

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer = null)
{
    comparer = comparer ?? EqualityComparer<TKey>.Default;
    HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
    foreach (TSource element in source)
    {
        if (knownKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

Using that your code can now become:

public List<Facility> GetFacilities()
{
    return _facilityRepository.GetAll()
        .Where(x => x.Fac_Name == "Something")
        .DistinctBy(x => new
        {
            ID = x.Fac_Name.Substring(0, 6),
            Fac_Name = x.Fac_Name.Substring(0, 3)
        })
        .OrderBy(x => x.Fac_Name)
        .ToList();
}

In the event that you have an IQueryable as opposed to an in memory data set, you can use GroupBy to get the same behavior. In Linq-to-objects using GroupBy would be a lot less efficient, but for a query provider it will not be as the database is capable of optimizing this.

public List<Facility> GetFacilities()
{
    return _facilityRepository.GetAll()
        .Where(x => x.Fac_Name == "Something")
        .OrderBy(x => x.Fac_Name)
        .GroupBy(x => new
        {
            ID = x.Fac_Name.Substring(0, 6),
            Fac_Name = x.Fac_Name.Substring(0, 3)
        })
        .Select(group => group.First())
        .ToList();
}

Upvotes: 2

NinjaNye
NinjaNye

Reputation: 7126

The following code is instructing linq to return an anonymous type:

.Select(x => new { ID = x.Fac_Name.Substring(0, 6), Fac_Name = x.Fac_Name.Substring(0, 3) }

You need to create a class for your results

public class Result{
    public string ID { get;set; }
    public string Fac_Name { get; set; }
}

//Then do

.Select(x => new Result { ID = x.Fac_Name.Substring(0, 6), Fac_Name = x.Fac_Name.Substring(0, 3) }

This ensures you are only returning the information you need

Upvotes: 1

Douglas
Douglas

Reputation: 54887

Do you want to initialize new Facility instances with your results?

var facReturnList2 = facReturnList.Select(x => new Facility { ID =   // ...
                                                   ^ concrete type initializer

Response to edit: Inside the Select operator, you need to specify the type of the elements that you want to initialize, not their list. Your prior code seems to indicate that Fac_Name is defined in Facility, but it obviously wouldn't be defined in List<Facility>.

Upvotes: 3

Anthony Pegram
Anthony Pegram

Reputation: 126902

You are getting an anonymous type because you are creating it.

.Select(x => new {

If you do not want that, do not do that.

If an existing type already has the name and the precise properties you need, use that. If no type has what you need, create one. Then you can return a proper list or enumerable of that type.

.Select(x => new YourDefinedType { 

Upvotes: 3

Related Questions