many to many linq query with like expression - c#

What is the best way to do a many to many join ant the entity framework.
I have a tag class
favorite
I have a Tag class
[Table("tblTags")]
public class Tag
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
[Column("fld_int_id")]
public virtual int Id { get; set; }
[Column("fld_str_name")]
public virtual string Name { get; set; }
public virtual ICollection<DocumentUploadEntity> Documents { get; set; }
}
I have a documents class
[Table("tblUploadDocument")]
public class DocumentUploadEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("fld_int_ID")]
public int ID { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
I map them like this:
modelBuilder.Entity<DocumentUploadEntity>()
.HasMany(x => x.Tags)
.WithMany(x => x.Documents)
.Map(x =>
{
x.ToTable("tblUploadDocumentsTags");
x.MapLeftKey("fld_int_document_id");
x.MapRightKey("fld_int_tag_id");
});
I want to search for any documents contain a tag name in a like expression. If I was to do this in sql I would do this:
SELECT * FROM tblUploadDocument d
INNER JOIN tblUploadDocumentsTags ud
ON fld_int_document_id = d.fld_int_id
INNER JOIN tbltags t
ON ud.fld_int_tag_id = t.fld_int_id
WHERE t.fld_str_name like 'foo%';
Please excuse the table names and field names, this was not my doing.
How can I do this with linq and entity framework.

var documents = DbContext.Tags.Where(x => x.Name.StartsWith("foo"))
.SelectMany(y => y.Documents).ToList()

The beauty of the EF is that you can start from either side and use the navigation property to get to the other side of the many-to-many relationship. Behind the scenes EF will use the link table and necessary joins.
For instance, you can start from DocumentUploadEntity:
var documents =
from document in db.DocumentUploadEntities
where document.Tags.Any(tag => tag.Name.Contains("foo"))
select document;
or you can start from Tag:
var documents =
from tags in db.Tags
where tag.Name.Contains("foo")
from document in tag.Documents
select document;
UPDATE:: As #James Dev correctly stated in the comments, the equivalent of SQL LIKE 'foo% is Name.StartsWith("foo").

Related

ASP.NET Core - How to convert SQL Server statement to Entity Framework Core

I have this SQL Server query which works and gives the desired result in SQL Server:
SQL Server query:
SELECT
s.RegNumber,
s.AdmissionDate,
c.CourseName
FROM student AS s
JOIN student_course AS sc
ON s.id = sc.StudentId
JOIN course AS c
ON c.id = sc.CourseId
WHERE c.Id = '67A21699-DFE6-4CB6-96B6-E009FD389596';
StudentCourses:
public class StudentCourses
{
public Guid Id { get; set; }
public Guid StudentId { get; set; }
public Guid CourseId { get; set; }
}
However, when I tried to turn it to ASP.NET Core-6 Entity Framework as shown below:
var sc = Context.StudentCourses
.Where(x => x.CourseId == '67A21699-DFE6-4CB6-96B6-E009FD389596')
.Include(x => x.Student)
.Include(x => x.Course);
return sc;
It gives me student details as null.
How do I get this sorted out?
Thanks.
In order to understand how to do that, you need to go back to basics
You need to study how to build a model in EF. There are many tutorials on the web; and SO discourages opinions; so I will just put a link to official Microsoft documentation. You will end up with something like this:
public class Student
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Student> Students { get; set; }
}
Note that there is no class StudentCourses!
You need to study the implementation of many-to-many relationship in Entity Framework 6. Again, you should start at official Microsoft documentation; but it is a complex topic - so you probably need to spend more time on some tutorials. In DbContext you will have something like this:
modelBuilder.Entity<Students>()
.HasMany(p => p.Courses)
.WithMany(p => p.Students)
Once you have basic understanding of that, writing a LINQ statement is quite straightforward. Remember, you are NOT converting SQL to LINQ!
var sc = _context.Courses
.Where(x => x.CourseId == '67A21699-DFE6-4CB6-96B6-E009FD389596')
.Select(c => new {
c.CourseId,
c.Name,
c.Students
});
Also note - that whenever you use Include(), it's "code smell"; it means you need to review your design, and maybe study some more tutorials.

