Inner join two lists based on a child list - c#

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.

Related

How to return nested object using Generic method and Dapper [duplicate]

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.

LINQ query in C#?

I'm pretty new to C# and Linq programming world.
I Want to do something similar to this, but SubType is a FK to table Type and I can't do what I've done in this example:
public static List<DropdownModel> GetSubTypes(List<string> ListTypes)
{
List<DropdownModel> SubTypes = new List<DropdownModel>();
using (DocumentXtractorEntities DataBase = new DocumentXtractorEntities())
{
foreach (string TypeID in ListTypes)
{
int TypeIDINT = Int32.Parse(TypeID);
SubTypes.AddRange((from C in DataBase.SubType.Where(s => s.Active && s.TypeID == TypeIDINT)
select new DropdownModel()
{
ID = C.SubTypeID,
Description = C.Name,
Selected = false
}).ToList());
}
}
return SubTypes;
}
So, the code above kinda filters the subtype text box when I chosen one or more Types.
Now, I need to do the opposite, fill the Type List when subtypes are chosen.
I've tried something but I know that isn't possible the way I'm doing this.
My code for now is this:
public static List<DropdownModel> GetTypesBySubTypes(List<string> ListSubTypes)
{
List<DropdownModel> Types = new List<DropdownModel>();
using (DocumentXtractorEntities DataBase = new DocumentXtractorEntities())
{
foreach (string SubTypeID in ListSubTypes)
{
int SubTypeIDINT = Int32.Parse(SubTypeID);
Types.AddRange((from C in DataBase.Type.Where(s => s.Active && s.SubType.Contains(SubTypeIDINT))
select new DropdownModel()
{
ID = C.TypeID,
Description = C.Name,
}).ToList());
}
}
return Types;
}
[EDIT]
I've manage to do a sql query to do the job:
select T.TypeID from Type T join SubType ST on St.TypeID=T.TypeID
where ST.SubTypeID=3
But I don't know how to transform that to a linq query and do a Type.AddRange().
Can someone help me with that?
You can use the Intersect method to find the types that include any subtypes from the list of provided subtypes. This also eliminates the need to iterate using foreach and leaving that for Linq to handle.
List<int> subTypes = ListSubTypes.Select(s => int.Parse(s)).ToList();
DataBase.Type.Where(s => s.SubType.Select(st => st.SubTypesID).Intersect(subTypes).Any())
Here's an example based off of your code.
public static List<DropdownModel> GetTypesBySubTypes(List<string> ListSubTypes)
{
List<DropdownModel> Types = new List<DropdownModel>();
List<int> subTypes = ListSubTypes.Select(s => int.Parse(s)).ToList();
using (DocumentXtractorEntities DataBase = new DocumentXtractorEntities())
{
Types.AddRange((from C in DataBase.Type
.Where(s => s.Active
&& subTypes.Intersect(s.SubType.Select(st => st.SubTypesID)).Any())
select new DropdownModel()
{
ID = C.TypeID,
Description = C.Name,
}).ToList());
}
return Types;
}
HTH
You can write a similar join query to how you wrote your sql.
from C in DataBase.Type
join s in DataBase.SubType.Where(s => s.Active && s.SubTypeId == SubTypeIDINT) on C.TypeID equals s.TypeID
select new DropdownModel()
{
ID = C.TypeID,
Description = C.Name,
}

Contains matches on pairs of items

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

Performing a union in LINQ

I'm trying to get the union of these two queries but keep getting the following error:
'System.Linq.IQueryable<AnonymousType#1>' does not contain a definition for 'Union' and the best extension method overload 'System.Linq.ParallelEnumerable.Union<TSource>(System.Linq.ParallelQuery<TSource>, System.Collections.Generic.IEnumerable<TSource>)' has some invalid arguments
The linq queries look like this:
var g = from p in context.APP_PROD_COMP_tbl
where p.FAM_MFG == fam_mfg
group p by new
{
a_B_G = p.B_G,
a_MFG = p.MFG,
a_PRODUCT_FAM = p.PRODUCT_FAM,
};
var q = from p in context.APP_COMP_tbl
where p.FAM_MFG == fam_mfg
group p by new
{
a_B_G = p.a_B_G,
a_MFG = p.a_MFG,
a_PRODUCT_FAM = p.a_PRODUCT_FAM,
};
var data = q.Union(g);
I've tried using IEnumerable around the queries, but it still didn't work. Not really sure where I'm going wrong at this point, although admittedly LINQ isn't something I've had a ton of exposure to.
Update:
So I've gone in a slightly different direction from what I posted earlier. After doing more research, the group by statements were from old code and no longer needed for the intended purpose. I changed those to select new statements and had no further issue with the union.
I think that your problem here is type mismatch: g is of type IGrouping<AnonymousType#1, APP_PROD_COMP_tbl> and q is of type IGrouping<AnonymousType#1, APP_COMP_tbl>; this is why Union gives you the error.
I am not really sure what you are trying to Union (keys of the groups or groups of data themselves) but the solution would be:
If you want to union group keys, select the keys of your groups
var data = g.Select(x => x.Key).Union(q.Select(x => x.Key));
If you want to union the groups themselves then you need to project each element from both sequences into a common type, perform the grouping and then union the groups
var g = context.APP_PROD_COMP_tbl
.Where(p => p.FAM_MFG == fam_mfg)
.Select(ToCommonType)
.GroupBy(p => new
{
a_B_G = p.B_G,
a_MFG = p.MFG,
a_PRODUCT_FAM = p.PRODUCT_FAM,
});
var q = context.APP_COMP_tbl
.Where(p => p.FAM_MFG == fam_mfg)
.Select(ToCommonType)
.GroupBy(p => new
{
a_B_G = p.a_B_G,
a_MFG = p.a_MFG,
a_PRODUCT_FAM = p.a_PRODUCT_FAM,
});
var data = g.Union(q);
private CommonClass ToCommonType(APP_PROD_COMP_tbl item)
{
return new CommonClass
{
};
}
private CommonClass ToCommonType(APP_COMP_tbl item)
{
return new CommonClass
{
};
}
The problem is your Anonymouse types don't match:
var a = Enumerable.Range(1, 10).Select(x => new {a = x}).AsQueryable();
var b = Enumerable.Range(1, 10).Select(x => new {b = x}).AsQueryable();
var c = a.Union(b);
This won't work because typeof a is not same as typeof b
var a = Enumerable.Range(1, 10).Select(x => new {a = x}).AsQueryable();
var b = Enumerable.Range(1, 10).Select(x => new {a = x}).AsQueryable();
var c = a.Union(b);
But this will work, because Anonymouse types are the same.
You can try selecting same anonymouse types from your collection in q and g. Read more about Union for IQueryable
Union on IQueryAble<TSource>() accepts IQueryAble<TSource> as a parameter, so collection has to be the same type.

