LINQ to SQL and LINQ to Entity Join with static AND condition - c#

I'm trying to convert the following Join statement into LINQ TO SQL or LINQ to Entity. I know how to join tables in either implementation; but, i'm struggling with the AND clause in the Join statement.
SELECT DISTINCT
p.LastName,
p.FirstName
FROM
dbo.Patient p INNER JOIN dbo.FormPat fp ON p.PatientID = fp.PatientID
INNER JOIN dbo.TxCyclePhase tcp ON fp.TxCyclePhase = tcp.TxCyclePhaseID AND tcp.Type = 2
As far as LINQ to SQL is concerned, I have the followings:
var query = (from p in Context.Set<Patient>().AsNoTracking()
join fp in Context.Set<PatientForm>().AsNoTracking() on p.Id equals fp.PatientId
join tcp in Context.Set<TxCyclePhase>().AsNoTracking() on new { fp.TxCyclePhaseId, seconProperty = true } equals new { tcp.Id, seconProperty = tcp.Type == 2 }
select new
{
p.FirstName,
p.LastName,
}).Distinct();
However, I'm getting an ArgumentNullException on the second join statement.
For the LINQ to Entity, I have the followings, however, this is giving me a distinct IQueryable of FormPat, instead of Patient.
var patients = Context.Set<Patient>().AsNoTracking()
.SelectMany(p => p.Forms)
.Where(fp => fp.Phase.Type == 2)
.Distinct();

As far as the LINQ to Entity is concerned, I was able to figure it out. I'd still like to know how to do it in LINQ to SQL tho.
I'm using the EF fluent API. My Patient object looks like:
public Patient()
{
Programs = new HashSet<Program>();
}
public virtual ICollection<PatientForm> Forms { get; set; }
My PatientForm object looks like:
public class PatientForm
{
public int FormId { get; set; }
public Patient CurrentPatient { get; set; }
public TxCyclePhase Phase { get; set; }
}
And the CyclePhase object looks like:
public TxCyclePhase()
{
this.FormPats = new HashSet<PatientForm>();
}
public int Id { get; set; }
public virtual ICollection<PatientForm> FormPats { get; set; }
In the entity configurations, I have the relationships set. So, in the repository, all I have to do is to use the Any() function when selecting the Patient forms.
var patients = Context.Set<Patient>().AsNoTracking()
.Where(p => p.Forms.Any(f => f.Phase.Type == 2))
.Distinct();

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

How to query multiple tables using LINQ syntax in .NET core 3

I need to query two database tables for a search term and return the results. The following was working in EF Core 2:
var SearchTerm = "hello";
IQueryable<TableA> q;
q = (from a in context.TableA
join b in context.TableB on a equals b.A into leftjoin
from c in leftjoin.DefaultIfEmpty()
where c.Column1.Contains(SearchTerm)
|| a.Column1.Contains(SearchTerm)
|| a.Column2.Contains(SearchTerm)
select a);
return q.Include(a => a.TableD)
.GroupBy(a => a.Id)
.Select(group => group.First())
.ToList();
The idea of the above is to take a SearchTerm and query two columns from TableA, join to TableB and also query a column in this one then select distinct values from TableA.
In .NET 3 the above throws an error saying it can't be translated to SQL. I tried to rewrite this, the best I can do is the below:
var SearchTerm = "hello";
var q = (from a in context.TableA
join b in context.TableB on a equals b.A into leftjoin
from c in leftjoin.DefaultIfEmpty()
where c.Column1.Contains(SearchTerm)
|| a.Column1.Contains(SearchTerm)
|| a.Column2.Contains(SearchTerm)
select a.Id).Distinct().ToList();
return context.TableA
.Where(a => q.Contains(a.Id))
.Include(c => c.TableD)
.ToList();
Which works ok but involves two database queries, since I already have the list of TableA from the first query it would be great to be able to just use this without having to extract the Ids and performing the second query. Also making sure the database continues to handle the distinct part rather than C# would be preferable too.
The definitions of A and B are:
public class TableA
{
public int Id { get; set; }
public string Column1 { get; set; }
public string Column2 { get; set; }
public int TableDId { get; set; }
public TableD TableD { get; set; }
}
public class TableB
{
public int Id { get; set; }
public string Column1 { get; set; }
public int TableAId { get; set; }
public TableA TableA { get; set; }
}
If I understood you correctly you have one-to-many relation between TableA and TableB, so it should be possible to add collection navigation property to TableA like in this tutorial for example:
public class TableA
{
public int Id { get; set; }
...
public ICollection<TableB> TableBs { get; set; }
}
So you can try to do something like this:
context.TableA
.Where(ta => ta.TableBs.Any(tb => tb.Column1.Contains(SearchTerm))
|| ta.Column1.Contains(SearchTerm)
|| ta.Column2.Contains(SearchTerm))
.Include(c => c.TableD)
.ToList();
Another option is to try subquery:
var q = (from a in context.TableA
join b in context.TableB on a.Id equals b.TableAId into leftjoin
from c in leftjoin.DefaultIfEmpty()
where c.Column1.Contains(SearchTerm)
|| a.Column1.Contains(SearchTerm)
|| a.Column2.Contains(SearchTerm)
select a.Id); // remove Distinct and ToList
return context.TableA
.Where(a => q.Contains(a.Id))
.Include(c => c.TableD)
.ToList();

