RavenDB: how to write queries on grandchildren? - c#

Given the following C# code below, I am trying to retrieve all the continents having provinces whose cities include:
a city with Name and Address respectively set to "Aloma" and "123" and
another city with Name and Address respectively set to "Hemane" and "435".
public class Continent
{
public string Name { get; set; }
public List<Country> Countries{ get; set; }
}
public class Countries
{
public string Name { get; set; }
public List<Province> Provinces{ get; set; }
}
public class Province
{
public string Name { get; set; }
public List<Province> Cities { get; set; }
}
public class City
{
public string Name { get; set; }
public string Address { get; set; }
}
I have tried to use the query below, but it does not seem to work. Can you please help me?
Expression<Func<Province, bool>> matchCities = rec =>
(rec.Cities.Count(fi => fi.Name == "Aloma" && fi.Address== "123") > 0)
&& (rec.Cities.Count(fj => fj.Name == "Hemane" && fj.Address== "435") > 0);
Func<Province, bool> funcMatchCities= matchCities.Compile();
var results3 = session.Query<Continent>()
.Where(top => top.Countries.Any(ta => ta.Province.Any(
rec => funcMatchCities(rec))))
.OfType<Continent>()
.ToList();

You can query it like so:
var results3 = session.Query<Continent>()
.Where(top => top.Countries.Any(ta => ta.Province.Any(
rec =>
(rec.Fields.Any(fi => fi.Name == "Aloma" && fi.Address== "123") )
&& (rec.Fields.Any(fj => fj.Name == "Hemane" && fj.Address== "435") )))
.OfType<Continent>()
.ToList();
Note that you cannot Compile expression when you send them to the linq provider.

Related

C# and MongoDriver - How to get values from foreign collection (aggregate + lookup)?

I've got a problem with getting values from foreign collection in C#.
In this case I can easily get values from list:
var gamesList = gamesCollection.Find(_ => true).ToList();
foreach (var item in gamesList)
{
Console.WriteLine($"{item.Title}");
}
But when I'm using aggregate with lookup function, I can not access to values from foreign collection.
Here are my two collections which I try to join:
public class GameModel
{
[BsonId]
public ObjectId Id { get; set; }
public string Title { get; set; }
public List<String> Type { get; set; }
public string GameMode { get; set; }
public List<String> Platform { get; set; }
public string Production { get; set; }
}
public class FavouriteGameModel
{
[BsonId]
public ObjectId Id { get; set; }
public ObjectId UserID { get; set; }
public ObjectId GameID { get; set; }
}
And here's the part of problematic code:
var joinedFavGamesList = favouriteGamesCollection.Aggregate().Match(x => x.UserID == loggedUser[0].Id).//ToList();
Lookup("Games", "GameID", "_id", #as: ("myAlias")).
Project(
new BsonDocument { { "_id", 0 }, { "myAlias.Title", 1 } }
).ToList();
Is there any way to invoke to myAlias.Title? I want only this value to display, but i get:
{ "myAlias" : [{ "Title" : "Some Game" }] }
I will be greatful if someone could look at this and tell me what I'm doing wrong. Thanks
my choice would be to join/lookup using the AsQueryable interface like so:
var favGames = favCollection.AsQueryable()
.Where(fg=> fg.UserID== "xxxxxxxxxxx")
.Join(gameCollection.AsQueryable(), //foreign collection
fg => fg.GameID, //local field
gm => gm.ID, //foreign field
(fg, gm) => new { gm.Title }) //projection
.ToList();
with aggregate interface:
public class JoinedGameModel
{
public GameModel[] Results { get; set; }
}
var favGames = favGameCollection.Aggregate()
.Match(fg => fg.UserID == "xxxxxxxxxxxx")
.Lookup<FavouriteGameModel, GameModel, JoinedGameModel>(
gameCollection,
fg => fg.GameID,
gm => gm.ID,
jgm => jgm.Results)
.ReplaceRoot(jgm => jgm.Results[0])
.Project(gm => new { gm.Title })
.ToList();

How do I negotiate joins and groupings based on nested properties in LINQ?

So I've got a nested data structure like this:
public class ContractTerm
{
public int ContractId { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public int TermOrder { get; set; }
public TermItem TermNavigation { get; set; }
}
public class TermItem
{
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public string Text { get; set; }
public ICollection<ContractTerm> ContractNavigation { get; set; }
}
I've also got a class to map the section/subsection pairings in a more EF-friendly way (IRL this is an enum with attribute values and a helper, but this class abstracts away some work not necessary to reproduce the issue):
public class Section
{
public string Name { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
}
Both ContractTerm and TermItem have their own collections in a DbContext, and I'm trying to get a collection of all text entries assigned to specific Sections for a given ContractId. I have the following class to contain it:
public class TextsBySection
{
public string SectionName { get; set; }
public IEnumerable<string> Texts { get; set; }
}
I want to select a collection of TextsBySection, and have something like this:
public class ContractManager
{
//insert constructor initializing MyContext here
private MyContext Context { get; }
public IEnumerable<MyOutputClass> GetTerms(int contractId, IEnumerable<Section> sections)
{
Func<string, string, IEnumerable<string>> getBySection =
(section, subsection) => context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId
&& x.SectionId == section
&& x.SubsectionId == subsection)
.Select(x => x.TermNavigation.Text);
var result = sections.Select(x => new MyOutputClass
{
SectionName = x.Name,
Texts = getBySection(x.SectionId, x.SubsectionId)
}).ToList();
return result;
}
}
This works fine and dandy, but it hits the database for every Section. I feel like there's got to be a way to use Join and/or GroupBy to make it only query once, but I can't quite see it. Something like this, perhaps:
var result = context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId)
.Join(sections,
term => //something
section => //something
(term, section) => /*something*/)
If all this were in SQL, selecting the necessary data would be easy:
SELECT sections.name,
term_items.text
FROM contract_terms
JOIN term_items
ON term_items.section_id = contract_terms.section_id
AND term_items.subsection_id = contract_terms.subsection_id
AND term_items.term_id = contract_terms.term_id
JOIN sections --not a real table; just corresponds to sections argument in method
ON sections.section_id = contract_terms.section_id
AND sections.subsection_id = contract_terms.subsection_id
...and then I could group the results in .NET. But I don't understand how to make a single LINQ query that would do the same thing.
I changed my answer, well I would do something like this... maybe this may help you.
public static void Main(string[] args)
{
List<Section> sections = new List<Section>();
List<ContractTerm> contractTerms = new List<ContractTerm>();
List<TermItem> termItens = new List<TermItem>();
//considering lists have records
List<TextsBySection> result = (from contractTerm in contractTerms
join termItem in termItens
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId,
contractTerm.TermId
}
equals new
{
termItem.SectionId,
termItem.SubsectionId,
termItem.TermId
}
join section in sections
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId
} equals new
{
section.SectionId,
section.SubsectionId
}
select
new
{
sectionName = section.Name,
termItemText = termItem.Text
}).GroupBy(x => x.sectionName).Select(x => new TextsBySection()
{
SectionName = x.Key,
Texts = x.Select(i=> i.termItemText)
}).ToList();
}

