Linq - filter inner list - c#

I’m trying to filter a list based on this classes - The result should be a list with all the machines where there are sessions that match the search condition, but only those sessions.
class Machine {
public string collectionName { get; set; }
public string machineName { get; set; }
public bool status { get; set; }
public List<Session> sessionList { get; set; }
}
class Session {
public string userId { get; set; }
public string userName { get; set; }
public string computerName { get; set; }
public IPAddress ipAddress { get; set; }
}
List<Machine> allMachineData;
var auxfindResults = (from machine_item in allMachineData
from session_item in machine_item.sessionList
where (session_item.userId.ToUpperInvariant().Contains(searchTerm.ToUpperInvariant())
|| session_item.userName.ToUpperInvariant().Contains(searchTerm.ToUpperInvariant()))
select machine_item).ToList();
I get a list of all machines with sessions matching the condition, but i also get results that I don't want i.e. sessions that don't match the conditions.
If instead I try:
var auxfindResults = (from machine_item in allMachineData
from session_item in machine_item.sessionList
where (session_item.userId.ToUpperInvariant().Contains(searchTerm.ToUpperInvariant())
|| session_item.userName.ToUpperInvariant().Contains(searchTerm.ToUpperInvariant()))
select session_item).ToList();
I get all the sessions matching the condition, but obviously i loose the "machine" part
I have a working solution using loops, but I don't like it.
Is there any way of doing this using linq - I’m sure there is, but I can’t find it.
Any suggestions/pointers will be greatly appreciated.

You need to also filter the sessions you include in your selection.
Using Linq method syntax:
searchTerm = searchTerm.ToLower();
var result = allMachineData
.Where(m => m.sessionList.Any(s => s.userId.ToLower().Contains(searchTerm) || s.userName.ToLower().Contains(searchTerm)))
.Select(m => new Machine
{
collectionName = m.collectionName,
machineName = m.machineName,
status = m.status,
sessionList = m.sessionList.Where(s => s.userId.ToLower().Contains(searchTerm) || s.userName.ToLower().Contains(searchTerm)).ToList(),
}).ToList();

Related

Query separate collection in RavenDB Index (WHERE IN)

Using RavenDB v4.2 or higher, I want to setup an index that queries another collection. Basically, reproduce a WHERE IN clause in the mapping part of the index.
The models below represent two collections. Here each User has a collection of Device ID's:
class Device {
public string Id { get; set; }
public string Name { get; set; }
}
class User {
public string Id { get; set; }
public string BlogPostId { get; set; }
public List<string> DeviceIds { get; set; }
}
Now consider the following index as an example on what I'm trying to achieve:
public class DeviceIndex : AbstractIndexCreationTask<Device, DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
Map = devices => from d in devices
select new Result
{
Id = d.Id,
DeviceName = d.Name,
HasUser = ... ?, // How to get this from Users collection?
UserCount = ... ? // same...
};
}
How do I fill the HasUser true/false and UserCount properties in this index? E.g. how can I query the 'User' collection here?
Please note that this example is seriously simplified for brevity. I'm not so much interested in workarounds, or changing the logic behind it.
As #Danielle mentioned you need to use a mutli-map-index and reduce the result.
Here is a working example
public class DeviceIndex : AbstractMultiMapIndexCreationTask<DeviceIndex.Result>
{
public class Result
{
public string Id { get; set; }
public string DeviceName { get; set; }
public bool HasUser { get; set; }
public int UserCount { get; set; }
}
public DeviceIndex()
{
AddMap<User>(users => from u in users
from deviceId in u.DeviceIds
let d = LoadDocument<Device>(deviceId)
select new Result
{
Id = d.Id,
HasUser = true,
UserCount = 1,
DeviceName = d.Name,
});
AddMap<Device>(devices => from d in devices
select new Result
{
Id = d.Id,
HasUser = false,
UserCount = 0,
DeviceName = d.Name,
});
Reduce = results => from result in results
group result by new { result.Id } into g
select new Result
{
Id = g.First().Id,
DeviceName = g.First().DeviceName,
HasUser = g.Any(e => e.HasUser),
UserCount = g.Sum(e => e.UserCount),
};
}
}
and you can call it like this
var result = await _session.Query<DeviceIndex.Result, DeviceIndex>().ToListAsync();
If you would have a Users List in the Device class List<string> Users
a list that contains the document ids from the Users collection then you could Index these Related documents.
See:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
Or do the opposite,
Create an index on the Users collection, and index the related Device info
Without changing current models,
You can create a Multi-Map Index to index data from different collections.
https://ravendb.net/docs/article-page/4.2/csharp/indexes/multi-map-indexes
https://ravendb.net/docs/article-page/4.2/csharp/studio/database/indexes/create-multi-map-index
https://ravendb.net/learn/inside-ravendb-book/reader/4.0/10-static-indexes-and-other-advanced-options#querying-many-sources-at-once-with-multimap-indexes

Search and Order results by keywords

I'm trying to create a search on a collection of Organisations (I'm using LINQ to Entities).
public class Organisation
{
public string OrgName { get; set; }
public string ContactName { get; set; }
public string OverviewOfServices { get; set; }
public string Address1 { get; set; }
public string Town { get; set; }
public string PostCode { get; set; }
public string Keywords { get; set; }
}
The user inputs some keywords, and then I want to return all Organisations where all the keywords exist in any of the Organisation fields above.
// 1. Remove special characters and create an array of keywords
string[] Keywords = CleanKeyWordString(model.keyword)
// 2 . Get organisations
orglist = _UoW.OrganisationRepo.All();
(OrganisationRepo.All returns an IQueryable of Organisation. There are further queries on the search prior to this)
// 3. Filter results by keywords
orglist = (from org in orglist
where Keywords.All(s =>
org.OrgName.ToLower().Contains(s)
|| org.OverviewOfServices.ToLower().Contains(s)
|| org.ContactName.Contains(s)
|| org.Address1.ToLower().Contains(s)
|| org.Town.ToLower().Contains(s)
|| org.PostCode.ToLower().Contains(s)
|| org.Keywords.ToLower().Contains(s))
orderby searchTerms.Any(s => org.OrgName.ToLower().Contains(s)) ? 1 : 2
select org);
This brings back the required results, however, I would now like to order them so that those records with the keywords in the title are ordered first and then the rest after.
Is this possible without adding some kind of ranking algorithm to the results?
var withRank = orglist
.Select(o =>
new {
Org = o,
Rank = o.OrgName.ToLower().Contains(s)
});
var orderedOrgList = withRank
.OrderBy(o => o.Rank)
.Select(o => o.Org);

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

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);

Clean linq implementation to filter a list by grandchildren

I have a list of Users whom are attached to applications that included clients. I'm looking to filter a list of users by the application and client via Linq and am spinning.
Ideally I'd be using a single statement where Application.Name == "example" that are also in ClientApp.Id == 1.
This is where I'm at thus far but am having some internal brain issues regarding nesting. Any help is appreciated
var users2 = users.Where(x => x.App.Select(y => y.Name).Contains("example"));
public class User
{
public string FirstName { get; set; }
public List<Application> App { get; set; }
}
public class Application
{
public string Name { get; set; }
public List<ClientApp> Client { get; set; }
}
public class ClientApp
{
public string Id { get; set; }
}
You can use nested calls to Enumerable.Any to filter this:
var filtered = users.Where(u =>
u.App.Any(
a => a.Name == "example"
&& a.Client.Any(c => c.Id == 1)));

Categories