Sorting collection based on keywords with Linq - linq

I've created an application were users search through organisations.
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 can enter multiple keywords. I split the keywords into an array:
string[] searchTerms = keywordphrase.Split(new[] { ' ' },StringSplitOptions.RemoveEmptyEntries);
and search the model as follows:
orglist = _UoW.OrganisationRepo.All();
orglist = (from org in orglist
where searchTerms
.All(s => org.OrgName.ToLower().Contains(s.ToLower()) ||
org.OverviewOfServices.ToLower().Contains(s.ToLower()) ||
org.ContactName.ToLower().Contains(s.ToLower()) ||
org.Address1.ToLower().Contains(s.ToLower()) ||
org.Town.ToLower().Contains(s.ToLower()) ||
org.PostCode.ToLower().Contains(s.ToLower()) ||
org.Keywords.ToLower().Contains(s.ToLower()))
select org);
I now have a list of those organisations that contain the keywords in any of the fields specified.
I want to order the results so that the organisations with any/all of the keywords listed in the OrgName come first (by relevance), then the organisations where the keywords are not listed in the OrgName come after.
The following lists orgs based on the indexof and only if I use the keywordphrase (not each keyword):
orglist = orglist.OrderBy(m => m.OrgName.StartsWith(keywordphrase))
But this is not really suitable.
Is there a way to order these so the organisations containing any of the keywords entered come first in the results collections, thenby the rest of the records, i.e.where the keywords are not in the OrgName.

I believe you should be able to use a conditional statement in the orderby phrase to control sorting:
orglist = (from org in orglist
where searchTerms
.All(s => org.OrgName.ToLower().Contains(s.ToLower()) ||
org.OverviewOfServices.ToLower().Contains(s.ToLower()) ||
org.ContactName.ToLower().Contains(s.ToLower()) ||
org.Address1.ToLower().Contains(s.ToLower()) ||
org.Town.ToLower().Contains(s.ToLower()) ||
org.PostCode.ToLower().Contains(s.ToLower()) ||
org.Keywords.ToLower().Contains(s.ToLower()))
orderby searchTerms.Any(s => org.OrgName.Contains(s)) ? 1 : 2
select org);

Related

Correct interpretation of SQL request by EF Core

I have a certain table in the database that stores the following objects:
public partial class Invoice
{
public string DocumentNumber { get; set; }
public DateTime? DocumentDate { get; set; }
public string DocumentReference { get; set; }
public string SerialNumber { get; set; }
public string ProductCode { get; set; }
public string Description { get; set; }
public string Certificate { get; set; }
public string Language { get; set; }
public string Email { get; set; }
}
I also have a query that returns me the number of specific elements:
SELECT Count(*)
FROM (
SELECT DocumentNumber,DocumentDate,DocumentReference
FROM vInvoiceSwivelInfoWeb
WHERE Email = 'someemail#gmail.com' AND Language = 'FR'
GROUP BY DocumentNumber,DocumentDate,DocumentReference
) AS T
The answer looks something like this:
How to use EF to make such a request and get a numerical answer?
I tried like this:
_context.Database.ExecuteSqlRawAsync($"..some SQL query..")
but I do not get the expected result.
UPD: Having received the answer about the impossibility of fulfilling this request through EF, the following question reasonably arose: Is it possible to make this request using LINQ?
You can Leverage ADO.NET via the Context.Database property.
Unfortunately, there is no way to get the count from the database using EF Core execute methods if you have a custom query that is not related to your entities.
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "SELECT Count(*) From Table1";
context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
// do something with result
}
}
for Updated question
var count = from a in _context.vInvoiceSwivelInfoWeb
where a.Email == "someemail#gmail.com" && a.Language == "FR"
group new { a.DocumentNumber , a.DocumentReference , a.DocumentDate } by a into g
select g.Count()
also, it's important to know which version of EF-Core are you using:
currently, if you are using EF-Core 3 group-by doesn't translate to SQL command so you have to do it on client-side:
check this link :
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client
for EF-Core 3.0 - 3.1.1
var count = _context.vInvoiceSwivelInfoWeb
.Where(a => a.Email == "someemail#gmail.com" && a.Language == "FR" ).ToList()
.GroupBy(a => new { a.DocumentNumber ,a.DocumentDate, a.DocumentReference }).Count();

