How do I use LINQ to obtain a unique list of properties from a list of objects? - linq

I'm trying to use LINQ to return a list of ids given a list of objects where the id is a property. I'd like to be able to do this without looping through each object and pulling out the unique ids that I find.
I have a list of objects of type MyClass and one of the properties of this class is an ID.
public class MyClass
{
public int ID { get; set; }
}
I want to write a LINQ query to return me a list of those Ids.
How do I do that, given an IList<MyClass> such that it returns an IEnumerable<int> of the ids?
I'm sure it must be possible to do it in one or two lines using LINQ rather than looping through each item in the MyClass list and adding the unique values into a list.

IEnumerable<int> ids = list.Select(x=>x.ID).Distinct();

Use the Distinct operator:
var idList = yourList.Select(x=> x.ID).Distinct();

Using straight LINQ, with the Distinct() extension:
var idList = (from x in yourList select x.ID).Distinct();

When taking Distinct, we have to cast into IEnumerable too. If the list is <T> model, it means you need to write code like this:
IEnumerable<T> ids = list.Select(x => x).Distinct();

int[] numbers = {1,2,3,4,5,3,6,4,7,8,9,1,0 };
var nonRepeats = (from n in numbers select n).Distinct();
foreach (var d in nonRepeats)
{
Response.Write(d);
}
Output
1234567890

Related

How to remove from an List Object in c#

I have an Action method in my controller which returns a List Object
Public ActionResult GetCats(long Id,string strsortorder,string dltIds)
{
var Result=objrepo.GetCats(Id);//this method returns me List of Result
}
My array looks like this:
var Result=[{CatId:1015,CatName:Abc},{CatId:1016,CatName:Acd},
{CatId:1017,CatName:Adf},{CatId:1018,CatName:CDdf},{CatId:1019,CatName:asdas},
{CatId:1020,CatName:Abc},{CatId:1021,CatName:Abc},{CatId:1022,CatName:Abc},
{CatId:1023,CatName:Abc},{CatId:1024,CatName:Abc}]
What I want to do is:
Using two more parameters in my Action Method "strsortorder" and "dltIds"
that have a list of ids like this:
strsortorder="1021,1015,1016,1019,1022";
dltIds="1017,1018,1020";
From this the "Result" returned from my method , I want to remove the records which are in "dltids" and the remaining array should be sorted in the order which I have in "strsortorder";
In the end the new object should look like this:
var NewResult=[{CatId:1021,CatName:Abc},{CatId:1015,CatName:Abc},
{CatId:1016,CatName:Acd},{CatId:1019,CatName:asdas},{CatId:1022,CatName:Abc},
{CatId:1023,CatName:Abc},{CatId:1024,CatName:Abc}]
Can any one help me in acheiving this in linq or any other way?
I want to avoid any type of loop or froeach here for max extent, I know it can be done by looping but I want to avoid this since the result can sometimes contain large amounts of data.
I realized you can use an ArrayList instead of a Dictionary and it would be faster. I think Dictionary is clear how it works but here is the "better" implementation using array list:
var excludeList = dltIds.Split(",".ToCharArray());
ArrayList sortList = new ArrayList(strsortorder.Split(",".ToCharArray()));
var NewResult =
Result.Where(item => ! excludeList.Contains(item.CatId.ToString()))
.OrderBy(item => {
if (sortList.Contains(item.CatId.ToString()))
return sortList.IndexOf(item.CatId.ToString());
return sortList.Count;
});
Original answer below:
Public ActionResult GetCats(long Id,string strsortorder,string dltIds)
{
var Result=objrepo.GetCats(Id);//this method returns me List of Result
var excludeList = dltIds.Split(",".ToCharArray());
int orderCount = 0; // used in the closure creating the Dictionary below
var sortList = strsortorder.Split(",".ToCharArray())
.ToDictionary(x => x,x => orderCount++);
// filter
var NewResult =
Result.Where(item => ! excludeList.Contains(item.CatId.ToString()))
.OrderBy(item => {
if (sortList.ContainsKey(item.CatId.ToString()))
return sortList[item.CatId.ToString()];
return sortList.Count();
});
}
How this works:
First I create lists out of your comma separated exclude list using split.
This I create a dictionary with the key being the ordering ID and the value being an integer that goes up by one.
For the filtering I look to see if an item is in the exclude array before I continue processing the item.
I then do a sort on matching against the key and the dictionary and returning the value -- this will sort things in the order of the list since I incremented a counter when creating the values. If an item is not in the dictionary I return one more than the maximum value in the dictionary which must be the count of the items. (I could have used the current value of orderCount instead.)
Questions?

