LINQ GroupBy object with possible null values - c#

I have a following GroupBy query:
var groups = ordered.GroupBy(k => new
{
a = k[SelectedFirstCategory],
b = k[SelectedSecondCategory],
c = k[SelectedThirdCategory]
});
The problem is that Categories can be null.
How can I modify my GroupBy to accomodate for this. I know I could go with multiple "if" but that is not elegant for me.
To make this clearer an example would be that if SelectedSecondCategory is null but other are filled than resulting grouping should be according to "a" and "c" property.
I thought about null coalescent like this:
a = SelectedFirstCategory != null ? k[SelectedFirstCategory] : DON'T KNOW WHAT TU PUT HERE.
Alternative value should be something that would be irrelevant to groupBy.
Thanks in advance.

I appears to be quite easy:
var groups = ordered.GroupBy(k => new {
a = !String.IsNullOrEmpty(SelectedFirstCategory) ? k[SelectedFirstCategory] : null,
b = !String.IsNullOrEmpty(SelectedSecondCategory) ? k[SelectedSecondCategory] : null,
c = !String.IsNullOrEmpty(SelectedThirdCategory) ? k[SelectedThirdCategory] : null
});
It appears that if some parameter of grouping is null it is not taken into consideration when creating a group so that is what I wanted.

Use a helper method, Just be aware that you grouping may not make any sense. Suppose you had you columns being "Fruit", "Color", "Shape". Grouping ["Apple", "Red", "Round"] with ["Banana", "Oval"] doesn't give good results.
static void Main(string[] args)
{
List<List<object>> ordered = new List<List<object>>();
int SelectedFirstCategory = 1;
int SelectedSecondCategory = 2;
int SelectedThirdCategory = 3;
var groups = ordered.GroupBy(k => MyGroup(
k[SelectedFirstCategory],
k[SelectedSecondCategory],
k[SelectedThirdCategory])
);
}
static List<object> MyGroup(object a, object b, object c)
{
List<object> results = new List<object>();
if (a != null) results.Add(a);
if (b != null) results.Add(b);
if (c != null) results.Add(c);
return results;
}​

Related

Joining two lists from each statement

if (Settings.Default.All)
{
List = new ObservableCollection<LexisNexis>(UnitOfWork.Query.Lexis.LexisForApprove2().OrderBy(x => x.TxnID).Reverse());
}
if (Settings.Default.MLhuillier)
{
List = new ObservableCollection<LexisNexis>(UnitOfWork.Query.Lexis.LexisForApprove2().Where(x => x.ServiceMode == "MLhuillier").OrderBy(x => x.TxnID).Reverse());
}
if (Settings.Default.BPI)
{
List = new ObservableCollection<LexisNexis>(UnitOfWork.Query.Lexis.LexisForApprove2().Where(x => x.ServiceMode == "BPI").OrderBy(x => x.TxnID).Reverse());
}
I want to combine each list from each if statement that returns true. my program just return the last list. TYIA
Simplifying the code
The following should do what you want with little duplication and with at most one traversal through LexisForApprove2.
var orFilters = Settings.Default.All ? null : new List<string>();
if (!Settings.Default.All)
{
if (Settings.Default.MLhuillier) orFilters.Add("MLhuillier");
if (Settings.Default.BPI) orFilters.Add("BPI");
}
var l = orFilters == null
? UnitOfWork.Query.Lexis.LexisForApprove2() // Everything
: orFilters.Any()
? UnitOfWork.Query.Lexis.LexisForApprove2().Where(x => orFilters.Contains(x.ServiceMode))
: new List<LexisNexis>(); // Not 'All' but no others allowed
List = new ObservableCollection<LexisNexis>(l.OrderByDescending(y => y.TxnID));
Distinct
Just for the record, and not recommened for this case, you could use List's AddRange or Linq's Union followed by Distinct, which would work if the LexisNexis objects are good at comparing themselves with others :)

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

Linq to get difference of two listviews and put it in third in Windows Form C#

