How to convert SQL subquery to linq lambda? - c#

I have following SQL subqueries and want to convert them to linq lambda.
Count:
select count(*)
from ParentA
where orderStatus in ('Status1','Status2')
and orderNumer in (select orderNumber
from ChildB
where approval = 0)
Entities:
public class ParentA
{
public long ProductID { get; set; }
public string ProductName { get; set; }
public ChildB Order { get; set; }
public string orderStatus { get; set; }
public long orderNumer { get; set; }
}
public class ChildB
{
public long Id { get; set; }
public string Name { get; set; }
public long orderNumer { get; set; }
public bool approval { get; set;}
}
All columns:
select *
from ParentA
where orderStatus = 'Status1'
and orderNumer in (select orderNumber
from ChildB
where approval = 1)
Eg:
var query =_dbcontext.ParentA.Include().Where().Count();

After looking at your posted classes, it looks more like ChildB is the parent and ParentA is the child, with one ChildB per ParentA.
The following should be the method LINQ equivalents:
var result1 = db.ParentA
.Where(a =>
(a.orderStatus == "Status1" || a.orderStatus == "Status2")
&& !a.ChildB.approval
)
.Count();
var result2 = db.ParentA
.Where(a =>
(a.orderStatus == "Status1")
&& a.ChildB.approval
)
.ToList();
If you ChildB class has a ParentA collection not shown in your post, this could also be written as:
var result1 = db.ChildB
.Where(b => !b.approval)
.SelectMany(b => b.ParentA
.Where(a => a.orderStatus == "Status1" || a.orderStatus == "Status2")
)
.Distinct()
.Count();
var result2 = db.ChildB
.Where(b => b.approval)
.SelectMany(b => b.ParentA
.Where(a => a.orderStatus == "Status1")
)
.Distinct()
.ToList();

