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,
}
Related
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 query which selects a list of my class. It looks like so:
IQueryable<ClaimsBySupplierAggregate> agg =
(from d in alliance.SupplierSearchByReviewPeriod
where d.ClientID == ClientID && ReviewPeriodIDs.Contains((int)d.ReviewPeriodID)
select new ClaimsBySupplierAggregate {
Amount = d.Amount,
StatusCategoryID = d.StatusCategoryID,
DeptName = d.DepartmentName,
APLReason = d.APLReason,
Area = d.AreaDesc,
StatusCategoryDesc = d.StatusCategoryDesc,
Agreed = d.Agreed
});
Later on in the application I select each variable and get the distinct values like this:
SupplierModel.APLReason = agg.Select(r => r.APLReason).Distinct().ToList();
SupplierModel.AreaDesc = agg.Select(r => r.Area).Distinct().ToList();
SupplierModel.DeptName = agg.Select(r => r.DeptName).Distinct().ToList();
SupplierModel.StatCatDes = aggg.Select(r => r.StatusCategoryDesc).Distinct().ToList();
Is there a way to do this in one LINQ statement?
You could using Aggregate, but you would need a complex object for the seed, which brings you to the same complexity of code. I think that for this particular case, using LInQ enters the Golden Hammer antipattern. Just use an old fashioned loop and four HashSets instead of Lists and you are done and the code is more readable and your intention clearer.
I think you can use this then customize the code follow your expectation.
Or you can use group by.
public class LinqTest
{
public int id { get; set; }
public string value { get; set; }
public override bool Equals(object obj)
{
LinqTest obj2 = obj as LinqTest;
if (obj2 == null) return false;
return id == obj2.id;
}
public override int GetHashCode()
{
return id;
}
}
List<LinqTest> uniqueIDs = myList.Distinct().ToList();
There is a contrived way to do this:
from d in alliance.SupplierSearchByReviewPeriod
where d.ClientID == ClientID && ReviewPeriodIDs.Contains((int)d.ReviewPeriodID)
group d by 0 into g
select new
{
APLReasons = g.Select(d => d.APLReason).Distinct(),
AreaDescs = g.Select(d => d.Area).Distinct(),
DeptNames = g.Select(d => d.DeptName).Distinct(),
StatusCategoryDescs = g.Select(d => d.StatusCategoryDesc).Distinct(),
}
The part group d by 0 into g creates one group of all items, from which you can subsequently query any aggregate you want.
BUT...
...this creates a very inefficient query with UNIONs and (of course) DISTINCTs. It's probably better (performance-wise) to simply get the flat data from the database and do the aggregations in subsequent code.
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 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
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.