How to Return class with list using LINQ in C#?

I have one "Profiles" and "Addresses" table Profiles table contain one record of person and Addresses table contains its multiple address.
I want to write LINQ query to return single person record with its multiple address in one Query. So I have created one custom class with "profileId" ,"firstName" and list of address.
I need a output like this please help.
1 (profileId)
Mark (firstName)
------Sanday (address one)
------Stronsay (address two)
------Foula (address three)
public class profileDetails
{
public int profileIdData { get; set; }
public string firstNameData { get; set; }
public List<lstAddress> lstAddressData { get; set; }
}
public class lstAddress
{
public int addressId { get; set; }
public string address { get; set; }
}
//This is my query
var result = (from profile in DbContext.Profiles
join address in DbContext.Addresses
on profile.profileId equals address.addressId
select new profileDetails()
{
profileIdData = profile.profileId,
firstNameData = profile.firstName,
lstAddressData = new { address.addressId, address.address }.toList()
}
)
Have you tried using the Include method? Something like this:
var result = (from profile in DbContext.Profiles
join address in DbContext.Addresses
on profile.profileId equals address.addressId).Include("lstAddressData");
(the lstAddressData name may be incorrect in my example above, it refers to the reference property you want to populate with address items)

Count the occurrences of an object in a list where multiple criteria are matching using LINQ

I have a list of Cutdetails. I am trying to write a function using LINQ that will return the count of bars in the list where the CODE , BRAND, CODE and LENGTH all match. I want to be able to specify all these parameters and return a number for the number of matches.
I have tried using foreach statements which is fine but i'm sure there is an neater and smarter way to do it using LINQ. Any suggestions?
List<Bar> bars = new List<Bar>();
public class Bar
{
public string Brand { set; get; }
public string System { set; get; }
public string Code { set; get; }
public string Length { set; get; }
}
Thanks in advance!
Will
You can filter using the match and then do a count.
var occurences = bars.Where(x => x.Brand == "Brand" && x.Code == "code").Count();

LINQ search Product Index

I have a List<Product> contains properties of Bikes (Name, ProductName, Color, List Price). I'm struggling to figure out how to write a search function using LINQ. I'd like to find a name of Bike. Any suggest will be help me some ways.
Imagine that your name is taken from a variable called nameToSearch.
This is if you want to get the Product.
string nameToSearch = "BikeName";
List<Product> list = bikes.Where(x => x.Name == nameToSearch).ToList();
I assume you have the following Product class:
public class Product
{
public String Name { get; set; }
public String ProductName { get; set; }
public String Color { get; set; }
public String List { get; set; }
public String Price { get; set; }
}
You also mentioned you have your data in a List<Product>. I will give a demo name for it:
List<Product> myProductList = GetProductList();
// Where GetProductList() will create a new List<Product> and populate it.
String bikeNameFilter = GetNameFilter();
// You can chnage this by the string you want for filtering.
You can use the following to get your data:
List<Product> myFilteredProductList = (from p in myProductList
where p.Name = bikeNameFilter
select p;
).ToList()
Obviously you can change the filter you want to use to another property of your product. Finally to get the actual name, you can loop through the list you just got:
foreach (var p in myFilteredProductList)
{
Console.WriteLine(p.ProductName);
// Use this value wherever you want.
}
Take a look at a nuget package I have created
http://www.nuget.org/packages/NinjaNye.SearchExtensions
This will enable the following (and more) which will return results where the search term appears in any of the properties specified
var result = products.Search("searchTerm", p => p.Name, p => p.ProductName);
Performing a search against all string properties can be done as follows:
var result = products.Search("searchTerm");
Alternatively, you can perform an AND search where the search term exists in a set of properties as follows:
string searchTerm = "searchTerm";
var result = products.Search(searchTerm, p => p.Name)
.Search(searchTerm, p => p.ProductName);
For more information take a look at the projects GitHub page or my blog posts
UPDATE: don't forget the using directive...
using NinjaNye.SearchExtensions

