Join table with object list - c#

I have a table, lets say tblCar with all the related columns like Id, Make, Model, Color etc.
I have a search model for car containing two params Id and Model.
public class CarSearch
{
public int Id { get; set; }
public string Model { get; set; }
}
var carSearchObjets = new List<CarSearch>();
With list of primitive data (like Id list), to get cars with those Ids I could have done:
var idList = new List<int> { 1, 2, 3 };
var carsFromQuery = context.Cars.Where(x => idList.Contains(x.Id);
But if I have to fetch all the cars with Id and model from the list, how do I do it? Simple join cannot be done between in memory objects and tables.
I need something like,
from m in context.Cars
join n in carSearchObjets
on new { Id = n.Id, Model = n.Model } equals new { Id = m.Id, Model = m.Model }
select m;
This obviously won't work.
Please ignore any typos.And if you need more info or the question is not clear, let me know.

One (ugly-but-working) way to manage that is to use concatenation with a "never used" concat char.
I mean a char that should never appear in the datas. This is always dangerous, as... never is never sure, but you've got the idea.
For example, we'll say that our "never used" concat char will be ~
This is not good for perf, but at least working :
var carSearchObjectsConcatenated = carSearchObjets.Select(m => new { m.Id + "~" + m.Model});
then you can use Contains again (concatenating on the db too) : you'll need to use SqlFunctions.StringConvert if you wanna concatenate string and numbers on the db side.
var result = context.Cars.Where(m =>
carSearchObjectsConcatenated.Contains(SqlFunctions.StringConvert((double)m.Id) + "~" + m.Model);
EDIT
Another solution would be to use PredicateBuilder, as mentionned by Sorax, or to build your own Filter method if you don't want a third party lib (but PredicateBuilder is really fine).
Something like that in a static class :
public static IQueryable<Car> FilterCars(this IQueryable<Car> cars, IEnumerable<SearchCar> searchCars)
{
var parameter = Expression.Parameter(typeof (Car), "m");
var idExpression = Expression.Property(parameter, "Id");
var modelExpression = Expression.Property(parameter, "Model");
Expression body = null;
foreach (var search in searchCars)
{
var idConstant = Expression.Constant(search.Id);
var modelConstant = Expression.Constant(search.Model);
Expression innerExpression = Expression.AndAlso(Expression.Equal(idExpression, idConstant), Expression.Equal(modelExpression, modelConstant));
body = body == null
? innerExpression
: Expression.OrElse(body, innerExpression);
}
var lambda = Expression.Lambda<Func<Car, bool>>(body, new[] {parameter});
return cars.Where(lambda);
}
usage
var result = context.Cars.FilterCars(carSearchObjets);
this will generate an sql looking like
select ...
from Car
where
(Id = 1 And Model = "ax") or
(Id = 2 And Model = "az") or
(Id = 3 And Model = "ft")

'PredicateBuilder' might be helpful.
var predicate = PredicateBuilder.False<Car>();
carSearchObjects
.ForEach(a => predicate = predicate.Or(p => p.Id == a.Id && p.Model == a.Model));
var carsFromQuery = context.Cars.AsExpandable().Where(predicate);
Note the text in the link regarding EF:
If you're using Entity Framework, you'll need the complete LINQKit -
for the AsExpandable functionality. You can either reference
LINQKit.dll or copy LINQKit's source code into your application.

Old school solution..
//in case you have a
List<CarSearch> search_list; //already filled
List<Cars> cars_found = new List<Cars>();
foreach(CarSearch carSearch in search_list)
{
List<Cars> carsFromQuery = context.Cars.Where(x => x.Id == carSearch.Id && x.Model == carSearch.Model).ToList();
cars_found.AddRange(carsFromQuery);
}
Abd don't worry about the for loops.

I landed up passing in an xml list as a parameter to the sql query and joined to that:
var xml = new XElement("Cars", yourlist.Select(i => new XElement("Car", new XElement("Id", i.Id), new XElement("Model", i.Model))));
var results = Cars
.FromSql("SELECT cars.*"
+ "FROM #xml.nodes('/Cars/Car') Nodes(Node)"
+ "JOIN Cars cars on cars.Id = Nodes.Node.value('Id[1]', 'int') and cars.Model = Nodes.Node.value('Model[1]', 'varchar(100)')",
new SqlParameter("#xml", new SqlXml(xml.CreateReader())));
For entity-framework-core users I created a nuget package extension:
EntityFrameworkCore.SqlServer.Extensions.Contains

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,
}

Linq: let Count result into a specified column

I've got a table Installation which can contains one or many Equipements.
And for functionnal reasons, I've overwritten my table Installation and added a field NbrEquipements.
I want to fill this field with Linq, but I'm stuck...
Due to special reasons, there is no relation between these to tables. So, no Installation.Equipements member into my class. Therefore, no Installation.Equipements.Count...
I'm trying some stuff. Here is my code:
var query = RepoInstallation.AsQueryable();
// Some filter
query = query.Where(i => i.City.RegionId == pRegionId));
int?[] etatIds = { 2, 3 };
query = (from i in query
select new Installation
{
NbrEquipements= (from e in RepoEquipement.AsQueryable()
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e.SasId
).Count()
});
But with this try, I got this error:
The entity or complex type 'myModel.Installation' cannot be constructed in a LINQ to Entities query
I've tried some other stuff but I'm always turning around...
Another thing that can be useful for me: It would be great to fill a field called Equipements which is a List<Equipement>.
After that, I would be able to Count this list...
Is it possible ?
Tell me if I'm not clear.
Thanks in advance.
Here is the final code:
//In the class:
[Dependency]
public MyEntities MyEntities { get; set; }
//My Methode code:
var query = MyEntities .SasInstallations.AsQueryable();
// Some filter
query = query.Where(i => i.City.RegionId == pRegionId));
var liste = new List<Installation>();
var queryWithListEquipements =
from i in query
select new
{
Ins = i,
EquipementsTemp = (from eq in MyEntities.Equipements.AsQueryable()
where eq.SpecialId == i.SpecialId
&& (etatIds.Contains(eq.SasEquEtat))
select eq
).ToList()
};
var listWithListEquipements = queryWithListEquipements.ToList();
foreach (var anonymousItem in listWithListEquipements)
{
var ins = anonymousItem.Ins;
ins.Equipements = anonymousItem.EquipementsTemp;
ins.NumberEquipements = ins.Equipements.Count();
liste.Add(ins);
}
return liste;
By the way, this is very very fast (even the listing of Equipements). So this is working exactly has I wished. Thanks again for your help everyone!
Use an anonymous type. EF does not like to instantiate entity classes inside a query.
var results = (from i in query
select new
{
NbrEquipements= (from e in RepoEquipement
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e.SasId
).Count()
})
.ToList();
Notice how I used select new instead of select new Installation.
You can then use the data inside the list (which is now in memory) to create instances of type Installation if you want like this:
var installations = results.Select(x =>
new Installation
{
NbrEquipements = x.NbrEquipements
}).ToList();
Here is how to obtain the list of equipment for each installation entity:
var results = (from i in query
select new
{
Installation = i,
Equipment = (from e in RepoEquipement
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e).ToList()
})
.ToList();
This will return a list of anonymous objects. Each object will contain a property called Installation and another property called Equipment (which is a list). You can easily convert this list (of anonymous objects) to another list of whatever type that you want.

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.