Get objects whose property does not exist in enumerable

Multiple answers have led me to the following 2 solutions, but both of them do not seem to be working correctly.
What I have are 2 objects
public class DatabaseAssignment : AuditableEntity
{
public Guid Id { get; set; }
public string User_Id { get; set; }
public Guid Database_Id { get; set; }
}
public class Database : AuditableEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Server { get; set; }
public bool IsActive { get; set; }
public Guid DatabaseClientId { get; set; }
}
Now, the front-end will return all selected Database objects (as IEnumerable) for a given user. I am grabbing all current DatabaseAssignments from the database for the given user and comparing them to the databases by the Database.ID property. My goal is to find the DatabaseAssignments that I can remove from the database. However, my solutions keep returning all DatabaseAssignments to be removed.
if (databases != null)
{
var unitOfWork = new UnitOfWork(_context);
var userDatabaseAssignments = unitOfWork.DatabaseAssignments.GetAll().Where(d => d.User_Id == user.Id);
//var assignmentsToRemove = userDatabaseAssignments.Where(ud => databases.Any(d => d.Id != ud.Database_Id));
var assignmentsToRemove = userDatabaseAssignments.Select(ud => userDatabaseAssignments.FirstOrDefault()).Where(d1 => databases.All(d2 => d2.Id != d1.Database_Id));
var assignmentsToAdd = databases.Select(d => new DatabaseAssignment { User_Id = user.Id, Database_Id = d.Id }).Where(ar => assignmentsToRemove.All(a => a.Database_Id != ar.Database_Id));
if (assignmentsToRemove.Any())
{
unitOfWork.DatabaseAssignments.RemoveRange(assignmentsToRemove);
}
if (assignmentsToAdd.Any())
{
unitOfWork.DatabaseAssignments.AddRange(assignmentsToAdd);
}
unitOfWork.SaveChanges();
}
I think u are looking for an Except extension, have a look at this link
LINQ: Select where object does not contain items from list
Or other way is with contains see below Fiddler link :
https://dotnetfiddle.net/lKyI2F

