Excluding items from a list by object property - c#

I'm trying to build a list of items based on their presence in a list.
itemsAll contains all products
itemsNew contains only new products
I'd like itemsOld to contain only old products (i.e. itemsAll -
itemsNew)
This was my approach, which doesn't return the correct number of items.
var itemsAll = objProductStagingRepository.AllImports(fileId, cid).ToList();
var itemsNew = objProductStagingRepository.DetectNonPresentProductNames(fileId, cid).ToList();
var itemsOld = from t1 in itemsAll where !(from o in itemsNew select o.Id).Contains(t1.Id)
select t1; // this does not work
Does anybody have any suggestions as to how I shuold be approacing this? I have tried itemsAll.Except(itemsNew) which also doesn't yield the correct results!

I think you probably could use the Except method, but you would need to provide an equality comparer for the method to know when two items are equal.
http://msdn.microsoft.com/en-us/library/bb336390.aspx
In your question it looks like you're not using your own comparer, so it's comparing the items to see if they are the same object in memory (most likely), which is not what you're trying to do.
You want to compare the objects by database identity, which means you need to provide you're own comparer.
Example:
public class Item
{
public int Id { get; set; }
}
class ItemComparer : IEqualityComparer<Item>
{
public bool Equals(Item x, Item y)
{
if (Object.ReferenceEquals(x, y)) return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.Id == y.Id;
}
public int GetHashCode(Item value)
{
if (Object.ReferenceEquals(value, null)) return 0;
int hash = value.Id.GetHashCode();
return hash;
}
}

itemsOld.AddRange(itemsAll.Where(p => !itemsNew.Any(a => a.Id == p.Id)));

I prefer the fluent syntax so:
var itemsOld = itemsAll.Where(x => !itemsNew.Any(y => y.Id == x.Id));
or
var itemsOld = itemsAll.Where(x => !itemsNew.Exists(y => y.Id == x.Id));

This might work
var itemsOld = from a in itemsAll
join n in itemsNew on a.Id equals n.Id into ng
where !ng.Any()
select a;

Related

Remove Duplicate value from List<T>