Can I combine two Linq calls when they use different syntax?

I have the following code. The function has a lot of Linq calls and I had help on putting this into place.
public IList<Content.Grid> Details(string pk)
{
IEnumerable<Content.Grid> details = null;
IList<Content.Grid> detailsList = null;
var data = _contentRepository.GetPk(pk);
var refType = this.GetRefType(pk);
var refStat = this.GetRefStat(pk);
var type = _referenceRepository.GetPk(refType);
var stat = _referenceRepository.GetPk(refStat);
details =
from d in data
join s in stat on d.Status equals s.RowKey into statuses
from s in statuses.DefaultIfEmpty()
join t in type on d.Type equals t.RowKey into types
from t in types.DefaultIfEmpty()
select new Content.Grid
{
PartitionKey = d.PartitionKey,
RowKey = d.RowKey,
Order = d.Order,
Title = d.Title,
Status = s == null ? null : s.Value,
StatusKey = d.Status,
Type = t == null ? null : t.Value,
TypeKey = d.Type,
Link = d.Link,
Notes = d.Notes,
TextLength = d.TextLength
};
detailsList = details
.OrderBy(item => item.Order)
.ThenBy(item => item.Title)
.Select((t, index) => new Content.Grid()
{
PartitionKey = t.PartitionKey,
RowKey = t.RowKey,
Row = index + 1,
Order = t.Order,
Title = t.Title,
Status = t.Status,
StatusKey = t.StatusKey,
Type = t.Type,
TypeKey = t.TypeKey,
Link = t.Link,
Notes = t.Notes,
TextLength = t.TextLength,
})
.ToList();
return detailsList;
}
The first uses one format for Linq and the second another. Is there some way that I could simplify and/or combine these? I would really like to make this code simpler but I am not sure how to do this. Any suggestions would be much appreciated.
Of course you can combine them. The Linq keywords such as from, where and select get translated into calls like the Extension methods that you call below, so effectively there's no difference.
If you really want to combine them, the quickest way is to put () around the first query, then append the method calls you use on details in the second query. Like this:
detailsList =
(from d in data // <-- The first query
// ...
select new Content.Grid
{
// ...
})
.OrderBy(item => item.Order) // <-- The calls from the second query
.ThenBy(item => item.Title)
.Select((t, index) => new Content.Grid()
{
//...
}).ToList();
But i think that would be ugly. Two queries are just fine IMO.

Categories

Resources