LINQ - Searching one to many for exact match of array

Let me preface by saying that this is self study and I am trying to self learn LINQ and Entity Framework. I've spent a few days attempting to turn the SQL statement at the bottom of this question into LINQ with terrible results. I've also included the SQL diagram at the bottom.
My goal is to select all stories that have the same characters as the passed in string array. I'm not wanting stories returned that have additional characters or missing characters. This is what my feeble LINQ skills have come up with so far:
var characters = new string[] { "Harry", "Tom" };
var cq = _context.TblCharacter.AsNoTracking().Where(c => characters.Contains(c.NameVc));
var q = from c in cq
join sc in _context.TblStoryCharacter.AsNoTracking()
on c.IdI equals sc.CharacterIdI
join s in _context.TblStory.AsNoTracking().Include(s => s.TblStoryCharacter).ThenInclude(sc => sc.CharacterIdINavigation)
on sc.StoryIdI equals s.IdI
where s.TblStoryCharacter.Count() == characters.Length
where s.TblStoryCharacter.Where(sc => characters.Contains(sc.CharacterIdINavigation.NameVc)).Count() == characters.Length
select s;
The above code is spawning a bunch of queries (SQL profiler image below) and is loading a bunch of objects into memory. Is there any LINQ magic for this scenario?
LINQ spawned queries:
SELECT [t0].[StoryId_i]
FROM [tbl_story_character] AS [t0]
SELECT [sc1].[StoryId_i]
FROM [tbl_story_character] AS [sc1]
INNER JOIN [tbl_character] AS [sc.CharacterIdINavigation0] ON [sc1].[CharacterId_i] = [sc.CharacterIdINavigation0].[Id_i]
WHERE [sc.CharacterIdINavigation0].[Name_vc] IN ('Harry', 'Tom')
And this is the SQL I started out trying to convert to LINQ:
select *
from tbl_story
where Id_i in (
select sc.StoryId_i
from tbl_story_character sc
inner join tbl_character c
on c.Id_i = sc.CharacterId_i
where c.Name_vc in ('Harry', 'Tom')
and not exists (
select *
from tbl_story_character subsc
inner join tbl_character subc
on subc.Id_i = subsc.CharacterId_i
where subc.Name_vc not in ('Harry', 'Tom')
and subsc.StoryId_i = sc.StoryId_i
)
group by sc.StoryId_i
having count(*) = 2
)
Database diagram:
EDIT:
The models were generated by EFCore based on an existing database, each model contains navigation properties based on the foreign keys in the diagram.
New LINQ after taking advice of Jon Skeet and Munzer.
from s in _context.TblStory.AsNoTracking()
.Include(s => s.AuthorIdINavigation)
.Include(s => s.TblStoryCharacter)
.ThenInclude(sc => sc.CharacterIdINavigation)
where s.TblStoryCharacter.All(sc => characters.Contains(sc.CharacterIdINavigation.NameVc))
where s.TblStoryCharacter.Count == 2
select s;
This results in the following SQL which seems correct.
SELECT [s].[Id_i], [s].[AuthorId_i], [s].[Published_dt]
FROM [tbl_story] AS [s]
INNER JOIN [tbl_author] AS [t2] ON [s].[AuthorId_i] = [t2].[Id_i]
WHERE NOT EXISTS (
SELECT 1
FROM [tbl_story_character] AS [sc]
INNER JOIN [tbl_character] AS [sc.CharacterIdINavigation] ON [sc].[CharacterId_i] = [sc.CharacterIdINavigation].[Id_i]
WHERE ([s].[Id_i] = [sc].[StoryId_i]) AND [sc.CharacterIdINavigation].[Name_vc] NOT IN ('Harry', 'Tom')) AND ((
SELECT COUNT(*)
FROM [tbl_story_character] AS [t]
WHERE [s].[Id_i] = [t].[StoryId_i]
) = 2)
ORDER BY [s].[Id_i]
I believe All is what you are looking for here
it should be something like this
var q = from c in cq
join sc in _context.TblStoryCharacter.AsNoTracking()
on c.IdI equals sc.CharacterIdI
join s in _context.TblStory.AsNoTracking().Include(s => s.TblStoryCharacter).ThenInclude(sc => sc.CharacterIdINavigation)
on sc.StoryIdI equals s.IdI
where s.TblStoryCharacter.All(sc => characters.Contains(sc.CharacterIdINavigation.NameVc))
select s;
Try something like this :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] names = {"Harry", "Tom"};
var results = (from sc in Story_Character.story_character
join chr in Character.character on sc.Id_i equals chr.Id_i
join st in Story.story on sc.Id_i equals st.Id_i
join auth in Author.author on sc.Id_i equals auth.Id_i
where names.Contains(chr.Name_vc)
select new { sc = sc, chr = chr, st = st, auth = auth })
.GroupBy(x => x.sc.StoryId_i).Where(x => x.Count() >= 2).ToList();
}
}
public class Story_Character
{
public static List<Story_Character> story_character = new List<Story_Character>();
public int Id_i { get; set; }
public int StoryId_i { get; set; }
public int CharacterId_i { get; set; }
}
public class Character
{
public static List<Character> character = new List<Character>();
public int Id_i { get; set; }
public string Name_vc { get; set; }
}
public class Story
{
public static List<Story> story = new List<Story>();
public int Id_i { get; set; }
public DateTime Published_dt { get; set; }
public string Title_vc { get; set; }
public string AuthorId_i { get; set; }
}
public class Author
{
public static List<Author> author = new List<Author>();
public int Id_i { get; set; }
public string Name_vc { get; set; }
public string Url_vc { get; set; }
}
}