Join LINQ queries from multiple contexts

I am trying to create a ViewModel for data that I want to display in my view. The issue is, the data being displayed is spread across 2 databases and multiple tables within each. I've read that you cannot join Linq queries across multiple contexts, which makes sense, and I've also read that we can't use Code-First in EF5 to use Stored Procedures....which led me to using 3 different Linq queries and attempt to combine them into 1 ViewModel...I'm just not sure how to get there.
Here's my Linq queries:
var csdContext = new CSDContext(CustomerCode);
var masterContext = new MasterContext();
//Only returns 1 row - which is what we want.
List<Site> sites = (from s in csdContext.Sites
join sa in csdContext.SiteAddresses
on s.SiteID equals sa.SiteID
join a in csdContext.Addresses
on sa.AddressID equals a.AddressID
join spv in csdContext.SiteProductVersions
on s.SiteID equals spv.ProductVersionID
where s.SiteID == id
select s).ToList();
//List
List<States> states = (from s in masterContext.StatesTable
select s).ToList();
My ViewModel looks like this:
public class SiteDetailsViewModel
{
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string StateCode { get; set; }
public string ZipCode { get; set; }
public string OfficePhone { get; set; }
public string MobilePhone { get; set; }
public string AlternativePhone { get; set; }
public int ProductVersionID { get; set; }
}
Basically, I need the following data from these tables:
csdContext - Address
Address
Address2
City
ZipCode
csdContext - Sites
OfficePhone
MobilePhone
AlternativePhone
csdContext - SiteProductVersions
ProductVersionID
masterContext - States
StateCode
Here's how the tables are joined in SQL:
SELECT csd_a.Address, csd_a.Address2, csd_a.City, mstr_st.StateCode, csd_a.ZipCode, csd_s.OfficePhone, csd_s.MobilePhone,
csd_s.AlternativePhone, csd_spv.ProductVersionID
FROM CSD.dbo.Sites AS csd_s
INNER JOIN CSD.dbo.SiteAddress AS csd_sa ON csd_sa.SiteID = csd_s.SiteID
INNER JOIN CSD.dbo.Address AS csd_a ON csd_a.AddressID = csd_sa.AddressID
INNER JOIN CSD.dbo.SiteProductVersions AS csd_spv ON csd_s.SiteID = csd_spv.SiteID
INNER JOIN MasterDB.dbo.States AS mstr_st ON mstr_st.StateID = csd_a.StateID
I can't figure out how to merge these 3 results to create the ViewModel data for SiteDetailsViewModel. Can anyone help?
If you materialize your queries as described in OP you can use this:
var query = sites.Join(
states,
si => si.StateID,
st => st.StateID,
(si, st) => new SiteDetailsViewModel
{
Address = si.Address,
Address2 = si.Address2,
City = si.City,
StateCode = st.StateCode,
ZipCode = si.ZipCode,
OfficePhone = si.OfficePhone,
MobilePhone = si.MobilePhone,
AlternativePhone = si.AlternativePhone,
ProductVersionID = siProductVersionID
});
One possible alternate solution is to create views in DB #2 of the tables in DB #1. Then you can model/map those views in EF for DB #2. You would of course still need a context for DB #1 if you need to update any of the tables. But the benefit of this solution is that you can do all of your read access on a single context and get joins at the server instead of in memory.
So why don't you write it like this:
from ...
...
where s.SiteID == id
select new SiteDetailsViewModel()
{
Address = sa.Address,
Address2 = as.Address2,
..
}

Resources