c# comparing 2 list<object> - c#

I've got 2 lists of the same object. Now the first list is filled with data from a database table. the other list is filled with data downloaded a server.
Example:
public class HistoricData {
public int Id{get;set;}
public DateTime Date {get;set;}
public string Name {get;set;}
public float Impressions {get;set;}
}
So in my object I have the Id property that is unique. Now I need to check if I have objects in my second list that aren't in my first list.
I thought that I could do it in Linq, but I'm a little stuck.
var difference = from objHD in objHistoricData
join objHDN in objHistoricDataNew on objHD.Id equals objHDN.Id
select new {objHDNA = objHDN};
This always returns 0.

var difference = objHistoricDataNew.Except(objHistoricDataNew, new HistoricDataComparer());
(where HistoricDataComparer is a IEqualityComparer<HistoricData>)

use the Except function from LINQ, ans pass the IEqualityComparer that compare the ID.
var difference = a.Except( b, new YouEqualitityComparer() )

You need to override Equals and GetHashcode so that your types are compared by their values rather then by their references.

Try
var difference = list1.Except(list2);
But I think you'll need your own GetHashCode and Equals methods.

I assume that you would like to select all items in objHistoricDataNew that are different, than stored in objHistoricData?
Then maybe something like that:
var difference = objhistoricDataNew.Where(objHDN => objHistoricData.Select(objHD => objHD.Id).IndexOf(objHDN.Id) == -1)

Related

How to do GroupBy on complex object with an IQueryable

