PredicateBuilder with navigation properties and multiple conditions - linq

I'm using the PredicateBuilder in the LinqKit to dynamically form the Where expression. When either LastName or FirstName from the SearchCriteria is populated then the SQL statement works correctly. However when both FirstName and LastName are used to search for ConsumerAccount then the generated SQL (as shown below) will not bring back the correct number of rows.
SELECT distinct [c].[consumerAccountId]
FROM [dbo].[ConsumerAccount] AS [c]
INNER JOIN [ConsumerAccountOwner] AS [c4] on [c].[ConsumerAccountId] = [c4].[ConsumerAccountId]
INNER JOIN [Consumer] AS [c5] ON [c4].[ConsumerId] = [c5].[ConsumerId]
WHERE EXISTS (
SELECT 1
FROM [consumeraccountowner] AS [c0]
INNER JOIN [consumer] AS [c1] ON [c0].[consumerId] = [c1].[consumerId]
WHERE [c].[consumerAccountId] = [c0].[consumerAccountId] AND [c1].[FirstName]) = 'fName') AND EXISTS (
SELECT 1
FROM [ConsumerAccountOwner] AS [c2]
INNER JOIN [Consumer] AS [c3] ON [c2].[ConsumerId] = [c3].[ConsumerId]
WHERE [c].[ConsumerAccountId] = [c2].[ConsumerAccountId] AND [c3].[LastName]) = 'lName')
Is there a way to get it to generate the WHERE clause with both conditions for LastName and FirstName as follows?
WHERE EXISTS (
SELECT 1
FROM [consumeraccountowner] AS [c0]
INNER JOIN [consumer] AS [c1] ON [c0].[consumerId] = [c1].[consumerId]
WHERE [c].[consumerAccountId] = [c0].[consumerAccountId] AND [c1].[FirstName]) = 'fName' AND [c1].[LastName]) = 'lName')
public static void Main()
{
var searchCriteria = new SearchCriteria();
searchCriteria.SearchLastName = "LastName1";
searchCriteria.SearchFirstName = "FirstName1";
var predicate = PredicateBuilder.New<ConsumerAccount>();
if (!string.IsNullOrEmpty(searchCriteria.SearchLastName))
{
predicate = predicate.And(ca => ca.Owners.Any(o => o.Consumer.LastName.Equals(searchCriteria.SearchLastName)));
}
if (!string.IsNullOrEmpty(searchCriteria.SearchFirstName))
{
predicate = predicate.And(ca => ca.Owners.Any(o => o.Consumer.FirstName.Equals(searchCriteria.SearchFirstName)));
}
var query = (from ca in _dbContext.ConsumerAccounts.AsExpendable().Where(predicate)
join ao in _dbContext.AccountOwners on ca.Id equals ao.Id
join c in _dbContext.Consumers on ao.ConsumerId equals c.ConsumerId
select ca.Id).Distinct();
var accountCount = query.Count();
}
public class SearchCriteria
{
public string SearchLastName { get; set; }
public string SearchFirstName { get; set; }
}
public class Consumer
{
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public List<ConsumerAccount> Accounts { get; set; }
}
public class ConsumerAccount
{
public int Id { get; set; }
public int ConsumerId { get; set; }
public List<AccountOwner> Owners { get; set; }
}
public class AccountOwner
{
public int Id { get; set; }
public int ConsumerId { get; set; }
public int ConsumerAccountId { get; set; }
public Consumer Consumer { get; set; }
}
I have tried to join the tables in different ways but could not to get it to work for all scenarios.

You need an if statement for the case when both inputs are non-empty, and then move the conjunction into the any, so that both conditions are for the same person:
if (!string.IsNullOrEmpty(searchCriteria.SearchLastName) && !string.IsNullOrEmpty(searchCriteris.SearchFirstName))
{
predicate = predicate.And(ca => ca.Owners.Any(o => o.Consumer.LastName.Equals(searchCriteria.SearchLastName)) && o.Consumer.FirstName.Equals(searchCriteria.SearchFirstName)));
}