LINQ Distinct set by column value

Is there a simple LINQ query to get distinct records by a specific column value (not the whole record)?
Anyone know how i can filter a list with only distinct values?
You could use libraries like morelinq to do this. You'd be interested in the DistinctBy() method.
var query = records.DistinctBy(record => record.Column);
Otherwise, you could do this by hand.
var query =
from record in records
group record by record.Column into g
select g.First();
Select a single value first and then run the Distinct.
(from item in table
select item.TheSingleValue).Distinct();
If you want the entire record you need to use group x by into y. You then need to find a suitable aggregate function like First, Max, Average or similar to select one of the other values in the group.
from item in table
group item by item.TheSingleValue into g
select new { TheSingleValue = g.Key, OtherValue1 = g.First().OtherValue1, OtherValue2 = g.First().OtherValue2 };
You could make an implementation of the IEqualityComparer interface:
public class MyObjectComparer : IEqualityComparer<MyObject>
{
public bool Equals(MyObject x, MyObject y)
{
return x.ColumnNameProperty == y.ColumnNameProperty;
}
public int GetHashCode(MyObject obj)
{
return obj.ColumnNameProperty.GetHashCode();
}
}
And pass an instance into the Distinct method:
var distinctSource = source.Distinct(new MyObjectComparer());

Writing Group By on Anonymous Types

I am writting a group by clause on two tables which are joined and being accessed via Entity Data Model. I am not able to iterate over the anonymous type, can somebody help me out.
public string GetProductNameByProductId(int productId)
{
string prodName=string.Empty;
using (VODConnection vodObjectContext = new VODConnection())
{
var products = from bp in vodObjectContext.BFProducts
join bpf in vodObjectContext.BFProductMasters on bp.ProductMasterId equals bpf.ProductMasterId
where bp.ProductId == productId
group bp by new { ProductId = bp.ProductId, ProductName = bp.ProductName, ProductMasterName=bpf.ProductMasterName} into newInfo
select newInfo;
//Want to iterate over products or in fact need to get all the results. How can I do that? Want productmastername property to be set in prodName variable by iterating
return (prodName);
}
}
One problem is that you've used a query continuation for no reason. That still shouldn't have prevented you from using the Key property, mind you. Try this as a slightly cleaner approach:
var products = from bp in vodObjectContext.BFProducts
join bpf in vodObjectContext.BFProductMasters
on bp.ProductMasterId equals bpf.ProductMasterId
where bp.ProductId == productId
group bp by new { bp.ProductId,
bp.ProductName,
bpf.ProductMasterName};
foreach (var group in products)
{
var key = group.Key;
// Can now use key.ProductName, key.ProductMasterName etc.
}
As for what you set your prodName variable to - it's unclear exactly what you want. The first ProductName value? The last? A concatenation of all of them? Why do you need a grouping at all?
foreach(var prod in products)
{
prodName += prod.Key.ProductMasterName;
}

Remove duplicates using linq

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();

Sum of items in a collection

Using LINQ to SQL, I have an Order class with a collection of OrderDetails. The Order Details has a property called LineTotal which gets Qnty x ItemPrice.
I know how to do a new LINQ query of the database to find the order total, but as I already have the collection of OrderDetails from the DB, is there a simple method to return the sum of the LineTotal directly from the collection?
I'd like to add the order total as a property of my Order class. I imagine I could loop through the collection and calculate the sum with a for each Order.OrderDetail, but I'm guessing there is a better way.
You can do LINQ to Objects and the use LINQ to calculate the totals:
decimal sumLineTotal = (from od in orderdetailscollection
select od.LineTotal).Sum();
You can also use lambda-expressions to do this, which is a bit "cleaner".
decimal sumLineTotal = orderdetailscollection.Sum(od => od.LineTotal);
You can then hook this up to your Order-class like this if you want:
Public Partial Class Order {
...
Public Decimal LineTotal {
get {
return orderdetailscollection.Sum(od => od.LineTotal);
}
}
}

Resources