C# search using a LINQ query with child records - entity-framework

I have a Person and PersonType object where a Person has one to many PersonTypes. A PersonType can be Accountant, Consultant etc.
Person table has the following columns:
'firstname' | 'lastname' | 'email' | 'phonenumber'
PersonType table has the following columns:
'name' | 'description'
I've created a search mechanism using LINQ in C# but the problem is that I get a wrong result due to an issue with a LINQ constraint. Suppose we have two Person records where the first record is a Consultant and the other record is Consultant and Accountant meaning it has to child objects.
Note. My search is case insensitive.
Suppose my search text is 'co'. Then I don't want the result to be 3 records. Now it returns the first person and two instances of the second person due to two matches of 'co' for Accountant and Consultant for the second Person.
Of course I can go through the result and filter out duplicates but it would be nice to have it one query.
Many thanks for your input.
Here is the Linq:
private IList<PersonViewModel> SearchAll(string searchCriteria)
{
var result = System.Web.HttpContext.Current.Session["Persons"] as IList<PersonViewModel>;
if (result != null)
{
var v = (from a in result
from b in a.PersonTypes
where
a.FirstName.CaseInsensitiveContains(searchCriteria) ||
a.LastName.CaseInsensitiveContains(searchCriteria) ||
a.Email.CaseInsensitiveContains(searchCriteria) ||
a.PhoneNumber.CaseInsensitiveContains(searchCriteria) ||
b.Name.CaseInsensitiveContains(searchCriteria)
select a);
return v.ToList();
}
return result;
}

One little strategy may be very helpful in problems with queries and search conditions:
Only query the entities you need and do nothing but where.
You need PersonViewModels. By joining in PersonTypes (from - from is a GroupJoin) also PersonTypes become accessible to the end result. So if you only query the entities you need, there should be one, and only one, from:
var v = from a in result
Now what should the where be? The conditions for PersonViewModel are clear, but what about PersonTypes? Loosely defined in plain language, where at least one of their PersonTypes contains "co". In LINQ that amounts to:
var v = from a in result
where a.FirstName.CaseInsensitiveContains(searchCriteria)
|| a.LastName.CaseInsensitiveContains(searchCriteria)
|| a.Email.CaseInsensitiveContains(searchCriteria)
|| a.PhoneNumber.CaseInsensitiveContains(searchCriteria)
|| a.PersonTypes.Any(pt => pt.Name.CaseInsensitiveContains(searchCriteria));
Now you don't need Distinct because there's no join that duplicates results.

you can get result as following
var v=result?.Where(p=>p.PersonTypes.Select(t=>t.Name).Contains(searchCriteria) ||
p.FirstName.CaseInsensitiveContains(searchCriteria) ||
p.LastName.CaseInsensitiveContains(searchCriteria) ||
p.Email.CaseInsensitiveContains(searchCriteria) ||
p.PhoneNumber.CaseInsensitiveContains(searchCriteria) ||)?.ToList();

Salam
i think you can do it like that
v.GroupBy(person => person.id)
.Select(g => g.First())
.ToList();

Related

Entity Framework, Linq : concatenating results from child table

I have an existing linq query which gets some data into a view model object. This is working fine.
I want to add a new property for data from child table which will have column values from a child table in a comma separated string format.
Problem: I am not able to concatenate the results using string.join
Simplified version of tables showing only relevant fields
part
id
part number
1
ABC1
2
DEF1
vendor
id
vendorname
1
acme
2
john
vendor part name (vendor specific part number)
partid
vendorid
partname
1
1
GDSE-553-32
1
2
JWWVV-HH-01
simplified version of query
result = (from p in DBContext.Parts.Where(w => w.EquipmentId == eId)
select new PartModel
{
Id = p.Id,
Number = p.PartNumber,
VendorPartNames= String.Join(",", DBContext.VendorPartName.Where(w => w.PartId == p.Id).Select(s => s.PartName))//this line causes exception (shown below)
});
Exception:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.String[])' method, and this method cannot be translated into a store expression.
Please note: the actual query has some joins and other columns, so please dont suggest solutions that requires joins.
If I change the "VendorPartName" to a List type , I can get the results without any problems.
My only problem is in "How to convert the results for "VendorPartName" property to a comma separated strings?"
eg: based on sample table data provided, it should be
GDSE-553-32, JWWVV-HH-01
Entity Framework does not support String.Join() method.
So, what we can do is to fetch VendorPartNames as a string collection and then we can later separate it with ,.
Note: For this, we would first use an anonymous object and later convert it to PartModel.
So your query would look like this:
var parts = DBContext.Parts
.Where(w => w.EquipmentId == eId)
.Select(p => new {
Id = p.Id,
Number = p.PartNumber,
VendorPartNames = p.VendorPartName.Select(n => n.PartName)
}).ToList();
var result = parts.Select(i => new PartModel {
Id = i.Id,
Number = i.Number,
VendorPartNames = String.Join(",", i.VendorPartNames)
}).ToList();

Linq with Included Subquery is failing on EF Core