"Property is not defined" runtime error in Linq query

I'm having an issue using .Net/EF Core, a select statement in one of my view model constructors is returning the following error at Runtime: Property 'System.String Name' is not defined for type 'Microsoft.EntityFrameworkCore.Storage.ValueBuffer'.
I've tried commenting out the line I suspected was causing the issue and have narrowed it down to that, but I can't figure out what's wrong with what I'm doing.
The following is the expression that's causing the error:
playerholder = players.Select(m => new PlayerListItem_PDMI
{
PlayerID = m.PlayerID,
FirstName = m.FirstName,
LastName = m.LastName,
Rating = m.Rating,
Assigned = m.TeamAssignments.Where(n => n.Team.League == league && n.SeasonID == SeasonID).Count() > 0,
TeamName = (m.TeamAssignments.Where(n => n.Team.League == league && n.SeasonID == SeasonID).Count() > 0 ? m.TeamAssignments.Where(n => n.Team.League == league && n.SeasonID == SeasonID).First().Team.Name : "Not Assigned")
}).ToList();
and the specific line is
TeamName = (m.TeamAssignments.Where(n => n.Team.League == league && n.SeasonID == SeasonID).Count() > 0 ? m.TeamAssignments.Where(n => n.Team.League == league && n.SeasonID == SeasonID).First().Team.Name : "Not Assigned")
What am I missing here?
Note: The database being queried is completely empty. The check for records should return false and make the ternary expression return "Not Assigned"
Edit:
The TeamAssignment class is as follows:
public class TeamAssignment : IModel
{
public TeamAssignment()
{
}
public TeamAssignment(WWAHLContext db)
{
Season = db.Seasons.Last();
}
[Key]
public int ID { get; set; }
public int PlayerID { get; set; }
public int SeasonID { get; set; }
public int TeamID { get; set; }
public int PlayerNumber { get; set; }
public Position Position { get; set; }
[ForeignKey("PlayerID")]
public virtual Player Player { get; set; }
[ForeignKey("TeamID")]
public virtual Team Team { get; set; }
[ForeignKey("SeasonID")]
public virtual Season Season { get; set; }
}
and the Team class is this:
public class Team : IModel
{
public int TeamID { get; set; }
public int SeasonID { get; set; }
public League League { get; set; }
public string Name { get; set; }
public virtual TeamStats TeamStats { get; set; }
public virtual TeamCarousel Carousel { get; set; }
public virtual ICollection<GameDetails> Games { get; set; }
public virtual ICollection<CurrentTeam> CurrentPlayers { get; set; }
[NotMapped]
public virtual IEnumerable<Penalty> Penalties => Games.SelectMany(m => m.Penalties);
}
I resolved this by using joins as follows (edited for left outer join):
playerholder = players.GroupJoin(db.TeamAssignments, player => player.PlayerID, assignment => assignment.PlayerID, (player, assignment) => new
{
PlayerID = player.PlayerID,
FirstName = player.FirstName,
LastName = player.LastName,
Rating = player.Rating,
NameLeague = assignment.Where(m => m.SeasonID == seasonId).Join(db.Teams, assign => assign.TeamID, team => team.TeamID, (assign, team) => new
{
League = team.League,
Name = team.Name
}).Where(m => m.League == league)
}).Select(m => new PlayerListItem_PDMI
{
PlayerID = m.PlayerID,
FirstName = m.FirstName,
LastName = m.LastName,
Rating = m.Rating,
Assigned = m.NameLeague.Any(),
TeamName = (m.NameLeague.Any() ? m.NameLeague.First().Name : "Not Assigned")
}).ToList();
First, let's make a sensible query out of this.....
playerholder = from m in players
let team = m.TeamAssignments.FirstOrDefault(n => n.Team.League == league && n.SeasonID == SeasonID)
select new PlayerListItem_PDMI
{
PlayerID = m.PlayerID,
FirstName = m.FirstName,
LastName = m.LastName,
Rating = m.Rating,
Assigned = team != null,
TeamName = team != null ? team.Name : "Not Assigned")
}).ToList();
Just try that first to see if it works, or gives the same error. Since the error references ValueBuffer, I'm going to guess that the thing .Name is attached to is not a Team object --- possibility due to a misplace parenthesis in that mess...