Here's a rough take of what you're looking for.
// Create a collection of some form with your statuses you want to check.
var statuses = new string[] {"Status1", "Status2"};
// Get your order numbers for the 'orderNumber in' portion of your query.
var orderNumbers = _dbcontext.ChildB.Where(x => x.approval == 0);
// Get the count using these collections.
var count = _dbcontext.ParentA.Where(x => statuses.Contains(x.orderStatus) && orderNumbers.Contains(x.orderNumber)).Count();
// Your 'All columns' query is slightly simpler, if you know you only need to filter by the one status instead of multiple...
var approvedOrderNumbers = _dbcontext.ChildB.Where(x => x.approval == 1);
var results = _dbcontext.ParentA.Where(x => x.orderStatus == "Status1" && approvedOrderNumbers.Contains(x.OrderNumber)).ToList();
Some things to note:
If you have a navigation property between ParentA and ChildB (which any properly designed database/DbContext should have, the LINQ would look different.
LINQ is always a little backwards from how SQL does in. As you can see, first you must build the collection you want to use for your in parameter, and then see if it contains the entity value.
I wrote this code freehand in a text editor, and I never use string arrays so I'm not even sure if that's the proper syntax to initialize one.
Based on the confirmation that you have navigation properties on ParentA, here's an example of how to use those:
var statuses = new string[] {"Status1", "Status2"};
var count = _dbcontext.ParentA.Where(x => statuses.Contains(x.orderStatus) && x.ChildBs.Any(y => y.approval == 0)).Count();
var results = _dbcontext.ParentA.Where(x => x.orderStatus == "Status1" && x.ChildBs.Any(y => y.approval == 1)).ToList();
Some additional notes:
If you were to share your actual goal, such as "Get the count of all the ParentAs where all of their ChildBs are approved," or, "Get all the columns from ParentA where any of their ChildBs are not approved," would allow us to better help you in writing the correct statements.
As it stands, these queries will get you:
A set of ParentA that contain at least one ChildB with approval of 0.
A set of ParentA that contain at least one ChildB with approval of 1.
If that's not what you want, then you need to include in plain English what your result set should look like.

Related

Left outer join using LINQ Query Syntax EF Core C#

I have a question in regards with the below,
Left outer join of two tables who are not connected through Foreign Key.
Order by the results matched in second table.
I would like this to be done in LINQ Query method syntax as I am adding lots of conditions depending on the input provided along with skip and limit.
If we have below Product and Favorite tables
So the output that I would like to have is:
meaning with the favorites as part of first set and which are not favorites should be behind them. Below are the tries that I did.
I am able to join the tables get the output but not sure how I can make sure that in the first page I get all the favs.
This answer was very near to what I thought but it gets the result and then does the ordering which will not be possible in my case as I am doing pagination and using IQueryable to get less data.
Group Join and Orderby while maintaining previous query
Open to any solutions to achieve the same.
[Table("Product")]
public class ProductModel
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ProductId { get; set; }
public string ProductName {get; set;}
public bool IsFavorite { get; set; }
}
[Table("UserFavorite")]
public class UserFavoriteModel
{
[Required]
public Guid UserId { get; set; }
[Required]
public Guid Identifier { get; set; }
[Required]
public FavoriteType Type { get; set; }
}
// Gets products
private async Task<List<ProductModel>> GetProductsAsync(
Guid categoryId,
Guid subCategoryId,
int from,
int limit)
{
var query = _context.Products.AsQueryable();
if (!string.IsNullOrEmpty(categoryId))
query = query.Where(product => product.CategoryId == categoryId);
if (!string.IsNullOrEmpty(subCategoryId))
query = query.Where(product => product.SubCategoryId == subCategoryId);
query = query.Skip(from).Take(limit);
var products = await query.ToListAsync();
query = query.GroupJoin(
_context.Favorites.AsNoTracking()
.Where(favorite => favorite.Type == FavoriteType.FASHION)
// This user Id will come from context just adding for overall picture.
.Where(favorite => favorite.UserId == userId),
//This orderby if I add will not make any difference.
//.OrderByDescending(favorite => favorite.Identifier),
v => v.ProductId,
f => f.Identifier,
(product, fav) => new { product, fav }).
SelectMany(x => x.Fav.DefaultIfEmpty(),
(x, y) => SetFavorite(x.Project, y));
}
private static ProductModel SetFavorite(ProductModel v, UserFavoriteModel si)
{
v.IsFavorite = (si != null);
return v;
}
I would do something like this:
var query =
_context.Products.AsQueryable().Select(p => new ProductModel {
ProductId = p.ProductId,
ProductName = p.ProductName,
IsFavorite =
_context.Favorites.Any(f =>
f.Identifier = p.ProductId &&
f.Type == FavoriteType.FASHION &&
f.UserId == userId
)
}).OrderByDescending(favorite => favorite.Identifier);

Unable to use Contains in LINQ to Entity VS 2017 my query attached