I am using MySQL DB with ef core all works fine but the following query does nto return expected result.
var query = _context.ServiceData.
Include(x => x.Country)
.Include(x=>x.Country.CountryLocale)
.Where(x=>x.Country.CountryLocale.Any(l=>l.Locale == "en-US"));
After executing and doing
query.First().Country.CountryLocale.Count // Returns count of greater than 1 when expected count is 1
The table only has 2 entries and the above pulls both when only 1 is expected.
Table Layout
Id Name Locale
1 Test en-US
2 Test en-GB
All examples I have seen seem to suggest doing it how I am doing it so not sure what I am missing.
Here is the sql generated by above Linq
SELECT `a`.`Id`, `a`.`ServiceDataCode`, `a`.`CountryId`, `a`.`Enabled`, `a`.`LastUpdated`, `a`.`TimezoneId`, `c`.`Id`, `c`.`DialingCode`, `c`.`Enabled`, `c`.`IsoNumeric`, `c`.`IsoThreeLetterCode`, `c`.`IsoTwoLetterCode`, `c`.`LastUpdated`, `c0`.`Id`, `c0`.`IsoTwoLetterCode`, `c0`.`LastUpdated`, `c0`.`Locale`, `c0`.`Name`
FROM `ServiceData` AS `a`
INNER JOIN `CountryData` AS `c` ON `a`.`CountryId` = `c`.`Id`
LEFT JOIN `CountryLocale` AS `c0` ON `c`.`IsoTwoLetterCode` = `c0`.`IsoTwoLetterCode`
WHERE EXISTS (
SELECT 1
FROM `CountryLocale` AS `c1`
WHERE (`c`.`IsoTwoLetterCode` = `c1`.`IsoTwoLetterCode`) AND (`c1`.`Locale` = 'en-US'))
ORDER BY `a`.`Id`, `c`.`Id`, `c0`.`Id`
I guess a stored procedure is another option but wanted to do it without.
Well, here is an object query with a right paths of country and specific country locale that belong to first service data:
using (var query = _context.ServiceData
.Include("Country.CountryLocale")
.Where(x => x.ServiceData.CountryId == x.Country.Id)
.Where(x => x.Country.IsoTwoLetterCode == x.Country.CountryLocale.IsoTwoLetterCode)
.Where(x => x.Country.CountryLocale.Locale == "en-US"))
{
query.FirstOrDefault();
}
Hope this helps.

Entity Framework: How do I invoke pairs of entities from a raw query

For instance, I have a query:
SELECT * FROM
persons
LEFT JOIN vehicles
ON persons.Id = vehicles.OwnerId
I would like execute this query on an EF data context and have array of pairs "person-vehicle". how do I do it?
Another example:
SELECT persons.*, COUNT(vehicles.*) as cnt FROM
persons
JOIN vehicles
ON persons.Id = vehicles.OwnerId
GROUP BY vehicles.Id
Here I want to have a dictionary of a person as a key and number of vehicles he owns as a value.
I know that these quesies are simple enough and it's better to avoid raw sql in these cases. But I want to know possibilities of raw query handling, because real life queries can be much more complex.
You probably want to do some reading ion LINQ to Entities. https://msdn.microsoft.com/en-us/library/vstudio/bb386964(v=vs.100).aspx
The first one is pretty basic:
var persons = context.Persons
.Include(p => p.Vehicles)
.ToList();
The second one is a little more advanced:
var persons = context.Persons
.Select(p => new { Person p, VehicleCount = p.Vehicles.Count() }
.ToList();
You could also do a group by which is described in the link.

Linq to Entities Select clause with lambda

I am working on a new project and we are using Entity Framework and the dev lead would like to use lambda queries whenever possible. One thing we are having a hard time figuring out is how to select two columns specifically. Also how to select distinct. We have a table that has multiple entries for a vendor but we want to just get a list of vendors and load to a dictionary object. It fails because as written it is trying to add a key value that has already been added. Take the following query.
Dictionary<int, string> dict = new Dictionary<int, string>();
dict = GetWamVendorInfo().AsEnumerable()
.Where(x => x.vendor_name != null && x.vendor_id != null)
//.Select(x => x.vendor_id).Distinct()
.Take(2)
.ToDictionary(o => int.Parse(o.vendor_id.ToString()), o => o.vendor_name);
What I would like to do is select just vendor_id and vendor_name so we can get just the distinct records.
Any help would be greatly appreciated.
Thanks,
Rhonda
Use an anonymous type:
// earlier bit of query
.Select(x => new { VendorId = x.vendor_id, VendorName = x.vendor_name } )
.Distinct()
.ToDictionary(o => o.VendorId, o => o.VendorName);
I've removed the call to Take(2) as it wasn't clear why you'd want it - and also removed the parsing of VendorId, which I would have expected to already be an integer type.
Note that you should almost certainly remove the AsEnumerable call from your query - currently you'll be fetching all the vendors and filtering with LINQ to Objects. There's also no point creating an empty dictionary and then ignoring it entirely. I suspect your complete query should be:
var vendors = GetWamVendorInfo()
.Select(x => new { VendorId = x.vendor_id,
VendorName = x.vendor_name } )
.Distinct()
.ToDictionary(o => o.VendorId,
o => o.VendorName);
As an aside, you should ask your dev lead why he wants to use lambda expressions (presumably as opposed to query expressions) everywhere. Different situations end up with more readable code using different syntax options - it's worth being flexible on this front.
Just use an anonymous object:
var vendors = GetWamVendorInfo().AsEnumerable()
.Where(x => x.vendor_name != null && x.vendor_id != null)
.Select(new {x.vendor_id, x.vendor_name})
.Take(2)
That's it. You can now work with vendors[0].vendor_id, vendors[0].vendor_name, and so on.

Dynamic query with Linq To Entities

I've got an EDM with two tables Product and ProductCategory with a many-to-many relationship between both.
What I'm currently trying to do is to build a dynamic query to select the products related to the categories the user has selected through the UI.
In short I should build a query like the following but based one or more categories that I don't know at compile time.
var productQuery = context.Product.Where
(p => p.ProductCategories.Any(c => c.CategoryId == id1 ||
c.CategoryId == id2 || ...));
I've read a lot of things and I'm actually very new to linq so I really don't know where to start.
What is the best approach to do such queries ?
Thank you for your time.
var ids = new [] { id1, id2, // ...
var productQuery = context.Product.Where(
p => p.ProductCategories.Any(c => ids.Contains(c.CategoryId)));