Linq query with first or default join

I have the following data model:
public class Course
{
public int CourseId { get; set; }
public int StateId { get; set; }
}
public class CompletedCourse
{
public int CompletedCourseId { get; set; }
public int UserId { get; set; }
public Course Course { get; set; }
public string LicenseNumber { get; set; }
}
public class License
{
public int LicenseId { get; set; }
public int UserId { get; set; }
public int StateId { get; set; }
public string LicenseNumber { get; set; }
}
I'm trying to come up with an IQueryable for CompletedCourses and I would like to populate CompletedCourse.LicenseNumber with the LicenseNumber property of the FirstOrDefault() selection from my Licenses table where UserId and StateId match the completed course records.
Here is my query, but I don't think this will handle duplicate licenses correctly:
var entries =
(from course in context.CompletedCourses
join license in context.Licenses on course.UserId equals license.UserId
where license.StateId == course.Course.StateId
select course)
.Include(x => x.Agent)
.Include(x => x.Course.State);
Is this something that can be done in a single query? Thanks in advance.
Here is how you can do that:
var entries =
(from course in context.CompletedCourses
join license in context.Licenses
on new { course.UserId, course.Course.StateId }
equals new { license.UserId, license.StateId }
into licenses
let licenseNumber = licenses.Select(license => license.LicenseNumber).FirstOrDefault()
select new { course, licenseNumber });
But please note that with this type of projection you cannot have Includes in your query (you can, but they will not be in effect).
The EF generated query I'm getting from the above is:
SELECT
[Extent1].[CompletedCourseId] AS [CompletedCourseId],
[Extent1].[UserId] AS [UserId],
[Extent1].[LicenseNumber] AS [LicenseNumber],
[Extent1].[Course_CourseId] AS [Course_CourseId],
(SELECT TOP (1)
[Extent2].[LicenseNumber] AS [LicenseNumber]
FROM [dbo].[Licenses] AS [Extent2]
INNER JOIN [dbo].[Courses] AS [Extent3] ON [Extent3].[StateId] = [Extent2].[StateId]
WHERE ([Extent1].[Course_CourseId] = [Extent3].[CourseId]) AND ([Extent1].[UserId] = [Extent2].[UserId])) AS [C1]
FROM [dbo].[CompletedCourses] AS [Extent1]
It can be noticed that EF effectively ignores the join, so the same result can be obtained by simple natural query:
var entries =
(from course in db.CompletedCourses
let licenseNumber =
(from license in db.Licenses
where license.UserId == course.UserId && license.StateId == course.Course.StateId
select license.LicenseNumber).FirstOrDefault()
select new { course, licenseNumber });
#IvanStoev's answer was very helpful in joining on anonymous types, but ultimately I couldn't use it because I needed Includes. Here is the solution I went with that results in two DB queries instead of one which is fine for my situation.
var entries = context.CompletedCourses
.Include(x => x.Agent)
.Include(x => x.Course);
var courses = entries.ToList();
var courseIds = entries.Select(x => x.CompletedCourseId);
var licenses =
(from course in entries
join license in context.Licenses
on new { course.AgentId, course.Course.StateId }
equals new { AgentId = license.UserId, license.StateId }
where courseIds.Contains(course.CompletedCourseId)
select license);
foreach (var course in courses)
{
var license = agentLicenses.FirstOrDefault(x => x.UserId == course.AgentId &&
x.StateId == course.Course.StateId);
if (license != null)
{
course.LicenseNumber = license.LicenseNumber;
}
}
return courses;