my query is,
Here i am trying to get region id where the region name matches,
var Rid = db.Regions.Where(p => SqlFunctions.PatIndex("%" + Search + "%", p.RegionName) > 0).Select(p => p.RegionId).ToList();
Change Id's comma separated in string,
string joined = string.Join(",", Rid);
Trying to get all records related to Id's comma separated in string joined
var employ = db.Employees.Include(f => f.City).Include(f => f.Region).Where(p=>p.RegionId);
Issue is that we dont have contain when i tried use in where clause ??
Hopes for your suggestions
In Linq we have Any:
var employ = db.Employees.Include(f => f.City)
.Include(f => f.Region)
.Where(p=> Rid.Any(rid => rid == p.RegionId));
or :
var employ = db.Employees.Include(f => f.City)
.Include(f => f.Region)
.Where(p=> Rid.Contains(p.RegionId));
Now it will select those employees have the region id in the Rid list.
You should map your relationships using virtual properties:
public class Employee
{
...
public int RegionId { get; set; }
[ForeignKey("RegionId")]
public virtual Region Region { get; set; }
...
}
public class Region
{
...
[ForeignKey("RegionId")]
public virtual ICollection<Employee> Employees { get; set; }
...
}
Then you can use it directly in your where statement:
var employees = db.Employees
.Where(a => a.Region.RegionName.Contains(Search));
or
var employees = db.Regions
.Where(a => a.RegionName.Contains(Search)
.SelectMany(a => a.Employees);
This way you are sending less data to do your query, think of the situation where have 100 region ids selected. Also you are doing 1 query instead of 2.

Get Table A record if Table B has a match in a search

I have two tables
CREATE TABLE RetailGroup(
Id int IDENTITY(1,1),
GroupName nvarchar(50),
)
CREATE TABLE RetailStore(
Id int IDENTITY(1,1),
StoreName nvarchar(100),
RetailGroupId int
)
Where RetailGroupId in RetailStore is referencing RetailGroup ID. I am trying to create a search function where I can search for both RetailGroup and RetailsStores. If I get a matching RetailStore I want to return the Group it is tied to and the matching Store record. If I get a matching Group, I want the group record but no retail stores.
I tried to do it the following way:
public List<RetailGroup> SearchGroupsAndStores(string value)
{
return _context.RetailGroups.Where(group => group.GroupName.Contains(value)).Include(group => group.RetailStores.Where(store => store.StoreName.Contains(value))).ToList();
}
But this is wrong because include should not be used for selection.
Here is my entity framework model for groups
public class RetailGroup
{
[Key]
public int Id { set; get; }
[MaxLength(50)]
public String GroupName { set; get; }
//Relations
public ICollection<RetailStore> RetailStores { set; get; }
}
And here is the one for the store
public class RetailStore
{
[Key]
public int Id { get; set; }
[MaxLength(100)]
public string StoreName { get; set; }
[ForeignKey("RetailGroup")]
public int RetailGroupId { get; set; }
//Relations
public RetailGroup RetailGroup { get; set; }
public ICollection<EGPLicense> Licenses { get; set; }
}
How do I create my LINQ to get the results I am looking for ?
The query returning the desired result with projection is not hard:
var dbQuery = _context.RetailGroups
.Select(rg => new
{
Group = rg,
Stores = rg.RetailStores.Where(rs => rs.StoreName.Contains(value))
})
.Where(r => r.Group.GroupName.Contains(value) || r.Stores.Any());
The problem is that you want the result to be contained in the entity class, and EF6 neither supports projecting to entity types nor filtered includes.
To overcome these limitations, you can switch to LINQ to Objects context by using AsEnumerable() method, which at that point will effectively execute the database query, and then use delegate block to extract the entity instance from the anonymous type projection, bind the filtered collection to it and return it:
var result = dbQuery
.AsEnumerable()
.Select(r =>
{
r.Group.RetailStores = r.Stores.ToList();
return r.Group;
})
.ToList();
Try using an OR condition to filter both the group name and the store name.
return _context.RetailGroups
.Where(group => group.GroupName.Contains(value) || group.RetailStores.Any(store => store.StoreName.Contains(value)))
.Include(group => group.RetailStores.Where(store => store.StoreName.Contains(value)))
.ToList();
Another option would be doing 2 searches, one for the groups and other for the stores. The problem here would be geting a unique set of groups from both results.
List<RetailGroup> retailGroups = new List<RetailGroup>();
var groupSearchResults = _context.RetailGroups
.Where(group => group.GroupName.Contains(value))
.Include(group => group.RetailStores.Where(store => store.StoreName.Contains(value)))
.ToList();
var storeSearchResults = _context.RetailStores
.Where(store => store.StoreName.Contains(value))
.Select(store => store.RetailGroup)
.ToList();
retailGroups.AddRange(groupSearchResults);
retailGroups.AddRange(storeSearchResults);
// Remove duplicates by ID
retailGroups = retailGroups
.GroupBy(group => group.Id)
.Select(group => group.First());
Either use OR condition and have the search in one statement :
public List<RetailGroup> SearchGroupsAndStores(string value)
{
return _context.RetailGroups
.Where(rg => rg.GroupName.Contains(value) || rg.RetailStores.Any(rs => rs.StoreName.Contains(value)))
.Include(rg => rg.RetailStores.Where(rs => rs.StoreName.Contains(value)).ToList())
.ToList();
}
Or you can split the search, first look for RetailGroups then search RetailStores and return their RetailGroup :
public List<RetailGroup> SearchGroupsAndStores(string value)
{
List<RetailGroup> searchResults = new List<RetailGroup>();
searchResults.AddRange(_context.RetailGroups.Where(rg => rg.GroupName.Contains(value)).ToList());
searchResults.AddRange(_context.RetailStores.Where(rs => rs.StoreName.Contains(value)).Select(rs => rs.RetailGroup).ToList());
}

LINQ Sub Query In Result Entity Framework 5

I've the following class
public class Interview
{
public int Id { get; set; }
public ICollection<InterviewSlot> Slots { get; set; }
}
public class InterviewSlots
{
public int Id { get; set; }
public Candidate Candidate { get; set; }
}
public class Candidate
{
public int Id { get; set; }
}
I want something like this,
var candidates = _DbContext.Interviews.Where(i => i.Id == Id).Select(s => s.Slots.Select(c => c.Candidate).ToList();
I don't want to use the InterviewSlots or the Candidate object
I want to get all the Candidates in a interview.
What would the LINQ be for this??
I'm thinking it may be along the lines of something like this in linq:
var candidates = _DbContext.Interviews.Where(i => i.Id == id)
.SelectMany(interview => interview.Slots)
.Select(slot => slot.Candidate)
.ToList();
tho, without seeing exactly how you plan to use it, quite a tricky one to answer.
I don't really understand your question
What would the LINQ be for this??
But here's what you need in order to get all candidates in an interview.
Without null checking.
var interview = _DbContext.Interviews.Where(i => i.Id == Id).Single();
var candidates = interview.Slots.Select(s => s.Candidate);
With null checking
var interview = _DbContext.Interviews.Where(i => i.Id == Id).SingleOrDefault();
if (interview != null)
var candidates = interview.Slots.Select(s => s.Candidate);
In one line
_DbContext.Interviews.Where(i => i.Id == Id)
.Single()
.Slots.Select(s => s.Candidate);

How to create a lambda expression that filters from a list of items?

I am using the Entity Framework, and have the following classes:
There is a search function which queries the database based upon some criteria, which is gathered in the SearchJSON object:
public class SearchJSON : ISearchObject
{
public List<String> sites { get; set; }
public int minWage { get; set; }
public int maxWage { get; set; }
public List<String> countries { get; set; }
public int minRating { get; set; }
public int maxRating { get; set; }
}
For now, I've managed to filter the results by HourWageMin and HourWageMax, but I would like to filter on countries and skills aswell. This is my query so far:
var query =
db.Freelancers.Where(x => x.HourWageMin >= data.minWage && x.HourWageMax <= data.maxWage).ToList();
I get the skills from the List<String> skills and countries from List<String> countries,
how do i add this to the lambda expression? The fields on the Skills and Countries objects are called Name
First of all, you should rename the Countries navigation property on Freelancers to Country to avoid confusion as it is a 0 or 1 to many relationship. I assume it's intended that a Freelancer can't have more than one country.
To filter on country is easy:
var query = db.Freelancers.Where(x => x.Country != null && x.Country.Name == "France");
To filter based on whether a freelancer has a specific skill:
var query = db.Freelancers.Where(x => x.Skills.Any(s => s.Name == "Artist"));
You can also chain Where methods together instead using using && over and over to make it more readable:
var query = db.Freelancers.Where(x => x.HourWageMin >= data.minWage)
.Where(x => x.HourWageMax <= data.maxWage)
.Where(x => x.Country != null && x.Country.Name == "France")
.Where(x => x.Skills.Any(s => s.Name == "Artist"));
You can use Any and Contains:
var query = db.Freelancers.Where(
x => x.HourWageMin >= data.minWage &&
x.HourWageMax <= data.maxWage &&
x.skills.Any(s => data.countries.Contains(s.Name)) &&
x.countries.Any(c => data.skills.Contains(c.Name))
).ToList();

Categories

Resources