How to perform a this kind of Distinct operation with LINQ? - c#

I have the following foreach loop:
List<WorkingJournal> workingJournals = new List<WorkingJournal>();
foreach (WorkRoster workRoster in workRosters)
{
bool exists = workingJournals.Any(workingJournal => workingJournal.ServicePlan.Id == workRoster.ServicePlan.Id
&& workingJournal.Nurse.Id == workRoster.Nurse.Id
&& workingJournal.Month == workRoster.Start.Month
&& workingJournal.Year == workRoster.Start.Year);
if (exists == false)
{
WorkingJournal workingJournal = new WorkingJournal
{
ServicePlan = workRoster.ServicePlan,
Nurse = workRoster.Nurse,
Month = workRoster.Start.Month,
Year = workRoster.Start.Year
};
workingJournals.Add(workingJournal);
}
}
I started writing:
from workRoster in workRosters
select new WorkingJournal
{
ServicePlan = workRoster.ServicePlan,
Nurse = workRoster.Nurse,
Month = workRoster.Start.Month,
Year = workRoster.Start.Year
};
But now I am stuck with the comparison that produces distinct WorkingJournals.
I have a feeling that a group by clause should be here but I'm not sure how it should be done.

Assuming LINQ to objects:
(from workRoster in workRosters
select new WorkingJournal
{
ServicePlan = workRoster.ServicePlan,
Nurse = workRoster.Nurse,
Month = workRoster.Start.Month,
Year = workRoster.Start.Year
}).Distinct();
Note that for this to work you need Equals and GetHashCode implemented for the WorkingJournal object. If not, see Anthony's answer: How to perform a this kind of Distinct operation with LINQ?
If it's LINQ to SQL you could group by the new expression, then select the group key:
from workRoster in workRosters
group workRoster by new WorkingJournal
{
ServicePlan = workRoster.ServicePlan,
Nurse = workRoster.Nurse,
Month = workRoster.Start.Month,
Year = workRoster.Start.Year
} into workRosterGroup
select workRosterGroup.Key;

If you have proper Equals and GetHashCode implementations inside your class, you can simply invoke Distinct().
var result = workRosters.Select(...).Distinct();
On the chance you do not have such implementations, you can define an IEqualityComparer<WorkingJournal> implementation. This will have you defining Equals and GetHashCode methods for the T that can then be used by a dictionary or hashset and can also be used in overloads of Distinct() in Linq.
class JournalComparer : IEqualityComparer<WorkingJournal>
{
public bool Equals(WorkingJournal left, WorkingJournal right)
{
// perform your equality semantics here
}
public int GetHashCode(WorkingJournal obj)
{
// return some hash code here.
return obj.ServicePlan.GetHashCode();
}
}
var comparer = new JournalComparer(); // implements the interface
var result = workRosters.Select(r => new WorkingJournal { ... }).Distinct(comparer);

Related

Union Lists using IEqualityComparer

I'we got two Lists of my class Nomen:
var N1 = new List<Nomen>();
var N2 = new List<Nomen>();
public class Nomen
{
public string Id;
public string NomenCode;
...
public string ProducerName;
public decimal? minPrice;
}
I need to join them. I used to do it like this:
result = N2.Union(N1, new NomenComparer()).ToList();
class NomenComparer : IEqualityComparer<Nomen>
{
public bool Equals(Nomen x, Nomen y)
{
return x.Equals(y);
}
public int GetHashCode(Nomen nomen)
{
return nomen.GetHashCode();
}
}
public override int GetHashCode()
{
return (Id + NomenCode + ProducerName).GetHashCode();
}
public bool Equals(Nomen n)
{
if (!String.IsNullOrEmpty(Id) && Id == n.Id) return true;
return (NomenCode == n.NomenCode && ProducerName == n.ProducerName);
}
As you can see, if Ids or NomenCode and ProducerName are equal, for me it's the same Nomen.
now my task have changed and I need to take, if they equal, the one with less minPrice. Please, help me to solve this problem.
Tried to do the same with linq, but failed
var groups = (from n1 in N1
join n2 in N2
on new { n1.Id, n1.NomenCode, n1.ProducerName } equals new { n2.Id, n2.NomenCode, n2.ProducerName }
group new { n1, n2 } by new { n1.Id, n1.NomenCode, n1.ProducerName } into q
select new Nomen()
{
NomenCode = q.Key.NomenCode,
ProducerName = q.Key.ProducerName,
minPrice = q.Min(item => item.minPrice)
}).ToList();
Mostly because I need to join Lists by Ids OR {NomenCode, ProducerName} and I don't know how to do it.
Concat, GroupBy and then Select again? for example (less untested than before):
var nomens = N1.Concat(N2)
.GroupBy(n=>n, new NomenComparer())
.Select(group=>group.Aggregate( (min,n) => min == null || (n.minPrice ?? Decimal.MaxValue) < min.minPrice ? n : min));
Linq joins with OR conditions have been answered in this SO post:
Linq - left join on multiple (OR) conditions
In short, as Jon Skeet explains in that post, you should do something like
from a in tablea
from b in tableb
where a.col1 == b.col1 || a.col2 == b.col2
select ...