I have two list views which have same data but differing in the number of records. I want to get the non-matching listviewitems in third list view. I have using the following code but it is not helping. The variables x and y are making problem.
var list1Source = lvFace.Items.Cast<ListViewItem>();
var list2Source = lvDBdata.Items.Cast<ListViewItem>();
lvDataToUpload = list1Source.Where(
(x => list2Source.All(y => y.Text != x.Text));
You are looking for LINQ Except method
var lvExcept1 = list1Source.Except(list2Source);
var lvExcept2 = list2Source.Except(list1Source);
lvDataToUpload = lvExcept1.Union(lvExcept2);
But you need to override Equals and GetHashCode methods for your ListViewItem class. If there is no option to do this (ListViewItem is Windows Forms class, not yours), you can define your own equality comparer:
public class ListViewItemComparer : IEqualityComparer<ListViewItem>
{
bool IEqualityComparer<ListViewItem>.Equals(ListViewItem x, ListViewItem y)
{
return (x.Text == y.Text);
}
int IEqualityComparer<ListViewItem>.GetHashCode(ListViewItem obj)
{
if (Object.ReferenceEquals(obj, null))
return 0;
return obj.Text.GetHashCode();
}
}
And final code is:
var lvExcept1 = list1Source.Except(list2Source, new ListViewItemComparer());
var lvExcept2 = list2Source.Except(list1Source, new ListViewItemComparer());
lvDataToUpload = lvExcept1.Union(lvExcept2);
LINQ doesn't have a "set difference" operator itself... but you can use Except twice:
var list1Text = list1Source.Select(x => x.Text);
var list2Text = list2Source.Select(x => x.Text);
var difference = list1Text.Except(list2Text)
.Concat(list2Text.Except(list1Text))
.ToList();
Try this
listIntersection = list1Source.Intersect(list2Source); // Gets matching elements
listUnion = list1Source.Union(list2Source); // Gets all elements
lvDataToUpload = listUnion.Except(listIntersection);

LINQ - Assign a Value to Anonymous Type's Read Only Property

I would like to create an anonymous type from linq. Then change the value of a single property(status) manually and give the list to a repeater as data source. But doesn't let me do that as theay are read-only. Any suggestion?
var list = from c in db.Mesai
join s in db.MesaiTip on c.mesaiTipID equals s.ID
where c.iseAlimID == iseAlimID
select new
{
tarih = c.mesaiTarih,
mesaiTip = s.ad,
mesaiBaslangic = c.mesaiBaslangic,
mesaiBitis = c.mesaiBitis,
sure = c.sure,
condition = c.onaylandiMi,
status = c.status
};
foreach (var item in list)
{
if (item.condition==null)
{
item.status == "Not Confirmed";
}
}
rpCalisanMesai.DataSource = list.ToList();
rpCalisanMesai.DataBind();
Instead of trying to change the value after creating the list, just set the right value while creating the list.
var list = from c in db.Mesai
join s in db.MesaiTip on c.mesaiTipID equals s.ID
where c.iseAlimID == iseAlimID
select new
{
tarih = c.mesaiTarih,
mesaiTip = s.ad,
mesaiBaslangic = c.mesaiBaslangic,
mesaiBitis = c.mesaiBitis,
sure = c.sure,
condition = c.onaylandiMi,
status = c.onaylandiMi != null ? c.status : "Not Confirmed"
};
Also, if you could change the property, your problem would be executing the query twice: first in the foreach-loop, and then again by calling list.ToList() (which would create new instances of the anonymous type).
You cannot, anonymous type's properties are read-only.
You need to set it during object creation. See #Dominic answer for code sample.
You can. For instance:
var data = (from a in db.Mesai select new { ... status = new List<string>() .. }).ToList();
Next, compute your status:
foreach (var item in data) {
item.status.Add("My computed status");
}
And then on rendering:
foreach (var item data) {
Response.Write(item.status[0]);
}
EDIT: The list can even be intialized as per your requirement:
var data = (from a in db.Mesai select new { ... status = new List<string>(new
string[] { c.status }) .. }).ToList();
foreach (var item in data) {
item.status[0] = "My computed status";
}
EDIT2: Seems like you must initialize the list, preferably with e.g. c.rowid.ToString(), otherwise the optimizer assigns the same new List() to all items, thinking that this might be some game or something.

How to convert List<AnonymousType> to List<string>

I want to convert a List<AnonymousType> to List<string>. I have the following code:
var lkpiTodisplay = _MGMTDashboardDB.KPIs.Where(m => m.Compulsory == true && m.Active == true)
.Select(m => new
{
KPI_Name = m.Absolute == true ? m.KPI_Name : (m.KPI_Name + "%")
}).ToList();
for(int i=1; i<= BusinessTargetCol; i++)
{
lkpiTodisplay.Add(new
{
KPI_Name = "Business Target"
});
}
This code creates a List<AnonymousType>. Then I would like to assign this List to a variable List<string>, as shown in the following code:
DashboardViewModel lYTMDashboard = new DashboardViewModel()
{
KPIList = (List<string>) lkpiTodisplay,
//other assignments
};
The casting does not work. How can I convert the variable? Other solutions that modify the first code snippet are welcome, as long as the KPIList variable is kept as a List<string>.
Thanks
Francesco
You can skip the Anonymous Class if you can and if you have no need for it
lkpiTodisplay.Add("Business Target");
or
you can do
lkpiTodisplay.Select( x => x.KPI_Name).ToList();
to get List<String>
You cannot assign List<AnonymousType> to List<String>, that types are not compatible.
Use lkpiToDiplay.Select(i => i.ToString()).ToList()

Categories