I'm looking for a way to do a GroupBy on a complex object, instead of just one property. The trouble is that I want to do this on an IQueryable, because getting all the data from the table is a really bad idea in this case.
We're using Entity Framework 6.1.
The class looks like this:
public class Pin {
public Guid Id {get;set;}
public Guid PageId {get;set;} /* this is the foreign key to our Pages-table */
public PageClass Page {get;set;} /* this is a relation */
}
I need to report on the times a certain page has been "pinned", printing the name of the page as well.
Right now my code looks like this:
var pinnedPages = GetAll().GroupBy(x => x, comparer);
foreach (var pinnedPage in pinnedPages)
{
var numberOfTimesPinned = pinnedPage.Count();
var pin = pinnedPage.Key;
//write a line to the report
}
But if I group on PageId, the pinnedPage.Key returns a Guid, obviously, while I need the whole Page object for my reporting needs.
I have tried implementing a custom comparer as well, but this cannot be translated to SQL, obviously which is why this doesn't work either.
GetAll().GroupBy(x => x.pageId).Select(_ => new {key = _.Key, page = _.FirstOrDefault().Page, count = _.Count()});
This will group by on the pageId, however the select will create a new anonymous object which will contain the key (pageId) and select the first PageClass object
You don't need any grouping if you query the pages directly and use a navigation property that I assume exist (or else should be added):
var pinnedPages = context.Pages
.Select(p => new
{
Page = p
Pins = p.Pins.Count()
});
foreach (var pinnedPage in pinnedPages)
{
var numberOfTimesPinned = pinnedPage.Pins;
var pin = pinnedPage.Page;
//write a line to the report
}
I use context.Pages because the source of the statement should be IQueryable. GetAll returns IEnumerable (apparently, otherwise the GroupBy overload with a comparer wouldn't work).

LINQ query returns duplicate despite Distinct()

Ive got the following query that binds to a DropDownList;
if (!Page.IsPostBack)
{
var branchTags =
(
from t in context.ContactSet
orderby t.py3_BranchArea
where t.py3_BranchArea != null
select new
{
BranchTagCode = t.py3_BranchArea,
BranchTag = (t.FormattedValues != null && t.FormattedValues.Contains("py3_brancharea") ? t.FormattedValues["py3_brancharea"] : null)
}
).Distinct();
ddlBranchTags.DataSource = branchTags;
ddlBranchTags.DataBind();
}
For some reason it still ourputs 2 rows that are visually the same. It might be the case that there are two enitites in the CRM with the same name. But, if Im using distinct on the query and only returning the 'py3_brancharea' then surely the Distinct should be run on the actual records returned?
So, this suggests to me -and my limited LINQ knowledge- that its because of the line:
BranchTagCode = t.py3_BranchArea
But, this needs to be called to make it possible to call the FormattedValues.
How then do I get a distinct set of results based purely on 'BranchTag' ?
If Distinct() is not working it is possibly a problem with the particular classes gethashcode() or equals() override methods, which are either not set up correctly or omitted entirely. In a custom class you will most likely need to specify these overrides to get Distinct() and other like methods to function correctly.
You could try to use a where or any clause to differentiate between duplicates as well. Which could be a work around for the Distinct() issues.
To further explain how to set up the Distinct() Method with custom classes. You will need to within the class that you are searching through set the override methods GetHashCode() and Equals(). These or Object level methods that should be in every single class no matter what. To start head to the class in question and type this:
public override bool Equals(object obj)
then
public override int GetHashCode()
Lets say you have this simple class before the overrides:
class foo{
int H {get;set;}
public foo(int _h){
H = _h;
}
}
It would now look like this:
class foo{
int H {get;set;}
int K {get;set;}
public override bool Equals(object obj){
if(obj == null) return false;
foo test = (foo)obj);
if(test == null) return false;
if(this.H == obj.H && this.K == obj.K) return true;
}
public override int GetHashCode(){
int hashH = H.GetHashCode();
int hashK = K.GetHashCode();
return hashH ^ hashK;
}
public foo(int _h){
H = _h;
}
}
Now you could use Distinct() on Ienumerable types containing the foo class like so:
List<foo> FooList = new List<foo>(Collection of 9 Foos);
var res = FooList.Distinct();
Another, much more simple way that worked for me, but may not work in all situations, is using this guys method ( GroupBy() and First()):
Finding Distinct Elements in a List
He creates a List<Customer> customers with FirstName and LastName. Then groups them all by FirstName and grabs the first element from each group!
`
List< Customer > customers = new List< Customer >;
{
new Customer {FirstName = "John", LastName = "Doe"},
new Customer {FirstName = "Jane", LastName = "Doe"},
new Customer {FirstName = "John", LastName = "Doe"},
new Customer {FirstName = "Jay", LastName = null},
new Customer {FirstName = "Jay", LastName = "Doe"}
};
`
Then:
`
var distinctCustomers = customers.GroupBy(s => s.FirstName)
.Select(s => s.First());
`
In my situation, I had to use FirstOrDefault() though.
Is it possible that the two results are different, do they have the same branch tag code and branch tag?
You could implement a custom equality comparer and pass it to distinct() so that it only compares the field that you want? it's a bit more difficult because of the anonymous type in your select statement, but this answer has a way around that.
The default equality comparison for anonymous types is case sensitive. Do the returned values you expect have different casing? As Matt suggested you may want to look at a custom IEqualityComparer implementation on a custom class otherwise.
I changed my code from
.Distinct().ToList();
to
.ToList().Distinct().ToList();
and now it's able to avoid the duplicate. Not sure what's the reason behind.
Another possibility it that the Entity Object has a define key that is not unique.

C# return generic list of objects using linq

