OrderBy embedded documents - c#

I'm trying to order my results by the value in an embedded document.
Consider a model such as:
public class Car
{
public Guid ID { get; set; }
public string Name { get; set; }
public IEnumerable<Passenger> Passengers { get; set; }
}
public class Passenger
{
public Guid ID { get; set; }
public virtual string Name { get; set; }
public virtual int Age { get; set; }
}
I'm trying to query my Car collection, and order by Passenger.Age
My query looks something like:
var results = (from car in _db.GetCollection<Car>("car").AsEnumerable()
from passenger in car.Passengers
where car.Name == "Ford"
orderby passenger.Age).ToList();
With this, I get the following exception:
The SelectMany query operator is not supported.
This is understandably a limitation of the C# mongo driver.
Is there a workaround?
Failing that, how could I order them after my .ToList() ?

You can probably re-write this with AsQueryable() to get an IEnumerable collection from ToList() back, from which you can then further query with any LINQ you want to use, not just the operations directly supported by the MongoCollection:
var passengers = _db.GetCollection<Car>("car").AsQueryable().ToList()
.Where(car => car.Name == "Ford")
.SelectMany(ford => ford.Passengers)
.OrderBy(p => p.Age);
Here's where you can find the directly supported LINQ operations for the MongoDb C# driver.

Related

In LINQ, how can I do an .OrderBy() on data that came from many to many relationship?

