I want to initialize var outside the foreach loop.
Here is my code:
public List<Course> GetCourse()
{
IList<Semester> semesters = Semester.Get();
foreach (Semester sm in semesters)
{
IList<CourseInstance> courseInstances = CourseInstance.Get(sm[0].SemesterId);
var courseInfos = from c in courseInstances
select new Course { Code = c.Course.Code, Name = c.Course.Name };
}
return courseInfos.ToList();
}
How do I initialize courseInfos out side the foreach loop? I try to initialize with null give me error!
var infers the type from the value you are initialising with, so initialising with null will never work. Everything else will.
I believe the Linq statement you want is
var courses = semesters
.SelectMany( s => CourseInstance.Get(s.SemesterId)
.Select( c => new Course ( Code = c.Course.Code, Name = c.Course.Name ) )
.ToList();
EDIT:
If you want to map SemesterName to a list of courses, I would recommend a dictionary.
semesters.ToDictionary(semester => semester.Name, semester =>
semesters.SelectMany(sid =>
CourseInstance.Get(sid.SemesterId))
.Select(c => new Course
{Code = c.Course.Code, Name = c.Course.Name}).ToList())
This will create a Dictionary<string, List<Course> This is nearly identical to the code below, except that it maps the semester.Name as the key. This would, of course, mean you have to have unique semester names, otherwise the dictionary can't be created.
You are reinitializing courseInfos every time you loop in the foreach, so you will only get a list of the last semesterId.
You can write a linq query that does this all in one line for you.
return semesters.SelectMany(sid => CourseInstance.Get(sid.SemesterId))
.Select(c => new Course { Code = c.Course.Code,
Name = c.Course.Name }).ToList()
To break it down,
.SelectMany(sid => CourseInstance.Get(sid.SemesterId))
does the same thing as the foreach. It will return an IEnumerable<CourseInstance>.
After that, you are calling
.Select(c => new Course { Code = c.Course.Code, Name = c.Course.Name })
on the result that we got in the last section; it returns an IEnumerable<Course> that you turn into a list.
SelectMany works similar to Select except it will take each IEnumerable<Course> and flatten it into one sequence instead of IEnumerable<IEnumerable<Course>>
The answer is:
IEnumerable<Course> courseInfos = null;
foreach (Semester sm in semesters)
{
IList<CourseInstance> courseInstances = CourseInstance.Get(semesters[0].SemesterId);
courseInfos = from c in courseInstances
select new Course { Code = c.Course.Code, Name = c.Course.Name };
}
return courseInfos.ToList();
However, you are discarding everything but the final iteration of the foreach. Is this what you meant to do?
why not just initialize the first instance of courseInfo with the first semester and then iterate over Semesters, is there a reason you need to initialize courseInfo before the foreeach?
may be this help. Collecting course info in each iteration.
public List<Course> GetCourse()
{
IList<Semester> semesters = Semester.Get();
List<Course> courseInfos = new List<Course>();
foreach (Semester sm in semesters)
{
IList<CourseInstance> courseInstances = CourseInstance.Get(sm.SemesterId);
IEnumerable<Course> result = from c in courseInstances
select new Course { Code = c.Course.Code , Name = c.Course.Name };
courseInfos.AddRange(result);
}
return courseInfos;
}
Related
I'm currently using Entity Framework for my db access but want to have a look at Dapper. I have classes like this:
public class Course{
public string Title{get;set;}
public IList<Location> Locations {get;set;}
...
}
public class Location{
public string Name {get;set;}
...
}
So one course can be taught at several locations. Entity Framework does the mapping for me so my Course object is populated with a list of locations. How would I go about this with Dapper, is it even possible or do I have to do it in several query steps?
Alternatively, you can use one query with a lookup:
var lookup = new Dictionary<int, Course>();
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (c, l) => {
Course course;
if (!lookup.TryGetValue(c.Id, out course))
lookup.Add(c.Id, course = c);
if (course.Locations == null)
course.Locations = new List<Location>();
course.Locations.Add(l); /* Add locations to course */
return course;
}).AsQueryable();
var resultList = lookup.Values;
See here https://www.tritac.com/blog/dappernet-by-example/
Dapper is not a full blown ORM it does not handle magic generation of queries and such.
For your particular example the following would probably work:
Grab the courses:
var courses = cnn.Query<Course>("select * from Courses where Category = 1 Order by CreationDate");
Grab the relevant mapping:
var mappings = cnn.Query<CourseLocation>(
"select * from CourseLocations where CourseId in #Ids",
new {Ids = courses.Select(c => c.Id).Distinct()});
Grab the relevant locations
var locations = cnn.Query<Location>(
"select * from Locations where Id in #Ids",
new {Ids = mappings.Select(m => m.LocationId).Distinct()}
);
Map it all up
Leaving this to the reader, you create a few maps and iterate through your courses populating with the locations.
Caveat the in trick will work if you have less than 2100 lookups (Sql Server), if you have more you probably want to amend the query to select * from CourseLocations where CourseId in (select Id from Courses ... ) if that is the case you may as well yank all the results in one go using QueryMultiple
No need for lookup Dictionary
var coursesWithLocations =
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (course, location) => {
course.Locations = course.Locations ?? new List<Location>();
course.Locations.Add(location);
return course;
}).AsQueryable();
I know I'm really late to this, but there is another option. You can use QueryMultiple here. Something like this:
var results = cnn.QueryMultiple(#"
SELECT *
FROM Courses
WHERE Category = 1
ORDER BY CreationDate
;
SELECT A.*
,B.CourseId
FROM Locations A
INNER JOIN CourseLocations B
ON A.LocationId = B.LocationId
INNER JOIN Course C
ON B.CourseId = B.CourseId
AND C.Category = 1
");
var courses = results.Read<Course>();
var locations = results.Read<Location>(); //(Location will have that extra CourseId on it for the next part)
foreach (var course in courses) {
course.Locations = locations.Where(a => a.CourseId == course.CourseId).ToList();
}
Sorry to be late to the party (like always). For me, it's easier to use a Dictionary, like Jeroen K did, in terms of performance and readability. Also, to avoid header multiplication across locations, I use Distinct() to remove potential dups:
string query = #"SELECT c.*, l.*
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id";
using (SqlConnection conn = DB.getConnection())
{
conn.Open();
var courseDictionary = new Dictionary<Guid, Course>();
var list = conn.Query<Course, Location, Course>(
query,
(course, location) =>
{
if (!courseDictionary.TryGetValue(course.Id, out Course courseEntry))
{
courseEntry = course;
courseEntry.Locations = courseEntry.Locations ?? new List<Location>();
courseDictionary.Add(courseEntry.Id, courseEntry);
}
courseEntry.Locations.Add(location);
return courseEntry;
},
splitOn: "Id")
.Distinct()
.ToList();
return list;
}
Something is missing. If you do not specify each field from Locations in the SQL query, the object Location cannot be filled. Take a look:
var lookup = new Dictionary<int, Course>()
conn.Query<Course, Location, Course>(#"
SELECT c.*, l.Name, l.otherField, l.secondField
FROM Course c
INNER JOIN Location l ON c.LocationId = l.Id
", (c, l) => {
Course course;
if (!lookup.TryGetValue(c.Id, out course)) {
lookup.Add(c.Id, course = c);
}
if (course.Locations == null)
course.Locations = new List<Location>();
course.Locations.Add(a);
return course;
},
).AsQueryable();
var resultList = lookup.Values;
Using l.* in the query, I had the list of locations but without data.
Not sure if anybody needs it, but I have dynamic version of it without Model for quick & flexible coding.
var lookup = new Dictionary<int, dynamic>();
conn.Query<dynamic, dynamic, dynamic>(#"
SELECT A.*, B.*
FROM Client A
INNER JOIN Instance B ON A.ClientID = B.ClientID
", (A, B) => {
// If dict has no key, allocate new obj
// with another level of array
if (!lookup.ContainsKey(A.ClientID)) {
lookup[A.ClientID] = new {
ClientID = A.ClientID,
ClientName = A.Name,
Instances = new List<dynamic>()
};
}
// Add each instance
lookup[A.ClientID].Instances.Add(new {
InstanceName = B.Name,
BaseURL = B.BaseURL,
WebAppPath = B.WebAppPath
});
return lookup[A.ClientID];
}, splitOn: "ClientID,InstanceID").AsQueryable();
var resultList = lookup.Values;
return resultList;
There is another approach using the JSON result. Even though the accepted answer and others are well explained, I just thought about an another approach to get the result.
Create a stored procedure or a select qry to return the result in json format. then Deserialize the the result object to required class format. please go through the sample code.
using (var db = connection.OpenConnection())
{
var results = await db.QueryAsync("your_sp_name",..);
var result = results.FirstOrDefault();
string Json = result?.your_result_json_row;
if (!string.IsNullOrEmpty(Json))
{
List<Course> Courses= JsonConvert.DeserializeObject<List<Course>>(Json);
}
//map to your custom class and dto then return the result
}
This is an another thought process. Please review the same.
I have a hierarical object (Result->userDataProfilePermissions->dataPermissions->Values->dataObjectValue)
I need to get distinct of dataObjectValue which is a string.
I've written the following loop, and it should work fine.
var businesses = new List<BusinessRbac>();
foreach (var udpp in result.userDataProfilePermissions)
{
foreach (var dp in udpp.dataPermissions)
{
foreach (var v in dp.values)
{
if (!businesses.Any(x => x.BusinessName == v.dataObjectValue))
businesses.Add(new BusinessRbac() { BusinessName = v.dataObjectValue, Regions = new List<RegionRbac>() });
}
}
}
When I convert it to LINQ, I get the following code
var businesses = (from udpp in result.userDataProfilePermissions
from dp in udpp.dataPermissions
from v in dp.values
where !businesses.Any(x => x.BusinessName == v.dataObjectValue)
select new BusinessRbac() { BusinessName = v.dataObjectValue, Regions = new List<RegionRbac>() }).ToList();
which is giving compilation error:
CS0841: Cannot user local variable 'businesses' before it is declared.
Your nested loop solution makes new insertions into businesses available for the uniqueness check during the following iteration, while your LINQ solution can't check businesses as it goes, because the result of the query has not been assigned.
You can fix this by performing uniqueness check in some other way - for example, by grouping on dataObjectValue, and selecting the first member of the group:
var businesses = result.userDataProfilePermissions
.SelectMany(udpp => udpp.dataPermissions.SelectMany(dp => dp.values))
.GroupBy(v => v.dataObjectValue)
.Select(g => g.First())
.Select(v => new BusinessRbac() {
BusinessName = v.dataObjectValue,
Regions = new List<RegionRbac>()
}).ToList();
I have two lists of different types of custom objects, and I'm trying to perform an inner join where the join criteria is contained within a child list of the objects.
Here's an example of my objects:
public class Container
{
public List<Reference> Refs;
public Container(List<Reference> refs )
{
Refs = refs;
}
}
public class Reference
{
public int Id;
public Reference(int id )
{
Id = id;
}
}
And here's an example the data I'm working with:
List<Container> containers = new List<Container>()
{
new Container(new List<Reference>()
{
new Reference(1),
new Reference(2),
new Reference(3)
}),
new Container(new List<Reference>()
{
new Reference(4),
new Reference(5),
new Reference(6)
})
};
List<Reference> references = new List<Reference>()
{
new Reference(4),
new Reference(5),
new Reference(6)
};
I'm trying to select all the Containers in List<Container> which have a matching Reference in the List<Reference> based on Reference.Id. With this data, I expect only the second item in the List<Container> to be selected.
If it were valid syntax, I'd be looking to do something along the lines of:
var query = from c in containers
join r in references on c.Refs.Contains( r.Id )
select c;
How can this be done? Thanks
Sorry for the poor title. I'm struggling to put this scenario into a short group of words - please suggest an edit if you can think of something more suitable. Thanks
an inner join is not necessary here, you're better off without it:
containers.Where(c => c.Refs.Any(x => references.Any(e => x.Id == e.Id)));
or if you want the entire set of Id's to be equal then use SequenceEqual:
var sequence = references.Select(e => e.Id);
var result = containers.Where(c => c.Refs.Select(s => s.Id).SequenceEqual(sequence));
containers.Where(c => c.Refs.Select(r => r.Id).Intersect(references.Select(r => r.Id)).Any());
I would use:
var query = from c in containers where c.Refs.SequenceEqual(references)
select c;
No join is necessary.
I have a mapping table in the following form:
Id ReferenceId ReferenceType LinkId
To retrieve a set of combinations, I could run each query separately:
var pairs = new List<Pair>
{
Pair.Create(1000, "Car"),
Pair.Create(2000, "Truck"),
};
var maps = new List<Mapping>();
foreach (var pair in pairs)
{
maps.AddRange(context.Mappings.Where(x => x.ReferenceId = pair.Id && x.ReferenceType == pair.Type).ToList());
}
However, I want to combine these into a single statement to reduce my hits on the db. Is there some form of Contains statement that can work with pairs of objects? Or is it possible to append an OR clause onto an IQueryable within a loop? Any other solutions?
Not sure if it works for your LINQ provider but you could try to join with an anonymous type:
var mapQuery = from p in pairs
join m in context.Mappings
on new { p.Id, p.Type } equals new { m.ReferenceId, m.ReferenceType}
select m;
List<Mapping> maps = mapQuery.ToList();
You could union your queries together.
Something like this:
var pairs = new List<Pair>
{
Pair.Create(1000, "Car"),
Pair.Create(2000, "Truck"),
};
List<Mapping> result =
pairs
.Select(pair =>
context.Mappings.Where(
x => x.ReferenceId == pair.Id
&& x.ReferenceType == pair.Type))
.Aggregate(Queryable.Union)
.ToList();
I have list A which is obj A and list B which is obj B. Both list share one property and I want to look up all the obj B of list B has in A and pull them out.
So, ex.
List A is a bunch of people
List B is a bunch of names
Both list have a personId
Now I want to get all the people with the names that are in List B. I was thinking something like a:
class names
{
public int id {get;set;}
public string name {get;set;}
}
class people
{
public int id {get;set;}
public string name {get;set;}
}
var newList = new List<person>();
foreach(var n in names)
{
var person = people.firstordefault(p => p.name == n);
if(person!=null)
{
newList.Add(person);
}
}
}
I was wondering is there is a more efficent way with LINQ I can do this because it wont be a list everytime it might be the database im calling it from and i dont want to call the database a thousands for no reason.
This is probably a bad example if i think about it.
This codes :
var newList = new List<person>();
foreach(var n in names)
{
var person = people.firstordefault(p => p.name == n);
if(person!=null)
{
newList.Add(person);
}
}
will produce the same result as :
var newList = new List<person>();
newList = people.Where(p => names.Contains(p.name)).ToList();
responding your update, if names is a list of names object instead of string, you can do as follow :
newList = people.Where(p => names.Select(o => o.name).Contains(p.name)).ToList();
With LINQ, You can do this:
var intersection = ListA.Intersect(ListB);
However, this is the set intersection, meaning if ListA and ListB don't have unique values in it, you won't get any copies. In other words if you have the following:
var ListA = new [] { 0, 0, 1, 2, 3 };
var ListB = new [] { 0, 0, 0, 2 };
Then ListA.Intersect(ListB) produces:
{ 0, 2 }
If you're expecting:
{ 0, 0, 2 }
Then you're going to have to maintain a count of the items yourself and yield/decrement as you scan the two lists.
Since you're dealing with two different classes, what you're really looking for is a join.
List<Person> people = new List<Person>{new Person{Name = "Mark"},
new Person{Name = "Alice"},
new Person{Name = "Jane"}};
List<string> names = new List<string>{"Mark"};
var query = from p in people
join n in names on p.Name equals n
select p; // will output Person Mark
Note: This has time complexity of O(p+n) (where p = number of people and n = number of names), because join is implemented as a hash join. Your nested loop above or a Where/Contains LINQ query time complexity O(p*n), since it's iterating n for every p. This may or may not be an issue depending on the sizes of your collections.