I have one list which has data and sometimes it contains duplicate rows and I want to remove that duplicate row for that I used below code
num = numDetailsTemp.Distinct().ToList();
var query = num.GroupBy(o => new { o.Number })
.Select(group =>
new
{
Name = group.Key,
Numbers = group.OrderByDescending(x => x.Date)
})
.OrderBy(group => group.Numbers.First().Date);
List<NumberDetails> numTemp = new List<NumberDetails>();
foreach (var group in query)
{
foreach (var numb in group.Numbers)
{
numTemp.Add(numb);
break;
}
}
num = numTemp;
The below image shows the duplicate value from the list.
And when I apply remove duplicate it give me an output
But I want to remove that row which not contains alter no or id proof and date like shown in first image first row not, contains AlterNo and ID Proof and date and the second row contains that so I want to remove the first row and display only second row. The date is compulsory to check and after that AlterNo and ID Proof.
You can try the following:
var group =
list
.GroupBy(r => r.Number)
.SelectMany(g => g) //flatten your grouping and filter where you have alterno and id
.Where(r => !string.IsNullOrEmpty(r.AlterNo) && !string.IsNullOrEmpty(r.Id))
.OrderByDescending(r=>r.Date)
.ToList();
You may eliminate duplicates using Distinct operator. First you need to define a comparer class which implements IEqualityComparer interface, and then pass it to the distinct operator in your method.
internal class NumberDetailsComparer : IEqualityComparer<NumberDetails>
{
public bool Equals(NumberDetails x, NumberDetails y)
{
if (\* Set of conditions for equality matching *\)
{
return true;
}
return false;
}
public int GetHashCode(Student obj)
{
return obj.Name.GetHashCode(); // Name or whatever unique property
}
}
And here is how to use it:
var distinctRecords = source.Distinct(new NumberDetailsComparer());
All you need to do is define the criteria for comparer class.
Hope this solves your problem.
This link could be useful for a fully working example:
http://dotnetpattern.com/linq-distinct-operator
So you have a sequence of NumberDetails, and a definition about when you would consider to NumberDetails equal.
Once you have found which NumberDetails are equal, you want to eliminate the duplicates, except one: a duplicate that has values for AlterNo and IdProof.
Alas you didn't specify what you want if there are no duplicates with values for AlterNo and IdProof. Nor what you want if there are several duplicates with values for AlterNo and IdProof.
But let's assume that if there are several of these items, you don't care: just pick one, because they are duplicates anyway.
In your requirement you speak about duplicates. So let's write a class that implements your requirements of equality:
class NumberDetailEqualityComparer : IEqualityComparer<NumberDetail>
{
public static IEQualityComparer<NumberDetail> Default {get;} = new NumberDetaulEqualityComparer();
public bool Equals(NumberDetail x, NumberDetail y)
{
if (x == null) return y == null; // true if both null
if (y == null) return false; // because x not null and y null
if (Object.ReferenceEquals(x, y) return true; // because same object
if (x.GetType() != y.GetType()) return false; // because not same type
// by now we are out of quick checks, we need a value check
return x.Number == y.Number
&& x.FullName == y.FullName
&& ...
// etc, such that this returns true if according your definition
// x and y are equal
}
You also need to implement GetHashCode. You can return anything you want, as long as you
are certain that if x and y are equal, then they return the same HashCode
Furthermore it would be more efficient that if x and y not equal,
then there is a high probability for different HashCode.
Something like:
public int GetHashCode(NumberDetail numberDetail)
{
const int prime1 = 12654365;
const int prime2 = 54655549;
if (numberDetail == null) return prime1;
int hash = prime1;
unsafe
{
hash = prime2 * hash + numberDetail.Number.GetHashCode();
hash = prime2 * hash + numberDetail.FullName.GetHashCode();
hash = prime2 * hash + numberDetail.Date.GetHashCode();
...
}
return hash;
Of course you have to check if any of the properties equal NULL before asking the HashCode.
Obviously in your equality (and thus in GetHashCode) you don't look at AlterNo nor IdProof.
Once that you've defined precisely when you consider two NumberDetails equal, you can make groups of equal NumberDetails
var groupsEqualNumberDetails = numberDetails.GroupBy(
// keySelector: make groups with equal NumberDetails:
numberDetail => numberDetail,
// ResultSelector: take the key and all NumberDetails thas equal this key:
// and keep the first one that has values for AlterNo and IdProof
(key, numberDetailsEqualToKey) => numberDetailsEqualToKey
.Where(numberDetail => numberDetail.AlterNo != null
&& numberDetail.IdProof != null)
.FirstOrDefault(),
// KeyComparer: when do you consider two NumberDetails equal?
NumberDetailEqualityComparer.Default;
}

What to do to get only one List?

Hello i have a method that compares the objects of 2 Lists for differences. Right now this works but only for one property at a time.
Here is the Method:
public SPpowerPlantList compareTwoLists(string sqlServer, string database, DateTime timestampCurrent, string noteCurrent, DateTime timestampOld, string noteOld)
{
int count = 0;
SPpowerPlantList powerPlantListCurrent = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampCurrent, noteCurrent);
SPpowerPlantList powerPlantListOld = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampOld, noteOld);
SPpowerPlantList powerPlantListDifferences = new SPpowerPlantList();
count = powerPlantListOld.Count - powerPlantListCurrent.Count;
var differentObjects = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.mwWeb == l.mwWeb)).ToList();
foreach (var differentObject in differentObjects)
{
powerPlantListDifferences.Add(differentObject);
}
return powerPlantListDifferences;
}
This works and i get 4 Objects in the new List. The Problem is that i have a few other properties that i need to compare. Instead of mwWeb for example name. When i try to change it i need for every new property a new List and a new Foreach-Loop.
e.g.
int count = 0;
SPpowerPlantList powerPlantListCurrent = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampCurrent, noteCurrent);
SPpowerPlantList powerPlantListOld = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampOld, noteOld);
SPpowerPlantList powerPlantListDifferences = new SPpowerPlantList();
SPpowerPlantList powerPlantListDifferences2 = new SPpowerPlantList();
count = powerPlantListOld.Count - powerPlantListCurrent.Count;
var differentObjects = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.mwWeb == l.mwWeb)).ToList();
var differentObjects2 = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.shortName == l.shortName)).ToList();
foreach (var differentObject in differentObjects)
{
powerPlantListDifferences.Add(differentObject);
}
foreach (var differentObject in differentObjects2)
{
powerPlantListDifferences2.Add(differentObject);
}
return powerPlantListDifferences;
Is there a way to prevent this? or to make more querys and get only 1 List with all different Objects back?
I tried it with except and intersect but that didnt worked.
So any help or advise would be great and thx for your time.
PS: If there is something wrong with my question-style please say it to me becouse i try to learn to ask better questions.
You may be able to simply chain the properties that you wanted to compare within your Where() clause using OR statements :
// This should get you any elements that have different A properties, B properties, etc.
var different = current.Where(p => !old.Any(l => p.A == l.A || p.B == l.B))
.ToList();
If that doesn't work and you really want to use the Except() or Intersect() methods to properly compare the objects, you could write your own custom IEqualityComparer<YourPowerPlant> to use to properly compare them :
class PowerPlantComparer : IEqualityComparer<YourPowerPlant>
{
// Powerplants are are equal if specific properties are equal.
public bool Equals(YourPowerPlant x, YourPowerPlant y)
{
// Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
// Checks the other properties to compare (examples using mwWeb and shortName)
return x.mwWeb == y.mwWeb && x.shortName == y.shortName;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(YourPowerPlant powerPlant)
{
// Check whether the object is null
if (Object.ReferenceEquals(powerPlant, null)) return 0;
// Get hash code for the mwWeb field if it is not null.
int hashA = powerPlant.mwWeb == null ? 0 : powerPlant.mwWeb.GetHashCode();
// Get hash code for the shortName field if it is not null.
int hashB = powerPlant.shortName == null ? 0 : powerPlant.shortName.GetHashCode();
// Calculate the hash code for the product.
return hashA ^ hashB;
}
}
and then you could likely use something like one of the following depending on your needs :
var different = current.Except(old,new PowerPlantComparer());
or :
var different = current.Intersect(old,new PowerPlantComparer());
One way is to use IEqualityComparer as Rion Williams suggested, if you'd like a more flexible solution you can split logic in to two parts. First create helper method that accepts two lists, and function where you can define what properties you wish to compare. For example :
public static class Helper
{
public static SPpowerPlantList GetDifference(this SPpowerPlantList current, SPpowerPlantList old, Func<PowerPlant, PowerPlant, bool> func)
{
var diff = current.Where(p => old.All(l => func(p, l))).ToList();
var result = new SPpowerPlantList();
foreach (var item in diff) result.Add(item);
return result;
}
}
And use it :
public SPpowerPlantList compareTwoLists(string sqlServer, string database,
DateTime timestampCurrent, string noteCurrent,
DateTime timestampOld, string noteOld)
{
var powerPlantListCurrent = ...;
var powerPlantListOld = ...;
var diff = powerPlantListCurrent.GetDifference(
powerPlantListOld,
(x, y) => x.mwWeb != y.mwWeb ||
x.shortName != y.shortName);
return diff;
}
P.S. if it better suits your needs, you could move method inside of existing class :
public class MyClass
{
public SPpowerPlantList GetDifference(SPpowerPlantList current, SPpowerPlantList old, Func<PowerPlant, PowerPlant, bool> func)
{
...
}
}
And call it (inside of class) :
var result = GetDifference(currentValues, oldValues, (x, y) => x.mwWeb != y.mwWeb);
The easiest way to do this would be to compare some unique identifier (ID)
var differentObjects = powerPlantListCurrent
.Where(p => !powerPlantListOld.Any(l => p.Id == l.Id)
.ToList();
If the other properties might have been updated and you want to check that too, you'll have to compare all of them to detect changes made to existing elements:
Implement a camparison-method (IComparable, IEquatable, IEqualityComparer, or override Equals) or, if that's not possible because you didn't write the class yourself (code generated or external assembly), write a method to compare two of those SPpowerPlantList elements and use that instead of comparing every single property in Linq. For example:
public bool AreThoseTheSame(SPpowerPlantList a,SPpowerPlantList b)
{
if(a.mwWeb != b.mwWeb) return false;
if(a.shortName != b.shortName) return false;
//etc.
return true;
}
Then replace your difference call with this:
var differentObjects = powerPlantListCurrent
.Where(p => !powerPlantListOld.Any(l => AreThoseTheSame(p,l))
.ToList();

C# Distinct with ability to choose which object to save, which ones to remove

I implemented this comparer which works OK.
class ReservationDatesDistinctComparer : IEqualityComparer<ReservationModel>
{
public bool Equals(ReservationModel x, ReservationModel y)
{
return x.FromDate.Date== y.FromDate.Date && x.ToDate.Date == y.ToDate.Date && x.UnitId == x.UnitId;
}
public int GetHashCode(ReservationModel product)
{
int hashProductCode = 1;
return hashProductCode;
}
}
But on ReservationModel I have some other property let's call it ReservationType and I would like to filter out with distinct same dates but keep only ReservationModel who has Type A not Type B.
How it is posible to affect on Distinct which model it will choose?
Distinct will keep the elements it encounters first, a possible solution would be to order those which have ReservationType A first:
reservatonModels.OrderByDescending(m => m.ReservationType == ReservationType.A)
.Distinct(new ReservationDatesDistinctComparer());
I don't think you can use Distinct for this. (Unless you want to rely on undocumented implementation details, as per Lukazoid's answer.)
Something similar to this might do the trick. (Group the elements that your comparer deems to be equal, then order each group so that Type A is prioritised, then take the first element from each group.)
var result = source.GroupBy(x => x, new ReservationDatesDistinctComparer())
.Select(g => g.OrderBy(x => (x.ReservationType == "Type A") ? 1 : 2)
.First());

Select 1 object from list with LINQ based on 2 vars

I've been trying to solve this by reading what was on StackOverflow and the information was quiet helpful but i can't seem to implement the things i found.
I have a data List and i want to check if an entry exists in the data set that match 2 variables i provide.
public void SaveWaveDataFor( List<WaveData> newData )
{
foreach(WaveData wave in newData)
{
//WaveData item = data.FirstOrDefault( o => o.id == wave.id );
var item = data.Select( o => new{ wave.id, wave.waveNumber } );
Debug.Log( item.id );
}
}
If you want to get all of the wave objects that match two criteria, you can use a Where() clause:
// items will be an IEnumerable<WaveData> containing the matching objects
// where id == matchId and waveNumber == matchNumber
var items = data.Where(o => o.id == matchId && o.waveNumber == matchNumber);
The Select() clause is typically used to transform the matching elements into objects of another type.
The commented out line is better for this actually.
FirstOrDefault will return either the first matching item or null if no items match.
On the other hand, you could use Any() if you just want to know if an item exists.
Any(x=>x.Id == matchId) will return true only if the list contains an item with a matching Id, false otherwise.
You would do it like this:
public void SaveWaveDataFor( List<WaveData> newData )
{
int waveIdToMatch = 1;
int waveNumberToMatch = 2;
foreach(WaveData wave in newData)
{
WaveData item = data.FirstOrDefault( o => o.id == waveIdToMatch && o.waveNumber == waveNumberToMatch );
//if a match exists, item will not be a WaveData object, otherwise it will be null
Debug.Log( item.id );
}
}
If you just want to check whether entry exists or not then you can use Any Operator from Linq.
bool recordsExists = data.Any(o => o.id == matchId && o.waveNumber == matchNumber);

Why distinct in LINQ does not work for this case?

I have this comparer for my object Tenant
public class TenantComparer : IEqualityComparer<Tenant>
{
// Products are equal if their names and product numbers are equal.
public bool Equals(Tenant x, Tenant y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.Name == y.Name;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Tenant tenant)
{
//Check whether the object is null
if (Object.ReferenceEquals(tenant, null)) return 0;
//Get hash code for the Name field if it is not null.
int hashProductName = tenant.Name == null ? 0 : tenant.Name.GetHashCode();
//Calculate the hash code for the product.
return hashProductName;
}
}
Now, I have a table with some tenants and some of them have the same name. I want to fetch those who are distinct order by name:
public static List<Tenant> GetTenantListOrderyByNameASC()
{
DataClassesDataContext db = new DataClassesDataContext();
var tenantsList = (from t in db.Tenants
select t).Distinct().OrderBy( x => x.Name ).ToList();
return tenantsList;
}
But it still shows the tenants with the same names...
Can you please tell me where I am wrong?
You need to provide comparer explicitly, which at the moment you do not:
var tenantsList = (from t in db.Tenants
select t)
.Distinct(new TenantComparer())
.OrderBy( x => x.Name )
.ToList();
See the documentation.

Categories