If you need a large amount of conditions filtering on different parts of the entity graph, you probably want to build multiple dynamic predicates for each subgraph filter. E.g the owner filters should go in an owner specific predicate, so that they all apply to one owner, and any filters on the account should be added to a separate predicate.

Related

How to select a nested object with EF Core and Linq?

Hope someone can help me
I've some tables linked Table1-->Table2-->Table3-->Table4
The relationship is one to many (a record of Table1 has many records of Table2, each one of them has many records of Table3.......).
I have an Id of the Table1 and I need to get the data set of any property of Table4 (only these data). I can use Include and ThenInclude in LINQ to reach the last table, but... how could I select only these data?
I've a code like this one:
_databaseContext.Table1.Where(t1 => t1.Id == id)
.Include(t1 => t1.Table2Nav)
.ThenInclude(t2 => t2.Table3Nav)
.ThenInclude(t3 => t3.Table4Nav)
.ToList();
This returns to me the complete structure from the first table, but, how could I do a select of specific properties of Table4 (let's suposse that Table4 has a property named "Result" and I need recover a list of all values of "Result" I can reach from the Id of Table1.
Thank's in advance
UPDATE: Here's an example of the structure of the classes:
public class Table1
{
public int Id { get; set; }
public List<Tables12> Tables12Nav { get; set; }
}
public class Tables12
{
public int Id { get; set; }
public Table1 Table1Nav { get; set; }
public Table2 Table2Nav { get; set; }
}
public class Table2
{
public int Id { get; set; }
public List<Tables12> Tables12Nav { get; set; }
public List<Table3> Table3Nav { get; set; }
}
public class Table3
{
public int Id { get; set; }
public Table2 Table2Nav { get; set; }
public List<Table4> Table4Nav { get; set; }
}
public class Table4
{
public int Id { get; set; }
public Table3 Table3Nav { get; set; }
public string Result { get; set; }
}
You could use link to sql to get your data :
var TableD = from a in db.TableA
join b in db.TableB on a.Id equals b.TableAId
join c in db.TableC on b.Id equals c.TableBId
join d in db.TableD on c.Id equals d.TableCId
where a.Id == 2
select d;
if you'd like use your code you can retrieve table 4 (table d) as :
var TableD = TableTemp.SelectMany(a => a.TableB.SelectMany(
b => b.TableC.SelectMany(
c => c.TableD
)));

linq query that joins results from three other queries

I am trying to join results from three linq queries. The types are as follows:
orderItems is a List<OrderLineItem>
transitTimes is a Dictionary<int,int> where Key = SiteId and Value = TransitDays
shippingPriority is a Dictionary<int,int> where Key = DefaultPriority and Value = SiteId
public class OrderLineItem
{
public decimal OrderLineWorkId { get; set; }
public string Sku { get; set; }
public string SiteId { get; set; }
public char FlagSlapType { get; set; }
public char FlagTruck { get; set; }
public string SkuType { get; set; }
public int QtyOrdered { get; set; }
public int QtySellable { get; set; }
}
public class OrderAllocation
{
public int SiteId { get; set; }
public int TransitDays { get; set; }
public int QtyItemsInStock { get; set; }
public int QtyParcelInStock { get; set; }
public int DefaultPriority { get; set; }
}
Here is my linq query, but it always returns 0 results. Not sure where I am going wrong?
var results = (from i in orderItems
join t in transitTimes on i.SiteId equals t.Key.ToString()
join d in shippingPriority on t.Key equals d.Value
group i by i.SiteId into g
select new OrderAllocation()
{
SiteId = Convert.ToInt32(g.Key),
TransitDays = transitTimes.Select(x => x.Value).FirstOrDefault(),
QtyItemsInStock = g.Count(e => e.QtySellable >= e.QtyOrdered),
QtyParcelInStock = g.Count(e => e.QtySellable >= e.QtyOrdered && e.FlagSlapType != 'Y'),
DefaultPriority = defaultShippingPriority.Select(x => x.Key).FirstOrDefault()
}).OrderBy(x => x.SiteId).ToList();
UPDATE
I found what was wrong, the SiteId of type string had a values with a prefix 0 (ie 01, 02, etc) and therefore would not join with values like 1, 2, etc. Changed type from string to int and am now getting results. Sorry for the false alarm.
The issue may be related to differences in the data types:
(from i in orderItems
join t in transitTimes on i.SiteId equals t.Key.ToString()
orderItems defines SiteId as an string
public string SiteId { get; set; }
while transitTimes has a key of int
Dictionary<int,int>
The join condition of int to string may not provide the results you are expecting.

Performing a join in LINQ using SQL Syntax

I'm currently learning the Entity Framework and am trying to build an enrollment system. Right now I'm trying to display the courses that a student is enrolled in by using an association table called "EnrollmentModel". Normally if this were SQL my query would be something like:
SELECT *
FROM EnrollmentModel as e, StudentModel as s, CourseModel as c
WHERE s.ID = e.StudentID AND e.CourseID = c.ID
So is there a way to just do that using LINQ? Or do I have to 'translate' the syntax from SQL to LINQ (which I have no idea how to do).
My Schema:
public class StudentModel
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<EnrollmentModel> Enrollments {get; set; }
}
public class EnrollmentModel
{
public int ID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual StudentModel Students { get; set; }
public virtual CourseModel Courses { get; set; }
}
public class CourseModel
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<EnrollmentModel> Enrollments { get; set; }
}
If you change the SQL query to explicit join syntax;
SELECT *
FROM EnrollmentModel as e
JOIN StudentModel AS s ON e.StudentID = s.ID
JOIN CourseModel AS c ON e.CourseID = c.ID
...it's quite straight forward to translate to LINQ into an anonymous type;
var result =
from e in db.EnrollmentModel
join s in db.StudentModel on e.StudentID equals s.ID
join c in db.CourseModel on e.CourseID equals c.ID
select new {e,s,c};