Linq to objects - Search collection with value from other collection

I've tried to search SO for solutions and questions that could be similar to my case.
I got 2 collections of objects:
public class BRSDocument
{
public string IdentifierValue { get; set;}
}
public class BRSMetadata
{
public string Value { get; set;}
}
I fill the list from my datalayer:
List<BRSDocument> colBRSDocuments = Common.Instance.GetBRSDocuments();
List<BRSMetadata> colBRSMetadata = Common.Instance.GetMessageBRSMetadata();
I now want to find that one object in colBRSDocuments where x.IdentifierValue is equal to the one object in colBRSMetadata y.Value. I just need to find the BRSDocument that matches a value from the BRSMetadata objects.
I used a ordinary foreach loop and a simple linq search to find the data and break when the value is found. I'm wondering if the search can be done completely with linq?
foreach (var item in colBRSMetadata)
{
BRSDocument res = colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value);
if (res != null)
{
//Do work
break;
}
}
Hope that some of you guys can push me in the right direction...
Why not do a join?
var docs = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select d;
If there's only meant to be one then you can do:
var doc = docs.Single(); // will throw if there is not exactly one element
If you want to return both objects, then you can do the following:
var docsAndData = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select new
{
Doc = d,
Data = m
};
then you can access like:
foreach (var dd in docsAndData)
{
// dd.Doc
// dd.Data
}
Use Linq ?
Something like this should do the job :
foreach (var res in colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).Where(res => res != null))
{
//Do work
break;
}
If you are just interested by the first item, then the code would be :
var brsDocument = colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).FirstOrDefault(res => res != null);
if (brsDocument != null)
//Do Stuff

Select from list linq

