The following data is returned from an SQL View:
Name CandidateID Filled
Tom Jones 1003436 2014-05-09 07:13:53.087
Tom Jones 1003436 2014-05-09 07:13:18.957
Ed Harris 1421522 2014-05-09 08:17:20.234
I only want the one Tom Jones record with the latest Filled time. How can I achive this in C#/LINQ while getting or after getting data from server?
Maybe something like this:
var q = from n in table
group n by new {n.CandidateID,n.Name} into g
select new
{
CandidateID = g.Key.CandidateID,
Name = g.Key.Name,
Filled = g.Max(t=>t.Filled)
};
Test class
class Foo
{
public string Name { get; set; }
public int CandidateID { get; set; }
public DateTime Filled { get; set; }
}
Test case
var ls=new List<Foo>
{
new Foo(){Name="Tom Jones",CandidateID=1003436,
Filled=DateTime.Parse("2014-05-09 07:13:53.087")},
new Foo(){Name="Tom Jones",CandidateID=1003436,
Filled=DateTime.Parse("2014-05-09 07:13:18.957")},
new Foo(){Name="Ed Harris",CandidateID=1421522,
Filled=DateTime.Parse("2014-05-09 08:17:20.234")}
};
var q =
(from n in ls
group n by new {n.CandidateID,n.Name} into g
select new
{
CandidateID = g.Key.CandidateID,
Name = g.Key.Name,
Filled = g.Max(t=>t.Filled)
});
Output
CandidateID Name Filled
1003436 Tom Jones 09/05/2014 7:13:53 AM
1421522 Ed Harris 09/05/2014 8:17:20 AM
var q = from n in table
group n by n.CandidateID into g
select g.OrderByDescending(t=>t.Filled).FirstOrDefault();
you need Group by as shown below
var distinctItems = ls.GroupBy(x => x.CandidateID).Select(y => y.First());
Related
I'm struggling with linq (left join - group - count). Please help me.
Below is my code and it gives me this result.
Geography 2
Economy 1
Biology 1
I'm expecting this...
Geography 2
Economy 1
Biology 0
How can I fix it?
class Department
{
public int DNO { get; set; }
public string DeptName { get; set; }
}
class Student
{
public string Name { get; set; }
public int DNO { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Department> departments = new List<Department>
{
new Department {DNO=1, DeptName="Geography"},
new Department {DNO=2, DeptName="Economy"},
new Department {DNO=3, DeptName="Biology"}
};
List<Student> students = new List<Student>
{
new Student {Name="Peter", DNO=2},
new Student {Name="Paul", DNO=1},
new Student {Name="Mary", DNO=1},
};
var query = from dp in departments
join st in students on dp.DNO equals st.DNO into gst
from st2 in gst.DefaultIfEmpty()
group st2 by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count()
};
foreach (var st in query)
{
Console.WriteLine("{0} \t{1}", st.DName, st.Count);
}
}
}
var query =
from department in departments
join student in students on department.DNO equals student.DNO into gst
select new
{
DepartmentName = department.DeptName,
Count = gst.Count()
};
I don't think any grouping is required for answering your question.
You only want to know 2 things:
- name of department
- number of students per department
By using the 'join' and 'into' you're putting the results of the join in the temp identifier gst. You only have to count the number of results in the gst.
var query = from dp in departments
from st in students.Where(stud => stud.DNO == dp.DNO).DefaultIfEmpty()
group st by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count(x => x!=null)
};
You want to group the students by the department name but you want the count to filter out null students. I did change the join syntax slightly although that really does not matter to much.
Here is a working fiddle
Well, see what #Danny said in his answer, it's the best and cleanest fix for this case. By the way, you could also rewrite it to the lambda syntax:
var query = departments.GroupJoin(students,
dp => dp.DNO, st => st.DNO,
(dept,studs) => new
{
DName = dept.DNO,
Count = studs.Count()
});
I find this syntax much more predictable in results, and often, shorter.
BTW: .GroupJoin is effectively a "left join", and .Join is "inner join". Be careful to not mistake one for another.
And my answer is similar to #Igor
var query = from dp in departments
join st in students on dp.DNO equals st.DNO into gst
from st2 in gst.DefaultIfEmpty()
group st2 by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count(std => std != null)
};
g.Count(std => std != null) is only one change you should take.
I am making a group by linq statement where i convert a single list of data into an list with a nested list. Here is my code so far:
[TestMethod]
public void LinqTestNestedSelect2()
{
// initialization
List<combi> listToLinq = new List<combi>() {
new combi{ id = 1, desc = "a", name = "A", count = 1 },
new combi{ id = 1, desc = "b", name = "A", count = 2 },
new combi{ id = 2, desc = "c", name = "B", count = 3 },
new combi{id = 2, desc = "d", name = "B", count = 4 },
};
// linq group by
var result = (from row in listToLinq
group new { des = row.desc, count = row.count } by new { name = row.name, id = row.id } into obj
select new A { name = obj.Key.name, id = obj.Key.id, descriptions = (from r in obj select new B() { des = r.des, count = r.count }).ToList() }).ToList();
// validation of the results
Assert.AreEqual(2, result.Count);
Assert.AreEqual(2, result[0].descriptions.Count);
Assert.AreEqual(2, result[0].descriptions.Count);
Assert.AreEqual(2, result[1].descriptions.Count);
Assert.AreEqual(2, result[1].descriptions.Count);
}
public class A
{
public int id;
public string name;
public List<B> descriptions;
}
public class B
{
public int count;
public string des;
}
public class combi
{
public int id;
public string name;
public int count;
public string desc;
}
This is fine if the objects are small like the example. However I will implement this for objects with a lot more properties. How can I efficiently write this statement so I don't have to write field names twice in my linq statement?
I would like to return the objects in the statement and I want something like:
// not working wishfull thinking code
var result = (from row in listToLinq
group new { des = row.desc, count = row.count } by new { name = row.name, id = row.id } into obj
select new (A){ this = obj.key , descriptions = obj.ToList<B>()}).ToList();
Background: I am re writing a web api that retrieves objects with nested objects in a single database call for the sake of db performance. It's basically a big query with a join that retrieves a crap load of data which I need to sort out into objects.
probably important: the ID is unique.
EDIT:
based on the answers so far I have made a solution which sort of works for me, but is still a bit ugly, and I would want it to be better looking.
{
// start part
return (from row in reader.AsEnumerable()
group row by row.id into grouping
select CreateA(grouping)).ToList();
}
private static A CreateA(IGrouping<object, listToLinq> grouping)
{
A retVal = StaticCreateAFunction(grouping.First());
retVal.descriptions = grouping.Select(item => StaticCreateBFunction(item)).ToList();
return ret;
}
I hope the StaticCreateAFunction is obvious enough for what it does. In this scenario I only have to write out each property once, which is what I really wanted. But I hope there is a more clever or linq-ish way to write this.
var result = (from row in listToLinq
group new B { des = row.desc, count = row.count } by new A { name = row.name, id = row.id } into obj
select new A { name = obj.Key.name, id = obj.Key.id, descriptions = obj.ToList() }).ToList();
You can add to each of the A and B classes a constructor that receives a combi and then it takes from it only what it needs. For example for a:
public class A
{
public A(combi c)
{
id = c.id;
name = c.name;
}
}
public class B
{
public B(combi c)
{
count = c.count;
des = c.desc;
}
}
Then your query can look like:
var result = (from row in listToLinq
group row by new { row.id, row.name } into grouping
select new A(grouping.First())
{
descriptions = grouping.Select(item => new B(item)).ToList()
}).ToList();
If you don't like the grouping.First() you can then override Equals and GetHashCode and then in the group by do by a new a with the relevant fields (which will be those in the Equals) and then add a copy constructor from a
Another way, in which you decouple the A/B classes from the combi is to extract the convert logic to a collection of static methods.
In this example class IcdPatient represents a many-to-many relationship between a Patient table (not shown in this example) and a lookup table Icd.
public class IcdPatient
{
public int PatientId { get; set; }
public int ConditionCode { get; set; }
public static List<IcdPatient> GetIcdPatientList()
{
return new List<IcdPatient>()
{
new IcdPatient { PatientId = 100, ConditionCode = 111 },
new IcdPatient { PatientId = 100, ConditionCode = 222 },
new IcdPatient { PatientId = 200, ConditionCode = 111 },
new IcdPatient { PatientId = 200, ConditionCode = 222 },
new IcdPatient { PatientId = 3, ConditionCode = 222 },
};
}
}
public class Icd
{
public int ConditionCode { get; set; }
public string ConditionName { get; set; }
public static List<Icd> GetIcdList()
{
return new List<Icd>()
{
new Icd() { ConditionCode =111, ConditionName ="Condition 1"},
new Icd() { ConditionCode =222, ConditionName ="Condition 2"},
};
}
}
I would like for the user to be able to enter as many conditions as they want, and get a LINQ object back that tells them how many PatientIds satisfy that query. I've come up with:
List<string> stringFilteredList = new List<string> { "Condition 1", "Condition 2" };
List<int> filteringList = new List<int> { 111,222 };
var manyToMany = IcdPatient.GetIcdPatientList();
var icdList = Icd.GetIcdList();
/*Working method without joining on the lookup table*/
var grouped = from m in manyToMany
group m by m.PatientId into g
where g.Count() == filteringList.Distinct().Count()
select new
{
PatientId = g.Key,
Count = g.Count()
};
/*End*/
foreach (var item in grouped)
{
Console.WriteLine(item.PatientId);
}
Let's say that IcdPatient has a composite primary key on both fields, so we know that each row is unique. If we find the distinct number of entries in filteringList and do a count on the number of times a PatientId shows up, that means we've found all the people who have all conditions. Because the codes can be esoteric, I would like to do something like
let the user table in the ConditionName in type Icd and perform the same operation. I've not used LINQ this way a lot and I've gathered:
List<int> filteringList = new List<int> { 111,222 };
List<string> stringFilteredList= new List<string>{"Condition 1","Condition 2" };
filteringList.Distinct();
var manyToMany = IcdPatient.GetIcdPatientList();
var icdList = Icd.GetIcdList();
/*Working method without joining on the lookup table*/
var grouped = from m in manyToMany
join i in icdList on
m.ConditionCode equals i.ConditionCode
//group m by m.PatientId into g
group new {m,i} by new { m.ConditionCode }into g
where g.Count() == filteringList.Distinct().Count()
select new
{
Condition = g.Key.ConditionCode
};
/*End*/
but can't get anything to work. This is essentially a join on top of my first query, but I'm not getting what I need to group on.
You don't need to group anything in this case, just use a join and a contains:
List<string> stringFilteredList= new List<string>{"Condition 1","Condition 2" };
var patients =
from icd in Icd.GetIcdList()
join patient in IcdPatient.GetIcdPatientList() on icd.ConditionCode equals patient.ConditionCode
where stringFilteredList.Contains(icd.ConditionName)
select patient.PatientId;
Let's say that IcdPatient has a composite primary key on both fields, so we know that each row is unique. If we find the distinct number of entries in filteringList and do a count on the number of times a PatientId shows up, that means we've found all the people who have all conditions. Because the codes can be esoteric, I would like to do something like let the user table in the ConditionName in type Icd and perform the same operation.
I believe you're asking:
Given a list of ConditionCodes, return a list of PatientIds where every patient has every condition in the list.
In that case, the easiest thing to do is group your IcdPatients table by Id, so that we can tell every condition that a patient has by looking once. Then we check that every ConditionCode we're looking for is in the group. In code, that looks like:
var result = IcdPatient.GetIcdPatientList()
// group up all the objects with the same PatientId
.GroupBy(patient => patient.PatientId)
// gather the information we care about into a single object of type {int, List<int>}
.Select(patients => new {Id = patients.Key,
Conditions = patients.Select(p => p.ConditionCode)})
// get rid of the patients without every condition
.Where(conditionsByPatient =>
conditionsByPatient.Conditions.All(condition => filteringList.Contains(condition)))
.Select(conditionsByPatient => conditionsByPatient.Id);
In query format, that looks like:
var groupedInfo = from patient in IcdPatient.GetIcdPatientList()
group patient by patient.PatientId
into patients
select new { Id = patients.Key,
Conditions = patients.Select(patient => patient.ConditionCode) };
var resultAlt = from g in groupedInfo
where g.Conditions.All(condition => filteringList.Contains(condition))
select g.Id;
Edit: If you'd also like to let your user specify the ConditionName rather than the ConditionId then simply convert from one to the other, storing the result in filteringList, like so:
var conditionNames = // some list of names from the user
var filteringList = Icd.GetIcdList().Where(icd => conditionNames.Contains(icd.ConditionName))
.Select(icd => icd.ConditionCode);
Having some issues with my code, its a many to many between 3 tables, im using LINQ to entities, first I group the quote ids then use the id to get the info from each table and try to put it into a viewmodel, im not sure my inhertance is right
There should be one quote with multiple prices from multiple suppliers for multiple items, the item names need to be in the column and the prices below.
Anyway the issue below is
Error 3 The best overloaded method match for
System.Collections.Generic.List<ITAPP.Models.tblQuotes>.Add(ITAPP.Models.tblQuotes)
has some invalid arguments PurchasingController.cs 48 17 ITAPP`
Error 4 Argument 1: cannot convert from
System.Collections.Generic.List<ITAPP.Models.tblQuotes>' to 'ITAPP.Models.tblQuotes'
PurchasingController.cs 48 37 ITAPP`
and here is the code
var tblQuoting =
from d in db.Quotes_Items_Suppliers
group d by new
{
d.QuoteID
} into g
select new {
QuoteID = g.Key
};
var model = new List<QuoteViewModel>();
foreach (var Quotes in tblQuoting)
{
var ModelItem = new QuoteViewModel();
ModelItem.Quote = new List<tblQuotes>();
ModelItem.Suppliers = new List<tblSuppliers>();
ModelItem.Items = new List<tblItems>();
ModelItem.Prices = new List<tblQuotes_Items_Suppliers>();
//Add the quote info to model
int QuoteID = Convert.ToInt32(Quotes.QuoteID);
var Quote = (from d in db.Quotes
where d.ID == QuoteID
select d).ToList();
ModelItem.Quote.Add(Quote);
//add all the suppliers to the quote model
var Suppliers = (from d in db.Quotes_Items_Suppliers.Include(t => t.tblSuppliers)
where d.QuoteID == QuoteID
select d).ToList();
ModelItem.Suppliers.Add(Suppliers);
//add the items to the quote model
var Items = (from d in db.Quotes_Items_Suppliers.Include(t => t.tblItems)
where d.QuoteID == QuoteID
select d).ToList();
ModelItem.Items.Add(Items);
model.Add(ModelItem);
}
return View("Index", model);
this is my model (if its right?)
public class QuoteViewModel
{
public List<tblQuotes> Quote { get; set; }
public List<tblSuppliers> Suppliers { get; set; }
public List<tblItems> Items { get; set; }
public List<tblQuotes_Items_Suppliers> Prices { get; set; }
}
index
Use AddRange to add sequence of items to list:
ModelItem.Quote.AddRange(Quote);
ModelItem.Suppliers.AddRange(Suppliers);
ModelItem.Items.AddRange(Items);
Or simply assign lists without initialization (thus you will avoid creating intermediate list and copying items from one list to another):
ModelItem.Quote = Quote;
ModelItem.Suppliers = Supplies;
ModelItem.Items = Items;
Or even use object initalizer:
var ModelItem = new QuoteViewModel {
Quote = db.Quotes.Where(q => q.ID == QuoteID).ToList(),
Suppliers = db.Quotes_Items_Suppliers.Include(t => t.tblSuppliers)
.Where(s => s.QuoteID == QuoteID).ToList(),
Items = db.Quotes_Items_Suppliers.Include(t => t.tblItems)
.Where(i => i.QuoteID == QuoteID).ToList()
};
I have a problem loading the correct data to a DTO using EF and linq.
From my DB I receive following example data:
1, 1, 1
1, 1, 2
1, 1, 3
2, 1, 4
2, 1, 5
etc.
I want to load these data in a DTO which should look like this:
int, int, ICollection<int>
so for the example data:
new MyDto(1, 1, new List<int> { 1, 2, 3 });
new MyDto(2, 1, new List<int> { 4, 5 });
This is my linq query
var result = (from adresses in context.Adress
join person in context.Person on adresses.PersonId equals person.Id
select new MyObj { Id1 = adresses.Id1, Id2 = adresses.Id2, PersonId = person.Id })
But it is wrong, since it doesn't group by Id1 and Id2 and doesn't put the personIds in the list...
Could you please tell me how I can achieve this?
Pivot data using Linq is a better way. You can take look at this link:
Is it possible to Pivot data using LINQ
To answer your question, below is an example:
var result = (from adresses in context.Adress
join person in context.Person on adresses.PersonId equals person.Id
group address by address.Id1 into gResult
select new{
Id1 = gResult.Key,
Id2 = gResult.Select(r => r.Id2).FirstOrDefault (),
Id3 = gResult.Select (r => r.Id3)
});
In your Address class, do you have a property for a Person instance so you're able to set up a relationship between the two classes? If so, the following query may get you the result set that you're looking for:
public class Address
{
public int Id1 { get; set; }
public int Id2 { get; set; }
public virtual Person Person { get; set; }
}
public void Foo()
{
IEnumerable<MyObj> = context.Address.Select(x => new {
Id1 = x.Id1,
Id2 = x.Id2,
PersonId = x.Person.Id
});
}
Thanks for the good answers of you guys, I could finally work it out :-)
var result = from tuple in (from address in context.Adresses
join person in context.Persons on address.PersonId equals person.Id
select new { person.Id, address.Id1, address.Id2})
group tuple by new { tuple.Id1, tuple.Id2 } into myGrouping
select
new MyObj
{
Id1 = myGrouping.Key.Id1,
Id2 = myGrouping.Key.Id2,
PersonIds = myGrouping.Select(x => x.PersonId).Distinct()
};