I have already seen the answers to these questions In LINQ, how can I do an .OrderBy() on data that came from my .Include()?, ^ and ^, However, None is the answer to my question.
I have three entities: Letter, Person, LetterPerson as follows:
public class Letter
{
public int LetterId { get; set; }
public string Title { get; set; }
//MtoM
public ICollection<LetterPerson> LetterPersons { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
//MtoM
public ICollection<LetterPerson> LetterPersons { get; set; }
}
public class LetterPerson
{
public int LetterPersonId { get; set; }
[ForeignKey("Letter")]
public int LetterId { get; set; }
[ForeignKey("Person")]
public int PersonId { get; set; }
public DateTimeOffset? AssignDate { get; set; }=DateTimeOffset.Now;
public Letter Letter { get; set; }
public Person Person { get; set; }
}
The Letter entity has Many To Many relationship with the Person entity by the LetterPerson entity. Now, I'd like to get a list of the person according to a specific letter's id and order by on the LetterPerson's id.
I have something like the following query in mind:
var PersonRec = await _dbContext.Persons
.Include(u => u.LetterPersons)
.Where(u => u.LetterPersons.Any(i => i.LetterId == LetterId))
.OrderBy(u => u.LetterPersons.LetterPersonId)
//.Include(u => u.LetterPersons.OrderBy(f=>f.LetterPersonId))
//.Where(u => u.LetterPersons.OrderBy(f=>f.LetterPersonId).Any(i => i.LetterId == LetterId))
//.OrderBy(u => u.LetterPersons.FirstOrDefault().LetterPersonId)
.ProjectTo<PersonDTO>(_mapperConfiguration).ToListAsync();
The above commented codes are the attempts that I made, but still the desired result was not achieved. I need .OrderBy(u => u.LetterPersons.LetterPersonId) , However, it clearly gives a compile error.
Question:
How should I correct the OrderBy part?
Just note that I have to send the query as a Data Transfer Object (PersonDTO) that the same as Person entity except FirstName field.
I use EF6 in .Net6.
If the combination (LetterId, PersonId) is unique in the joining table (typical for many-to-many), then the one-to-many relation from Person to LetterPerson for specific LetterId value becomes one-to-one, hence you can use Select or SelectMany with filter to get the single LetterPerson entry, which then could be used for ordering.
For isntance, using LINQ query syntax (more natural for such type of queries):
var query =
(
from p in _dbContext.Persons
from lp in p.LetterPersons
where lp.LetterId == LetterId
orderby lp.LetterPersonId
select p
)
.ProjectTo<PersonDTO>(_mapperConfiguration);
var result = await query.ToListAsync();
Note that you don't need Include in order to access related data inside LINQ to Entities query. Also for projection queries Includes are ignored.

Perform a query over a nested collection in ElasticSearch with NEST in .NET Core

I am trying to perform a search over an index for the following object:
public class IndexedElement
{
public Guid Id { get; set; }
public long RowId { get; set; }
public IndexedElementType Type { get; set; }
public string Summary { get; set; }
public string Description { get; set; }
public IList<string> Tags { get; set; }
}
The purpose is to search by the Summary property, or by matching any of the strings inside the Tags collections
What I currently have is this:
public IEnumerable<IndexedElement> Search(string description)
{
var query = GetClient().Search<IndexedElement>(s => s.From(0).Size(5)
.Query(
q => q.Term(p => p.Summary, description)
||
q.Nested(n => n.Path(p => p.Tags).Query(q2 => q2.Terms(t => t.Field(f => f.Tags).Terms(description))))
));
return query.Documents.ToList();
}
But the Nested part is not working, I don't know if I am using it in the proper way or maybe I have to find another solution for that.
Any ideas?
Thank you all in advance
You don't need to perform a nested query to query the Tags field, as each tag is only a primitive JSON value i.e. a string. Just the terms query will suffice.
Where a nested query would be needed is where Tags is a POCO with multiple properties and is mapped as a nested datatype.

NHibernate, QueryOver returning list with list property

I have classes like:
public class A : BaseTableFields
{
public virtual string Name { get; set; }
public virtual IList<B> propB { get; set; }
}
public class B : BaseTableFields
{
public virtual A propA { get; set; }
public virtual IList<C> propC { get; set; }
}
public class C : BaseTableFields
{
public virtual B propB { get; set; }
public virtual IList<D> propD { get; set; }
}
So each of my class has one to many relation to class below of it.
How to write the most effecient query, that I receive List of type A (List listOfA) containing records in listOfA.propB and also the listOfA.propB having all of the referencing records in listOfA.propB.propC and so on.
Please help.
Let's assume for the start that:
var list = Session.QueryOver<A>().Where(x=>x.Name == "test").List().ToList();
returns me list with 3 elements of type A, but its property propB is empty.
I would suggest using the 'Fetch' or 'FetchMany' functions in the NHibernate LINQ provider. Examples are in this article for what it actually does and it shows the SQL it will generate.
Working off your example that would result like this:
var list = Session.QueryOver<A>()
.Where(x => x.Name == "test")
.FetchMany(x => x.propB)
.ThenFetchMany(x => x.propC)
.ToList();
If this is still causing you issues, then there may be a problem with your mapping files between the one to many relationships of the entities.

How to write LINQ query that selects a property which is a string concat of a collection of a nested entity properties?

Trying to be more efficient with my queries, but not sure how to write this all as one query. I've got a domain model:
public class UserReport : Entity
{
public string Name { get; set; }
public List<string> Statuses { get; set; }
public List<GroupModel> Groups { get; set; }
public string Email { get; set; }
public string OfficeStates {get; set;} //Comma delimited list
}
public class GroupModel : Entity
{
public string Name {get; set;}
public string Type {get; set;
}
Which is a "compound entity", if you will. The standard entities representing those collections are M2M relational entities with the User object:
public class User : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Status> Statuses { get; set; }
public ICollection<Group> Groups { get; set; }
public ICollection<Office> Offices { get; set; }
}
public class Office : Entity
{
//other properties
public State State { get; set; }
}
public class State : Entity
{
public string Name { get; set; }
public string Abbreviation { get; set; }
}
So far I've got:
Context.DbSet<User>.Select(user => new UserReport()
{
Name = user.FirstName + ", " + user.LastName,
Email = user.Email,
Statuses = user.Statuses.Select(status => status.Name).ToList(),
Groups = user.Groups.Select(group => new GroupModel(){ Name = group.Name, Type = group.Type.Name}).ToList(),
OfficeStates = string.Join(",", user.Offices.Select(office => office.State.Abbreviation).ToList())
}).ToList();
Which throws an error:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.Collections.Generic.IEnumerable`1[System.String])' method, and this method cannot be translated into a store expression.
I totally get what this is saying and I know if I called .ToList() before my select it would work, but I need to have this be one query, so I can still have an IQueryable to apply filtering later. Surely there must be some way in LINQ to do this. It can be done in SQL: Concatenate many rows into a single text string?, how can it be done via LINQ?
You can see that even in pure SQL that is not trivial (for xml, declaring variables etc). As far as I know there is no way to do exactly what you want with pure LINQ. However, at least in some cases you can do that using one query, though this query will not be the same as you would do this yourself in pure SQL. In your case that would be something like this:
Context.DbSet<User>.Select(user => new // note, anonymous type here
{
Name = user.FirstName + ", " + user.LastName,
Email = user.Email,
Statuses = user.Statuses.Select(status => status.Name), // no ToList - this won't work
Groups = user.Groups.Select(group => new GroupModel(){ Name = group.Name, Type = group.Type.Name}), // no ToList()
OfficeStates = user.Offices.Select(office => office.State.Abbreviation) // no ToList(), no String.Join()
}).ToList() // here we materizized, and now we can concatenate strings by hand
.Select(c => new UserReport {
Name = c.Name,
Email = c.Email,
Statuses = c.Statuses.ToList(),
Groups = c.Groups.ToList(),
OfficeStates = String.Join(",", c.Offices)
});
In simple cases I tested on, this generates one query to database, and this query receives only columns you need (though as I said - generated query is not the same you would use in SQL, conceptually). So I suggest to try this approach and see what query is generated (note also my comments in code above).

Convert IGrouping from one class to another using linq

I have two classes Teams and PlayerTeams
public class PlayerTeams
{
public int ID { get; set; }
public string PlayerName { get; set; }
public string PlayerCountry { get; set; }
public bool IsActive { get; set; }
public string PlayerTeam { get; set; }
}
public class Players
{
public int ID { get; set; }
public string Name { get; set; }
public string Country { get; set; }
public bool? Status { get; set; }
}
I have a list of PlayerTeams which is grouped by PlayerTeam like this.
var groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy( x => x.PlayerTeam)
.ToList();
Its of type List<IGrouping<string, PlayerTeams>> but I want it to be of type List<IGrouping<string, Players>> as I do not want the redundant key information on every row.
How could I possibly achieve that? I could only think of something like .ConvertAll() on the IGrouping. I am not able to make it also.
Is there an efiicient way to do this?
If you can change the grouping, I'd just use:
var groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy(x => x.PlayerTeam, Players.FromPlayerTeam)
.ToList();
Where Players.FromPlayerTeam is a static method in Players which takes a PlayerTeam and returns a Players.
Additionally, I'd suggest using ToLookup instead of GroupBy - a Lookup is exactly what you want here, without bothering with the ToList call.
This not testet, just an idea.
If you have trouble converting your linq statement, which is expecting the IGrouping type, to a string list, then you might have to select it before.
var groupedPlayers = new List<string>();
groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy(x => x.PlayerTeam, Players.FromPlayerTeam)
.Select(x => x.Key) // << added select
.ToList();

Categories

Resources