I am trying to get matches from linq query-
public ActionResult TagFilter(TagModel tag) {
List<CardModel> cardlist = null;
var cardtaglist = (from u in db.CardTagTables
where u.CardTagName == tag.tagName
select u).ToList();
cardlist = (from u in db.CardTables
where u.CardID == cardtaglist.Where(e=>e.FKCardTagID==u.CardID)
select new CardModel {
cardHashCode = tag.tagName,
cardDate = u.CardDate,
cardFileName = u.CardFileName,
cardFilePath = u.CardFilePath,
cardID = u.CardID,
cardTitle = u.CardTitle
}).ToList();
if (cardlist.Count == 0) {
return Json(new { success = false });
}
else {
return PartialView("_FunHomePartial", cardlist);
}
}
Where match of tag=>tagName would be from list cardtaglist.
I get Cannot implicitly convert type int to bool error in line-
where u.CardID == cardtaglist.Where(e=>e.FKCardTagID==u.CardID)
How Do I match elements from list cardtaglist ?
How to about replace
u.CardID == cardtaglist.Where(e=>e.FKCardTagID==u.CardID)
with
cardtaglist.Any(e=>e.FKCardTagID==u.CardID)
First of all, why you select all CardTagTable entity, if you use only FKCardTagID!? The best way - to select only required fields:
var cardtagIds = (from u in db.CardTagTables
where u.CardTagName == tag.tagName
select u.FKCardTagID).ToList();
About your error, you are traying to compare IQueriable with numeric value. You can use Contains method in this case:
cardlist = (from u in db.CardTables.Where(u => cardtagIds.Contains(u.CardID));
select new CardModel {
....
Edit
Also, this query can be optimized:
cardlist = (from u in db.CardTables.Where(u =>
db.CardTagTables
.Where(ct => ct.CardTagName == tag.tagName)
.Select(ct => ct.FKCardTagID)
.Contains(u.CardID))
select new CardModel {
....
Use:
var result=cardtaglist.Any(e=>e.FKCardTagID==u.CardID)

group by multiple columns (Dynamically) of datatable by linq Query

i want to group by multiple columns in a datatable by linq query.
i tried like this,
var _result = from row in tbl.AsEnumerable()
group row by new
{
id=row.Field<object>(_strMapColumn),
value=row.Field<object>(_strValueColumn),
} into g
select new
{
_strMapColumn = g.Key.id,
ToolTip = g.Sum(r => grp.Sum(r => r.Field<Double>(__strToolTip[1]))),
};
its works fine. my question is i have 10 column names in a strToolTip array i want to access 10 column names dynamically like for loop is it possible?
i want like this
select new
{_strMapColumn = g.Key.id,
for(int index = 1; index <= 10; index++)
{
ToolTip+index = g.Sum(r => getDoubleValue(r.Field<Double>(__strToolTip[1])))
}
};
and also want to add a DataType Dynamically please kindly provide the answer for solve this.
linq query is new for me.
You could group by a Dictionary and pass a custom comparer:
public class MyComparer : IEqualityComparer<Dictionary<string, object>> {
public bool Equals(Dictionary<string, object> a, Dictionary<string, object> b) {
if (a == b) { return true; }
if (a == null || b == null || a.Count != b.Count) { return false; }
return !a.Except(b).Any();
}
}
IEnumerable<string> columnsToGroupBy = ...
var rows = tbl.AsEnumerable();
var grouped = rows.GroupBy(r => columnsToGroupBy.ToDictionary(c => c, c => r[c]), new MyComparer());
var result = grouped.Select(g => {
// whatever logic you want with each grouping
var id = g.Key["id"];
var sum = g.Sum(r => r.Field<int>("someCol"));
});
Thanks to ChaseMedallion, I got dynamic grouping working.
Equals method was not enough, I had to add GetHashCode to MyComparer as well:
public int GetHashCode(Dictionary<string, object> a)
{
return a.ToString().ToLower().GetHashCode();
}

Comparing two large generic lists

I cannot find a specific example of this, so am posting the question. Any help appreciated.
I have two large generic lists, both with over 300K items.
I am looping through the first list to pull back information and generate a new item for a new list on the fly, but I need to search within the second list and return a value, based on THREE matching criteria, if found to add to the list, however as you can imagine, doing this 300k * 300k times is taking time.
Is there any way I can do this more efficiently?
My code:
var reportList = new List<StocksHeldInCustody>();
foreach (var correctDepotHolding in correctDepotHoldings)
{
var reportLine = new StocksHeldInCustody();
reportLine.ClientNo = correctDepotHolding.ClientNo;
reportLine.Value = correctDepotHolding.ValueOfStock;
reportLine.Depot = correctDepotHolding.Depot;
reportLine.SEDOL = correctDepotHolding.StockCode;
reportLine.Units = correctDepotHolding.QuantityHeld;
reportLine.Custodian = "Unknown";
reportLine.StockName = correctDepotHolding.StockR1.Trim() + " " + correctDepotHolding.StockR2.Trim();
//Get custodian info
foreach (var ccHolding in ccHoldList)
{
if (correctDepotHolding.ClientNo != ccHolding.ClientNo) continue;
if (correctDepotHolding.Depot != ccHolding.Depot) continue;
if (correctDepotHolding.StockCode != ccHolding.StockCode) continue;
if (correctDepotHolding.QuantityHeld != ccHolding.QuantityHeld) continue;
reportLine.Custodian = ccHolding.Custodian;
break;
}
reportList.Add(reportLine);
}
As Pranay says, a join is probably what you want:
var query = from correct in correctDepotHoldings
join ccHolding in ccHoldList
on new { correct.ClientNo, correct.Depot,
correct.StockCode, correct.QuantityHeld }
equals new { ccHolding.ClientNo, ccHolding.Depot,
ccHolding.StockCode, ccHolding.QuantityHeld }
// TODO: Fill in the properties here based on correct and ccHolding
select new StocksHeldInCustody { ... };
var reportList = query.ToList();
You could move the data from the lookup list into a dictionary, with the key being a unique hash of the 3 items you are searching on. Then you will have very quick lookups and save millions of iterations.
Check my full post : Linq Join on Mutiple columns using Anonymous type
Make use of Linq inner join that will do work for you.
var list = ( from x in entity
join y in entity2
on new { x.field1, x.field2 }
equals new { y.field1, y.field2 }
select new entity { fields to select}).ToList();
Join of linq on multiple field
EmployeeDataContext edb= new EmployeeDataContext();
var cust = from c in edb.Customers
join d in edb.Distributors on
new { CityID = c.CityId, StateID = c.StateId, CountryID = c.CountryId,
Id = c.DistributorId }
equals
new { CityID = d.CityId, StateID = d.StateId, CountryID = d.CountryId,
Id = d.DistributorId }
select c;
Use LINQ to join the lists and return it how you like.
eg
var list1 = GetMassiveList();
var list2 = GetMassiveList();
var list3 = from a in list1
join b in list2
on new { a.Prop1, a.Prop2 } equals
new { b.Prop1, b.Prop2 }
select new { a.Prop1, b.Prop2 };
To do your outter join, you can use DefaultIfEmpty()
This example is setting your RIGHT part of the join to a default object (often null) for the cases where a join wasn't made.
eg
from a in list1
join b in list2
on new { a.Prop1, a.Prop2 } equals
new { b.Prop1, b.Prop2 }
into outer
from b in outer.DefaultIfEmpty()
select new
Prop1 = a.Prop1,
Prop2 = b != null ? b.Prop2 : "Value for Prop2 if the b join is null"
}

Categories

Resources