LINQ multiple keyword search to PagedList

I'm a bit lost here and I've tried a few different ways to tackle it. So far I'm having a hard time writing out the LINQ to do what I want.
I want to take the user input string which can be multiple keywords split either by whitespace or ",".
This here works grabs the whole search term and compares it to the title in the Post or any tag I may have. I want the user to type in "HTML Preview" which would match a post called, "Preview the World" with the tags "HTML", "CSS", etc....
This query won't work...but I'm trying to modify it so that it does work.
public IPagedList<Post> SearchResultList(string searchTerm, int resultsPerPage, int page)
{
string[] terms = searchTerm.Split(null);
TNDbContext context = DataContext;
return context.Posts
.Include(a => a.Tags)
.Include(b => b.Comments)
.Where(c => (c.Title.Contains(searchTerm) || c.Tags.Any(d => d.Name.StartsWith(searchTerm))) || searchTerm == null)
.OrderByDescending(x => x.Views)
.ToPagedList(page, resultsPerPage);
}
I tried writing this instead of the other "Where" statement
.Where(x => (terms.All(y => x.Title.Contains(y))) || terms == null)
but it keeps throwing this error
Cannot compare elements of type 'System.String[]'. Only primitive types, enumeration types and entity types are supported.
FOR REFERENCE:
public class Post
{
public Post()
{
Tags = new HashSet<Tag>();
Comments = new HashSet<Comment>();
}
public int Id { get; set; }
public string Title { get; set; }
public string UrlTitle { get; set; }
public DateTime Date { get; set; }
public DateTime DateEdited { get; set; }
public string Body { get; set; }
public string Preview { get; set; }
public string PhotoPath { get; set; }
public int Views { get; set; }
//Navigational
public ICollection<Tag> Tags { get; set; }
public ICollection<Comment> Comments { get; set; }
}
public class Tag
{
public Tag()
{
Post = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; }
public int TimesTagWasUsed { get; set; }
//Navigational
public ICollection<Post> Post { get; set; }
}
You need to start with a base query, and then keep adding where clauses to it for each search term. Try this:
TNDbContext context = DataContext;
//Create the base query:
var query = context.Posts
.Include(a => a.Tags)
.Include(b => b.Comments)
.OrderByDescending(x => x.Views);
//Refine this query by adding "where" filters for each search term:
if(!string.IsNullOrWhitespace(searchTerm))
{
string[] terms = searchTerm.Split(" ,".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries);
foreach(var x in terms)
{
string term = x;
query = query.Where(post => (post.Title.Contains(term) ||
post.Tags.Any(tag => tag.Name.StartsWith(term))));
}
}
//Run the final query to get some results:
var result = query.ToPagedList(page, resultsPerPage);
return result;
You can nest queries with additional 'from' statements, so something like this should work:
var list = (from post in context.Posts.Include(a => a.Tags).Include(b => b.Comments)
from term in terms
where post.Title.Contains(term) || post.Tags.Any(d => d.Name.StartsWith(term))
select post).OrderByDescending(x => x.Views);

Categories