Create GroupBy Statements Dynamically

I am trying to dynamically re-structure some data to be shown in a treeview which will allows the user to select up to three of the following dimensions to group the data by:
Organisation
Company
Site
Division
Department
So for example, if the user were to select that they wanted to group by Company then Site then Division...the following code would perform the required groupings.
var entities = orgEntities
// Grouping Level 1
.GroupBy(o => new { o.CompanyID, o.CompanyName })
.Select(grp1 => new TreeViewItem
{
CompanyID = grp1.Key.CompanyID,
DisplayName = grp1.Key.CompanyName,
ItemTypeEnum = TreeViewItemType.Company,
SubItems = grp1
// Grouping Level 2
.GroupBy(o => new { o.SiteID, o.SiteName })
.Select(grp2 => new TreeViewItem
{
SiteID = grp2.Key.SiteID,
DisplayName = grp2.Key.SiteName,
ItemTypeEnum = TreeViewItemType.Site,
SubItems = grp2
// Grouping Level 3
.GroupBy(o => new { o.Division })
.Select(grp3 => new TreeViewItem
{
DisplayName = grp3.Key.Division,
ItemTypeEnum = TreeViewItemType.Division,
}).ToList()
}).ToList()
})
.ToList();
This would give a structre like this:
+ Company A
+ Site A
+ Division 1
+ Division 2
+ Site B
+ Division 1
+ Company B
+ Site C
+ Division 2
+ Company C
+ Site D
However, this only provides me with on of a large number of combinations.
How would I go about converting this into something that could create the equivalent expression dynamically based on the three dimensions that the user has chosen and so I don't have to create one of each of these expressions for each combination!!?
Thanks guys.
An intriguing problem. Choosing a single type for grouping keys and another type for results... makes it is very possible to get what you're asking for.
public struct EntityGroupKey
{
public int ID {get;set;}
public string Name {get;set;}
}
public class EntityGrouper
{
public Func<Entity, EntityGroupKey> KeySelector {get;set;}
public Func<EntityGroupKey, TreeViewItem> ResultSelector {get;set;}
public EntityGrouper NextGrouping {get;set;} //null indicates leaf level
public List<TreeViewItem> GetItems(IEnumerable<Entity> source)
{
var query =
from x in source
group x by KeySelector(x) into g
let subItems = NextGrouping == null ?
new List<TreeViewItem>() :
NextGrouping.GetItems(g)
select new { Item = ResultSelector(g.Key), SubItems = subItems };
List<TreeViewItem> result = new List<TreeViewItem>();
foreach(var queryResult in query)
{
// wire up the subitems
queryResult.Item.SubItems = queryResult.SubItems
result.Add(queryResult.Item);
}
return result;
}
}
Used in this way:
EntityGrouper companyGrouper = new EntityGrouper()
{
KeySelector = o => new EntityGroupKey() {ID = o.CompanyID, Name = o.CompanyName},
ResultSelector = key => new TreeViewItem
{
CompanyID = key.ID,
DisplayName = key.Name,
ItemTypeEnum = TreeViewItemType.Company
}
}
EntityGrouper divisionGrouper = new EntityGrouper()
{
KeySelector = o => new EntityGroupKey() {ID = 0, Name = o.Division},
ResultSelector = key => new TreeViewItem
{
DisplayName = key.Name,
ItemTypeEnum = TreeViewItemType.Division
}
}
companyGrouper.NextGrouping = divisionGrouper;
List<TreeViewItem> oneWay = companyGrouper.GetItems(source);
companyGrouper.NextGrouping = null;
divisionGrouper.NextGrouping = companyGrouper;
List<TreeViewItem> otherWay = divisionGrouper.GetItems(source);
Another option is to use DynamicLinq. If this is straight LINQ (not through some DB context such as LINQ2SQL), then this can be done by composing your grouping/selector strings:
var entities = orgEntities
.GroupBy("new(CompanyID, CompanyName)", "it", null) // DynamicLinq uses 'it' to reference the instance variable in lambdas.
.Select(grp1 => new TreeViewItem
{
...
.GroupBy("new(SiteID, o.SiteName)", "it", null)
// And so on...
You can probably abstract this into each of the criteria type. The only issue I see is the inner groupings might not be the easiest to compile together, but at least this can get you started in some direction. DynamicLinq allows you to build dynamic types, so it's certainly possible to abstract it even further. Ultimately, the biggest challenge is that based on what you're grouping by, the generated TreeViewItem contains different information. Good use case for dynamic LINQ, but the only problem I see is abstracting even further down the line (to the inner groupings).
Let us know what you come up with, definitely an interesting idea that I hadn't considered before.

Categories

Resources