i got a generic list that looks like this:
List<PicInfo> pi = new List<PicInfo>();
PicInfo is a class that looks like this:
[ProtoContract]
public class PicInfo
{
[ProtoMember(1)]
public string fileName { get; set; }
[ProtoMember(2)]
public string completeFileName { get; set; }
[ProtoMember(3)]
public string filePath { get; set; }
[ProtoMember(4)]
public byte[] hashValue { get; set; }
public PicInfo() { }
}
what i'm trying to do is:
first, filter the list with duplicate file names and return the duplicate objects;
than, filter the returned list with duplicate hash value's;
i can only find examples on how to do this which return anonymous types. but i need it to be a generic list.
if someone can help me out, I'd appreciate it. also please explain your code. it's a learning process for me.
thanks in advance!
[EDIT]
the generic list contains a list of objects. these objects are pictures. every picture has a file name, hash value (and some more data which is irrelevant at this point). some pictures have the same name (duplicate file names). and i want to get a list of the duplicate file names from this generic list 'pi'.
But those pictures also have a hash value. from the file names that are identical, i want another list of those identical files names that also have identical hash values.
[/EDIT]
Something like this should work. Whether it is the best method I am not sure. It is not very efficient because for each element you are iterating through the list again to get the count.
List<PicInfo> pi = new List<PicInfo>();
IEnumerable<PicInfo> filt = pi.Where(x=>pi.Count(z=>z.FileName==x.FileName)>1);
I hope the code isn't too complicated to need explaining. I always think its best to work it out on your own anyway but if anythign is confusing then just ask and I'll explain.
If you want the second filter to be filtering for the same filename and same hash being a duplicate then you just need to extend the lambda in the Count to check against hash too.
Obviously if you just want filenames at the end then it is easy enough to do a Select to get just an enumerable list of those filenames, possibly with a Distinct if you only want them to appear once.
NB. Code written by hand so do forgive typos. May not compile first time, etc. ;-)
Edit to explain code - spoilers! ;-)
In english what we want to do is the following:
for each item in the list we want to select it if and only if there is more than one item in the list with the same filename.
Breaking this down to iterate over the list and select things based on a criteria we use the Where method. The condition of our where method is
there is more than one item in the list with the same filename
for this we clearly need to count the list so we use pi.Count. However we have a condition that we are only counting if the filename matches so we pass in an expression to tell it only to count those things.
The expression will work on each item of the list and return true if we want to count it and false if we don't want to.
The filename we are interested in is on x, the item we are filtering. So we want to count how many items have a filename the same as x.FileName. Thus our expression is z=>z.FileName==x.FileName. So z is our variable in this expression and x.FileName in this context is unchanging as we iterate over z.
We then of course put our criteria in of >1 to get the boolean value we want.
If you wanted those that are duplicates when considering the filename and hashvalue then you would expand the part in the Count to be z=>z.FileName==x.FileName && z.hashValue==x.hashValue.
So your final code to get the distinct on both values would be:
List pi = new List();
List filt = pi.Where(x=>pi.Count(z=>z.FileName==x.FileName && z.hashValue==x.hashValue)>1).ToList();
If you wanted those that are duplicates when considering the filename and hashvalue then you would expand the part in the Count to compare the hashValue as well. Since this is an array you will want to use the SequenceEqual method to compare them value by value.
So your final code to get the distinct on both values would be:
List<PicInfo> pi = new List<PicInfo>();
List<PicInfo> filt = pi.Where(x=>pi.Count(z=>z.FileName==x.FileName && z.hashValue.SequenceEqual(x.hashValue))>1).ToList();
Note that I didn't create the intermediary list and just went straight from the original list. You could go from the intermediate list but the code would be much the same if going from the original as from a filtered list.
I think, you have to use SequenceEqual method for finding dublicate
(http://msdn.microsoft.com/ru-ru/library/bb348567.aspx).
For filter use
var p = pi.GroupBy(rs => rs.fileName) // group by name
.Where(rs => rs.Count() > 1) // find group whose count greater than 1
.Select(rs => rs.First()) // select 1st element from each group
.GroupBy(rs => rs.hashValue) // now group by hash value
.Where(rs => rs.Count() > 1) // find group has multiple values
.Select(rs => rs.First()) // select first element from group
.ToList<PicInfo>() // make the list of picInfo of result

converting query from IList to object

I'm newbie to c# and i have problem trying to access IList after i assign it to the query. Here is my code:
System.Collections.IList Invoices =
(from p in entities.InvoiceCards
where (p.CustomerCard.ID == CustomerID)
select new
{
InvoiceID = p.ID,
InvoiceDatetime = p.DateTime,
InvoiceTotal = (decimal) p.InvoiceTotal,
}).ToList();
// update the grid
invoiceCardDataGridView.DataSource = Invoices;
----------- Here the compiler is complaining about object c? how can I access the objects in the IList without executing the query again? I need to use IList to use as a datasource. what is a better way? Please include the code
foreach (var c in Invoices)
InvoiceTotal += (decimal)c.InvoiceTotal;
Your problem that you are using anonymous type in your query.
So when you get an IList of this anonymous type and assign to a datasource, by default you will loose its type.
When you want to retrieve it from the DataSource in another part of your code you have to cast it with the appropriate type. Since the anonymous type is generated by the compiler you will not be able to cast it.
A solution is to create the class that contain the type if doesn't exist already.
public class InvoicePart
{
public int InvoiceID {get; set}
public DateTime InvoiceDatetime {get; set}
public decimal InvoiceTotal {get; set}
}
Now you can modify your query to get a typed List
List<InvoicePart> Invoices =
(from p in entities.InvoiceCards
where (p.CustomerCard.ID == CustomerID)
select new InvoicePart
{
InvoiceID = p.ID,
InvoiceDatetime = p.DateTime,
InvoiceTotal = (decimal) p.InvoiceTotal,
}).ToList();
// update the grid
invoiceCardDataGridView.DataSource = Invoices;
and when you will get your data you will cast it to a List
List<InvoicePart> Invoices = (List<InvoicePart>)invoiceCardDataGridView.DataSource;
foreach (InvoicePart c in Invoices)
{
invoiceTotal += c.InvoiceTotal;
}
If the list contains anonymous types and the foreach loop is in another method than the first block of code you cannot use it that way.
Please have a look to this post that maybe could help in your case.
If you absolutely have to use IList then you are better of defining an explicit type rather than using an anonymous type. Then you'll have to cast the elements of the IList to your explicit type when you need to work with them.
Zied has the right idea about solving this issue. Note, however, that binding to a List<T> is not bidierctional (changes to the list will not reflect in the grid). For that you need to use a BindingSource:
List<InvoicePart> Invoices =
(from p in entities.InvoiceCards
where (p.CustomerCard.ID == CustomerID)
select ...
// update the grid
var bs = new BindingSource();
bs.DataSource = Invoices;
invoiceCardDataGridView.DataSource = bs;

How to compare two struct lists?

I have a small struct and I have to compare the values to find which ones have the same FreeFlow text, and then grab that struct ENumber.
public struct Holder
{
public string FreeFlow;
public int ENumber;
}
and here is how I add them
foreach(Class1.TextElement re in Class1._TextElements)
{
//create struct with all details will be good for later
Holder ph = new Holder();
ph.FreeFlow = re.FreeFlow;
ph.ENumber = re.ENumber;
lstHolder.Add(ph);
}
foreach(Class1.TextElement2 re in Class1._TextElements2)
{
//create struct with all details will be good for later
Holder phi = new Holder();
phi.FreeFlow = re.FreeFlow;
phi.ENumber = re.ENumber;
lstHolder2.Add(phi);
}
I can do a comparing using a foreach within a foreach, but I think this will not be the most effective way. Any help?
EDIT: I am trying to determine if freeflow text is exactly the same as the other struct freeflow text
I have to compare the values to find
which ones have the same FreeFlow
text, and then grab that struct
ENumber.
If you can use LINQ you can join on the items with the same FreeFlow text then select the ENumber values of both items:
var query = from x in Class1._TextElements
join y in Class1._TextElements2 on x.FreeFlow equals y.FreeFlow
select new { xId = x.ENumber, yId = y.ENumber };
foreach (var item in query)
{
Console.WriteLine("{0} : {1}", item.xId, item.yId);
}
EDIT: my understanding is the FreeFlow text is the common member and that ENumber is probably different, otherwise it would make sense to determine equivalence based on that. If that is the case the join query above should be what you need.
If I'm interpreting you correctly, you want to find the elements that are in both lstHolder and lstHolder2 - which is the intersection. If I'm interpreting correctly, then 2 step solution: first, override Equals() on your Holder struct. then use teh LINQ intersect operator:
var result = lstHolder.Intersect(lstHolder2);
What do you mean by "compare"? This could mean a lot of things. Do you want to know which items are common to both sets? Do you want to know which items are different?
LINQ might have the answer no matter what you mean. Union, Except, etc.
If you are using C# 3.0 or higher then try the SequenceEqual method
Class1._TextElements.SequenceEqual(Class1._TextElements2);
This will run equality checks on the elements in the collection. If the sequences are of different lengths or any of the elements in the same position are not equal it will return false.

Categories