What's the purpose of autogenerated linking table if you cannot use it from your C# Windows Application (using Linq)?

The code below, is fine. It works. Things are inserted, stuff appear on the right side on the left side etc. However, there's an issue. Using a program such as LinQPad (https://www.linqpad.net), I am able to query the data and see it listed.
But, trying to do the same in C#, says that the "auto" generated linking table does not exists in the context.
Relationship: Many-to-Many.
I am using Entity Framework 6.
EDIT: What's the purpose of autogenerated linking table if you cannot use it from your C# Windows Application? Using Linq?
My Code First tables:
public class Student
{
[Key, Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int StudentID { get; set; }
public string StudentName { get; set; }
public DateTime? DateOfBirth { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
[Key, Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CourseId { get; set; }
[Index("CourseName", 2, IsUnique = true)]
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
DBContext.cs
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("FK_StudentID");
cs.MapRightKey("FK_CourseID");
cs.ToTable("StudentCourse");
});
base.OnModelCreating(modelBuilder);
}
Querying from within the LinqPad, works perfectly fine:
void Main()
{
var data = (from s in Students
join sc in StudentCourse on s.StudentID equals sc.FK_StudentID
join c in Courses on c.CourseId equals sc.FK_CourseID
select new { s,c });
Console.WriteLine(data);
}
There is nothing stopping you from declaring it as an entity, but where the linking table is kept to just the required keys, it is an optimization not to have to declare it when using the navigation properties.
The key issue I think you are facing here is you're writing Linq like SQL and completely missing the navigation properties.
For instance, if I want all Courses for a particular student: Without a linking table I can do:
var studentCourses = context.Students
.Where(x => x.StudentId == studentId)
.SelectMany(x => x.Courses)
.ToList();
To get a list of each student/course combination as you outlined:
var studentCourses = context.Students
.SelectMany(s => s.Courses.Select(c => new {s, c}))
.ToList();
This gives me the list of courses for that student. I don't have to manually join up Student to Course through StudentCourse like writing an SQL statement.
Now you can certainly declare a StudentCourse entity, but this changes the relationships slightly. Rather than a Many-to-Many, you have a Many-to-One-to-Many:
public class Student
{
// ...
public virtual ICollection<StudentCourse> StudentCourses { get; set; } = new List<StudentCourse>();
}
public class Course
{
// ...
public virtual ICollection<StudentCourse> StudentCourses { get; set; } = new List<StudentCourse>();
}
public class StudentCourse
{
[Key, Column(Order=0), ForeignKey(Student)]
public int StudentId { get; set; }
[Key, Column(Order=1), ForeignKey(Course)]
public int CourseId { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course { get; set; }
}
It can be tempting to name the StudentCourses property "Courses" and "Students" in their respective counterparts, but IMO this gets misleading when diving trough the navigation as I'll point out in the example below.
For those using EFCore, I believe this is still the only supported option for many-to-many. This is also a required option if you want to make any alterations to the joining table such as using a dedicated PK column, or other columns such as CreatedAt etc. to track edits etc. for the relationships.
Then to do the first example query in Linq you'd need to change it slightly to:
var studentCourses = context.Students
.Where(x => x.StudentId == studentId)
.SelectMany(x => x.StudentCourses.Course)
.ToList();
To get a list of each student/course combination with this linking entity:
var studentCourses = context.StudentCourses.ToList();
// or, if there is other data in the linking entity and you just want student and course reference:
var studentCourses = context.StudentCourses
.Select(sc => new {sc.Student, sc.Course})
.ToList();
Alternatively you could write the Linq QL statement like you did in Linqpad now that the entity is declared and can be added as a DbSet in the context. (DbSet is not required if you are using navigation properties)
Edit: added example below: (I can't vouch for it's correctness as I pretty much exclusively use the Fluent Linq syntax, not Linq QL)
var data = (from s in context.Students
join sc in context.StudentCourse on s.StudentID equals sc.StudentID
join c in context.Courses on c.CourseId equals sc.CourseID
select new { s,c });
This is why I recommend naming the property on Student as StudentCourses rather than Courses. The alternative would read:
.SelectMany(x => x.Courses.Course)
Where Courses implies I should be getting courses (as the optimization to avoid the linking entity can give you) but you're getting StudentCourses so you're left with it looking rather weird as .Courses.Course to get the actual Course.

How to write T-SQL many-to-many with subquery in EF

I have two classes with a many-to-many relationship in a ASP.NET EF application. I'm trying to find all Listings that have any Categories which is posted from a view. The categories are checkboxes on the view form.
These are the classes with navigation properties simplified for example:
public class Listing
{
public int ID { get; set; }
public ICollection<Category> Categories { get; set; }
...
}
public class Category
{
public int ID { get; set; }
public ICollection<Listing> Listings { get; set; }
...
}
// this is the join table created by EF code first for reference
public class CategoryListings
{
public int Category_ID { get; set; }
public int Listing_ID { get; set; }
}
This is the query I am trying to use in my MVC Controller but it doesn't work and I don't really know what else to try:
if (model.Categories !=null && model.Categories.Any(d => d.Enabled))
{
List<Listing> itemsSelected = null;
foreach (var category in model.Categories.Where(d => d.Enabled))
{
var itemsTemp = items.Select(x => x.Categories.Where(d => d.ID == category.ID));
foreach (var item1 in itemsTemp)
{
itemsSelected.Add((Listing)item1); //casting error here
}
}
items = itemsSelected;
}
In SQL, I would write this using a subquery (the subquery represents the multiple categories that can be searched for):
select l.id, cl.Category_ID
from
listings as l inner join CategoryListings as cl
on l.id=cl.Listing_ID
inner join Categories as c on c.ID = cl.Category_ID
where c.id in (select id from Categories where id =1 or id=3)
How do I write that SQL query in EF using navigators or lambda? The subquery in the SQL will change each search and can be any id or IDs.
You forgot to tell us what objects are in your collection items. I think they are Listings. Your case doesn't work, because itemsTemp is a collection of Categories, and every item1 is a Category, which of course can't be cast to a Listing.
Advice: to debug casting problems, replace the word var
with the type you actually expect. The compiler will warn you about
incorrect types. Also use proper identifiers in your lambda expressions.
This makes them easier to read
IQueryable<???> items = ... // collection of Listings?
List<Listing> itemsSelected = null;
IQueryable<Category> enabledCategories = model.Categories.Where(category => category.Enabled));
foreach (Category category in enabledCategories)
{
IEnumerable<Category> itemsTemp = items
.Select(item => item.Categories
.Where(tmpCategory => tmpCategory.ID == category.ID));
foreach (Category item1 in itemsTemp)
{
// can't cast a Category to a Listing
We'll come back to this code later.
If I look at your SQL it seems that you want the following:
I have a DbContext with (at least) Listings and Categories.
I want all Listings with their Categories that have category Id 1 or 3
It's good to see that you followed the entity framework code-first conventions, however you forgot to declare your collections virtual:
In entity framework the columns in a table are represented by
non-virtual properties. The virtual properties represent the relations
between the table.
With a slight change your many-to-many relation can be detected automatically by entity framework. Note the virtual before the ICollection
class Listing
{
public int ID { get; set; }
// every Listing has zero or more categories (many-to-many)
public virtual ICollection<Category> Categories { get; set; }
...
}
class Category
{
public int ID { get; set; }
// every Category is used by zero or more Listings (many-to-many)
public ICollection<Listing> Listings { get; set; }
...
public bool Enabled {get; set;}
}
And the DbContext
public MyDbContext : DbContext
{
public DbSet<Listing> Listings {get; set;}
public DbSet<Category> Categories {get; set;}
}
Although a relational database implements a many-to-many relationship with a junction table, you don't need to declare it in your DbContext. Entity framework detects that you want to design a many-to-many and creates the junction table for you.
But how can I perform my joins without access to the junction table?
Answer: Don't do joins, use the ICollections!
Entity Framework knows which inner joins are needed and will do the joins for you.
Back to your SQL code:
Give me all (or some) properties of all Listings that have at least one Category with Id equal to 1 or 3
var result = myDbcontext.Listings
.Select(listing => new
{ // select only the properties you plan to use
Id = listing.Id,
Name = listing.Name,
...
Categories = listing.Categories
// you don't want all categories, you only want categories with id 1 or 3
.Where(category => category.Id == 1 || category.Id == 3)
.Select(category => new
{
// again select only the properties you plan to use
Id = category.Id,
Enabled = category.Enabled,
...
})
.ToList(),
})
// this will also give you the Listings without such Categories,
// you only want Listings that have any Categories left
.Where(listing => listing.Categories.Any());
One of the slower parts of database queries is the transfer of the selected data from the DBMS to your local process. Hence it is wise to only transfer the properties you actually plan to use. For example, you won't need the foreign keys of one-to-many relationships, you know it equals the Id value of the one part in the one-to-many.
Back to your code
It seems to me, that your items are Listings. In that case your code wants all Listings that have at least one enabled Category
var result = myDbContext.Listings
.Where(listing => ...) // only if you don't want all listings
.Select(listing => new
{
Id = listing.Id,
Name = list.Name,
Categories = listing.Categories
.Where(category => category.Enabled) // keep only the enabled categories
.Select(category => new
{
Id = category.Id,
Name = category.Name,
...
})
.ToList(),
})
// this will give you also the Listings that have only disabled categories,
// so listings that have any categories left. If you don't want them:
.Where(listing => listing.Categories.Any());
Do you have a relation between Listing/Category and CategoryListings?
Here is example for EF 6: http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
If you have it the query will be simple, something like that:
CategoryListing.Where(cl => new List<int>{1, 3}.Contains(cl.CategoryRefId))
.Select(x => new {x.ListingRefId, x.CategoryRefId});
If you need all properties of Listing or Category, Include() extension will help.

Use Where Clause on navigation property. Many-to-many relationship

I have been looking through other examples on SO and I am still unable to resolve this.
I have the following model structure
public class Event
{
[Key]
public int ID { get; set; }
public ICollection<EventCategory> EventCategories{ get; set; }
}
public class Category
{
[Key]
public int ID { get; set; }
public ICollection<EventCategory> EventCategories{ get; set; }
}
public class EventCategory
{
[Key]
public int ID { get; set; }
public int EventID{ get; set; }
public Event Event{ get; set; }
public int CategoryID{ get; set; }
public Category Category{ get; set; }
}
From my Events controller I am trying to use a LINQ query to only show Events where the CategoryID is equal to 1 but i keep on coming into errors with my WHERE clause I think.
UPDATE:
I have been trying multiple queries but at present it is
var eventsContext = _context.Events
.Include(e => e.EventCategories)
.Include(e=>e.EventCategories.Select(ms => ms.Category))
.Where(e=>e.ID==1)
.Take(15)
.OrderByDescending(o => o.StartDate);
This is the error I get
TIA
First, the lambda passed to Include must be a model expression. Specifically, that means you cannot use something like Select. If you're trying to include EventCategories.Category, then you should actually do:
.Include(e => e.EventCategories).ThenInclude(ms => ms.Category)
That will fix your immediate error. The next issue is that the way in which you're attempting to query the category ID is incorrect. The lamdas don't carry over from one clause to the next. In other words, when you're doing Where(e => e.ID == 1), e is Event, not Category. The fact that you just included Category doesn't limit the where clause to that context. Therefore, what you actually need is:
.Where(e => e.EventCategories.Any(c => c.CategoryID == 1))
For what it's worth, you could also write that as:
.Where(e => e.EventCategories.Any(c => c.Category.ID == 1))
Notice the . between Category and ID. Now this where clause requires joins to be made between all of Event, EventCategories, and Category, which then means you don't actually need your Include(...).ThenInclude(...) statement, since all this does is tell EF to make the same JOINs it's already making. I will still usually do the includes explicitly, though, as otherwise, if your where clause were to change in some future iteration, you may end up no longer implicitly including everything you actually want included. Just food for thought.

Many-to-many relationship in EF user instance is NULL [duplicate]

This question already has answers here:
EF Core returns null relations until direct access
(2 answers)
Closed 5 years ago.
I'm using .net core 2 mvc, I tried to build many-to-many relationship between Users and Steps.
the relationship is doen but when I query for the record I get user = null.
Hier is my code:
(applicationUser model):
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
(Steps model):
public class Steps
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
StepsUsers model:
public class StepsUsers : IAuditable
{
public int StepId { get; set; }
public Steps Step { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
In DbContext I did this :
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<StepsUsers>()
.HasKey(s => new { s.StepId, s.UserId });
builder.Entity<StepsUsers>()
.HasOne(su => su.Step)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.StepId);
builder.Entity<StepsUsers>()
.HasOne(su => su.User)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.UserId);
}
public DbSet<MyApp.Models.StepsUsers> StepsUsers { get; set; }
Now, when I query for an instance of StepsUsers with specific StepId I get all de fields correct except the User field is null
var stepUsers = await _context.StepsUsers.Where(s => s.StepId == id).ToListAsync();
I did the same code for another two tables and it works fine, I don't know why it is like this, any suggestion 1?
The cause of your problems is that your forgot to declare your To-many relations as virtual. Another improvement would be to declare them as virtual ICollection instead of List. After all, what would ApplicationUser.StepUser[4] mean?
If you configure a many-to-many relationship according to the entity framework conventions for many-to-many, you don't need to mention the junction table (StepsUsers). Entity framework will recognize the many-to-many and will create the junction table for you. If you stick to the code first conventions you won't even need the fluent API to configure the many-to-many.
In your design every ApplicationUser has zero or more Steps and every Step is done by zero or more ApplicationUsers.
class ApplicationUser
{
public int Id {get; set;}
// every ApplicationUser has zero or more Steps:
public virtual ICollection<Step> Steps {get; set;}
public string Name {get; set;}
...
}
class Step
{
public int Id {get; set;}
// every Step is performed by zero or more ApplicationUsers:
public virtual ICollection<ApplicationUser> ApplicationUsers {get; set;}
public string Name {get; set;}
...
}
public MyDbContext : DbContext
{
public DbSet<ApplicationUser ApplictionUsers {get; set;}
public DbSet<Step> Steps {get; set;}
}
This is all entity framework needs to know to recognize that you configured a many-to-many relationship. Entity framework will create the junction table for you and the foreign keys to the junction table. You don't need to declare the junction table.
But how am I suppose to do a join if I don't have the junction table?
The answer is: Don't do the join. Use the collections instead.
If you want all ApplicationUsers that ... with all their Steps that ... you would normally do an inner join with the junction table, and do some group by to get the Application users. Ever tried method syntax to join three tables? They look hideous, difficult to understand, error prone and difficult to maintain.
Using the collections in entity framework your query would be much simpler:
var result = myDbContext.ApplicationUsers
.Where(applicationUser => applicationUser.Name == ...)
.Select(applicationUser => new
{
// select only the properties you plan to use:
Name = applicationUser.Name,
Steps = applicationUser.Steps
.Where(step => step.Name == ...)
.Select(step => new
{
// again fetch only Step properties you plan to use
Name = step.Name,
...
})
.ToList(),
});
Entity framework will recognize that joins with the junction table is needed and perform them for you.
If you want Steps that ... with their ApplicationUsers who ... you'll do something similar:
var result = myDbContext.Steps
.Where(step => ...)
.Select(step => new
{
Name = step.Name,
... // other properties
ApplicationUsers = step.ApplicationUsers
.Where(applicationUser => ...)
.Select(applicationUser => new
{
...
})
.ToList(),
});
In my experience, whenever I think of performing a query with a of DbSets using entity framework, whether it is in a many-to-many, a one-to-many or a one-to-one relation, the query can almost always be created using the collections instead of a join. They look simpler, they are better to understand and thus better to maintain.

Categories