Is there an equivalent of a SQL IN statement in LINQ to objects?
Yes - Contains.
var desiredNames = new[] { "Jon", "Marc" };
var people = new[]
{
new { FirstName="Jon", Surname="Skeet" },
new { FirstName="Marc", Surname="Gravell" },
new { FirstName="Jeff", Surname="Atwood" }
};
var matches = people.Where(person => desiredNames.Contains(person.FirstName));
foreach (var person in matches)
{
Console.WriteLine(person);
}
(In LINQ to SQL this ends up as an "IN" query.)
Note that in LINQ to Objects the above isn't really very efficient. You'd be better off with a join:
var matches = from person in people
join name in desiredNames on person.FirstName equals name
select person;
(This could still be done with dot notation of course, but it ends up being somewhat messier.)
I will go for Inner Join in this context. If I would have used contains, it would iterate 6 times despite if the fact that there are just two matches. I just want to emphasize here that I will go for Joins instead of IN predicate.
var desiredNames = new[] { "Pankaj" };
var people = new[]
{
new { FirstName="Pankaj", Surname="Garg" },
new { FirstName="Marc", Surname="Gravell" },
new { FirstName="Jeff", Surname="Atwood" }
};
var records = (from p in people join filtered in desiredNames on p.FirstName equals filtered select p.FirstName).ToList();
Related
I would like to transform the below query:
(from documentType in entitySet
join userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
on documentType.Id equals userGroupId.Id into UserGroupIds
from userGroupId in UserGroupIds.DefaultIfEmpty()
join documentTypePermission in Repository.DocumentTypePermissions
on documentType.Id equals documentTypePermission.DocumentTypeId
join userSelectionParam in Repository.UserSelectionParams
on documentTypePermission.UserSelectionId equals userSelectionParam.Id
where documentTypePermission.IsActive &&
((userSelectionParam.UserGroupId != null && userGroupId.Id != null)
|| (userSelectionParam.UserId != null && userSelectionParam.UserId == CurrentUserId))
select documentTypePermission);
to something like this:
var query =
from documentType in entitySet
from userGroupId in Repository.ConvertToBigIntTable(userGroupIds, "userGroupId")
.Where(userGroupId => documentType.Id == userGroupId.Id)
.DefaultIfEmpty()
join documentTypePermission in Repository.DocumentTypePermissions
on documentType.Id equals documentTypePermission.DocumentTypeId
join userSelectionParam in Repository.UserSelectionParams
on documentTypePermission.UserSelectionId equals userSelectionParam.Id
....
Note:
I have the setup ready for intercepting the expression tree evaluation using QueryTranslationPreprocessor
I need the logic to generate the output of .Call, .SelectMany etc by using the first linq and then translating the same to second linq query shown above
The caveats are that internally the expression engine generates so many Anonymous types that I am unable to write a generic code that would suffice different cases of linq with such groupjoins
Reason to do this:
There are several kinds of GroupJoin issues in EF Core 6 which the community isn’t ready to resolve
I can't make the change to Linq query directly as there are 1000s of them and in several places
So after evaluating multiple options, the best way to do the above is as below at VisitMethodCall(MethodCallExpression node)
if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.GroupJoin) && node.Arguments.Count == 5)
{
var outer = Visit(node.Arguments[0]);
var inner = Visit(node.Arguments[1]);
var outerKeySelector = Visit(node.Arguments[2]).UnwrapLambdaFromQuote();
var innerKeySelector = Visit(node.Arguments[3]).UnwrapLambdaFromQuote();
var resultSelector = Visit(node.Arguments[4]).UnwrapLambdaFromQuote();
var outerKey = outerKeySelector.Body.ReplaceParameter(outerKeySelector.Parameters[0], resultSelector.Parameters[0]);
var innerKey = innerKeySelector.Body;
var keyMatch = MatchKeys(outerKey, innerKey);
var innerQuery = Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { innerKeySelector.Parameters[0].Type },
inner, Expression.Lambda(keyMatch, innerKeySelector.Parameters));
var asEnumerableInnerQuery = Expression.Call(
typeof(Enumerable),
nameof(Enumerable.AsEnumerable),
new Type[] { innerKeySelector.Parameters[0].Type }, innerQuery);
var resultTypes = resultSelector.Parameters.Select(p => p.Type).ToArray();
var tempProjectionType = typeof(Tuple<,>).MakeGenericType(resultTypes);
var tempProjection = Expression.New(
tempProjectionType.GetConstructor(resultTypes),
new Expression[] { resultSelector.Parameters[0], asEnumerableInnerQuery },
tempProjectionType.GetProperty("Item1"), tempProjectionType.GetProperty("Item2"));
var tempQuery = Expression.Call(
typeof(Queryable), nameof(Queryable.Select), new[] { outerKeySelector.Parameters[0].Type, tempProjectionType },
outer, Expression.Lambda(tempProjection, resultSelector.Parameters[0]));
var tempResult = Expression.Parameter(tempProjectionType, "p");
var projection = resultSelector.Body
.ReplaceParameter(resultSelector.Parameters[0], Expression.Property(tempResult, "Item1"))
.ReplaceParameter(resultSelector.Parameters[1], Expression.Property(tempResult, "Item2"));
var query = Expression.Call(
typeof(Queryable), nameof(Queryable.Select), new[] { tempProjectionType, projection.Type },
tempQuery, Expression.Lambda(projection, tempResult));
return query;
}
return base.VisitMethodCall(node);
}
I want to add a dummy member to an IQueryable and came up with this solution:
IQueryable<Geography> geographies = _unitOfWork.GeographyRepository.GetAll(); //DbSet<Geography>
var dummyGeographies = new Geography[] { new Geography { Id = -1, Name = "All" } }.AsQueryable();
var combinedGeographies = geographies.Union(dummyGeographies);
var test = combinedGeographies.ToList(); //throws runtime exc
But it throws the following exception:
Processing of the LINQ expression 'DbSet
.Union(EnumerableQuery { Geography, })' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
How could I make it work?!
you can only union on data structure which are the same
IQueryable is only applicable if the query expression not been been expressed (ToList) before its run against db and you want the expression modifiable . aka nothing which which is not going to db as a query needs to be IQueryable (simple explanation better to research and understand this yourself)
List<Geography> geographies = _unitOfWork.GeographyRepository
.GetAll() //DbSet<Geography>
.Select(o => new Geography { Id = o.Id, Name = o.Name })
.ToList();
List<Geography> dummyGeographies = new List<Geography>() {
new Geography[] { new Geography { Id = -1, Name = "All" } }
};
var combinedGeographies = geographies.Union(dummyGeographies);
var test = combinedGeographies.ToList();
I was able to achieve it with the following code:
IQueryable<Geography> geographies = _unitOfWork.GeographyRepository.GetAll().Select(o => new Geography { Id = o.Id, Name = o.Name });
IQueryable<Geography> dummyGeographies = _unitOfWork.GeographyRepository.GetAll().Select(o => new Geography { Id = -1, Name = "All" });
var combinedGeographies = geographies.Union(dummyGeographies);
If we do a .ToString() on a Linq expressiontree, we get a reprensentation of the actual code. Similarly is there a way to get the actual linq expression if you have access to an implementation of IQueryable?
In other words suppose I have this
var cars = new List<Car> { new Car { name = "Ford" }, new Car { name = "Merc"}, new Car { name = "Toyota" } };
var myCar = cars.Where(c => c.name =="Ford").AsQueryable();
How do I do this
var originalQuery=myCar.ToString() // I expect cars.Where(c => c.name =="Ford")
Looks like join cannot use sets defined inside query or am I doing something wrong?
from a in new[] {
new { Id = 1 },
new { Id = 2 } }
let bees = new[] {
new { Id = 2 },
new { Id = 3 } }
join b in bees on a.Id equals b.Id
select 1;
This one gives compile time error 'Element "bees" does not exist in the current context.' What's wrong with the query?
This is not legal either way you slice it - you cannot declare a range variable "in the middle" of a join - internally the let clause gets translated to a Select() statement with an anonymous type - but you cannot use Select() either in the middle of the join, you have to move it after the join.
Have a look at this question - I think it covers things:
Can i use join with let in linq - c#
Basically you can only use the let for the query, rather than for joins.
The following does seem to work, but its not as nice:
from a in new[] {
new { Id = 1 },
new { Id = 2 } }
join b in new[] {
new { Id = 2 },
new { Id = 3 } } on a.Id equals b.Id
select 1;
I'm building a word anagram program that uses a database which contains one simple table:
Words
---------------------
varchar(15) alphagram
varchar(15) anagram
(other fields omitted for brevity)
An alphagram is the letters of a word arranged in alphabetical order. For example, the alphagram for OVERFLOW would be EFLOORVW. Every Alphagram in my database has one or more Anagrams. Here's a sample data dump of my table:
Alphagram Anagram
EINORST NORITES
EINORST OESTRIN
EINORST ORIENTS
EINORST STONIER
ADEINRT ANTIRED
ADEINRT DETRAIN
ADEINRT TRAINED
I'm trying to build a LINQ query that would return a list of Alphagrams along with their associated Anagrams. Is this possible?
UPDATE: Here's my solution based on the suggestions below! Thanks all!
using (LexiconEntities ctx = new LexiconEntities())
{
var words = ctx.words;
var query =
from word in words
where word.alphagram == "AEINRST"
group word by word.alphagram into alphagramGroup
select new { Alphagram = alphagramGroup.Key, Anagrams = alphagramGroup };
foreach (var alphagramGroup in query)
{
Console.WriteLine("Alphagram: {0}", alphagramGroup.Alphagram);
foreach (var anagram in alphagramGroup.Anagrams)
{
Console.WriteLine("Anagram: {0}", anagram.word1);
}
}
}
var list = anagrams.Select(
a => new {
Alphagram = a.ToCharArray().OrderBy(s => s).ToString(),
Anagram = a
}).toList();
A totally new answer...
You seem to need a groupby query look at How to: Group Data (Entity Framework).
this should accomplish what you want...
I did a testy with LINQ and this works...
var words = new List<Word>()
{
new ConsoleApplication1.Word("EINORST", "NORITES"),
new ConsoleApplication1.Word("EINORST", "OESTRIN"),
new ConsoleApplication1.Word("EINORST", "STONIER"),
new ConsoleApplication1.Word("ADEINRT", "ANTIRED"),
new ConsoleApplication1.Word("ADEINRT", "DETRAIN"),
new ConsoleApplication1.Word("ADEINRT", "TRAINED")
};
var q = words.GroupBy(w => w.Alphagram).Select(w => new { Alphagram = w.Key, Anagrams = w.Select(p => p.Anagram).ToList() }).ToList();
foreach (var item in q)
{
Console.WriteLine("Alphagram : {0}, Anagrams = {1}", item.Alphagram, String.Join(",", item.Anagrams));
}
var words = new List<Words>()
{
new Words("EINORST", "NORITES"),
new Words("EINORST", "OESTRIN"),
new Words("EINORST", "STONIER"),
new Words("ADEINRT", "ANTIRED"),
new Words("ADEINRT", "DETRAIN"),
new Words("ADEINRT", "TRAINED")
};
var result = words.GroupBy(w => w.Alphagram, w => w.Anagram)
.Select(w => new {
Alphagram = w.Key,
Anagrams = w.Where(p => w.Key.ToCharArray().SequenceEqualUnOrdered(p.ToCharArray())).ToList()
}
)
.ToList();
public static bool SequenceEqualUnOrdered<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
return new HashSet<T>(first).SetEquals(second);
}
Is it what you are looking for? It is LINQ to Objects. You may want to use LINQ-to-SQL or LINQ-to-Entites to fetch your records into your objects and then use the above-mentioned LINQ-to-Objects query over the already-fetched object collection.