traversing one to many relationships in entity framework

I am having trouble figuring out how to traverse a one to many relasionship using LINQ-To-SQL in my asp.net site that uses EF 5. I have made the relationships in the class files but when I try to go from parent to child in my where clause I am not given a list of the child columns to filter on. Can anyone tell me what is wrong with my code, I am new to EF and LINQ.
Product.cs:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Category Category { get; set; }
}
}
Category.cs:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<Product> Products { get; set; }
}
Codebehind:
using (var db = new Compleate())
{
rpBooks.DataSource = (from c in db.Categories
where c.Products.Name == "Books"
select new
{
c.Name
}).ToList();
}
Do you want all products in the books category?
from p in db.Products
where p.Category.Name == "Books"
select new
{
p.Name
}
Or do you want to have all categories that contain products that are called called books?
from c in db.Categories
where c.Products.Contains( p => p.Name == "Books")
select new
{
c.Name
}
BTW, if you're only selecting the name, you can skip the anonymous type in the select part...
select p.name
Ok I had to update the codebhind to look like:
using (var db = new Compleate())
{
rpBooks.DataSource = (from c in db.Categories
join p in db.Products on c.ID equals p.id
where c.Products.Name == "Books"
select new
{
c.Name
}).ToList();
}
It should be name = c.Name it's not an issue with traversing, it's an issue with syntax, read the brief article on anonymous types here

Categories

Resources