How to apply Nested Select using Linq

I'm working with nested select and seems like what i am doing is not correct!.
I'm trying to retrieve all the Topics items.
public class Employee
{
public int Id{ get; set; }
//....other fields....
//......
public IList<Topics> Interest { get; set; }
}
public class Topics
{
public int Id { get; set; } ;
public string Name { get; set; } ;
//other fields
}
employeeItems = (from _emp in employees
select new Employee
{
EmpId = _emp.mediaId,
EmpName = _emp.mediaType,
......................
Interest = (from _emp1 in employees.Interest //has few rows
select new Topic
{
Id = _emp1.Topics[0].Id, //.<int>("id"), <<<ERROR
Name = _emp1.Topics[0].Name //["name"] <<<ERROR
}).ToList()
}).ToList();
}
Interest = (from topic in _emp.Interest.SelectMany(i=>i.Topic) //has few rows
select new Topic
{
Id = topic.Id, //.<int>("id"), <<<ERROR
Name = topic.Name //["name"] <<<ERROR
})
public class Employee
{
public int Id{ get; set; }
//....other fields....
//......
public IEnumberable<Topics> Interest { get; set; }
}
Leave the property as IEnumberable and don't do .ToList() inside the query. And it seems to me your data structure is employee has multiple interests and each interest has mulitple topics, that's why I used selectmany, but you can adjust it if I'm wrong with the data.

How to get Users who only have some specific department id, Linq

I have following two entities
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
public List<Department> Departments { get; set; }
}
public class Department
{
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public List<User> Users { get; set; }
}
As you see, relationship between two objects is M:N.
I wanna get User who only have specific department ID, in this case, How to get users using Linq?
Thanks in advance
int requiredId = ...
var usersInReqdDept = Users.Where(u => u.Departments
.Any(d => d.DepartmentId == requiredId));
If the Departments list can be null, you will need a null-check in the Where clause.
If you want to search the Departments list instead,
int requiredId = ...
var usersInReqdDept = Departments.Single(d => d.DepartmentId == requiredId)
.Users;
Of course, this will throw an exception if such a department doesn't exist.

Resources