I have some entities similar to the following:
public class Teacher
{
public int TeacherId { get; set; }
public string TeacherName { get; set; }
public int Age { get; set; }
public string CurrentGrade { get; set; }
public int YearsTeaching { get; set; }
pubilc ICollection<StudentFeedback> StudentFeedback { get; set; }
}
public class StudentFeedback
{
public int StudentFeedBackId { get; set; }
public int TeacherId { get; set; }
public int StudentId { get; set; }
public string Feedback { get; set; }
public Teacher Teacher { get; set; }
public Student Student { get; set; }
}
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public int Age { get; set; }
public string CurrentGrade { get; set; }
}
I have a repository with a method where I want to return a teacher or list of teachers where the StudentFeedback returned belongs to the student who is looking at it (the studentId is stored in a token).
So, lets say I have the Teacher (teacherId) and a Student (userId) who is hitting the API endpoint. I currently have the following:
int teacherId = 2;
int userId = 20; // This is the currently logged in user, extracted from the token.
var query = _context.Teachers.AsQueryable();/* _context is the DataContext*/
query = query.Where(t => p.TeacherId == teacherId);
query = query.Where(u => u.StudentFeedback.Any(x => x.StudentId == userId));
However this is still returning all StudentFeedback from all Students, so long as the userId (student) has feedback provided for the teacher in question. I had a look at the query that gets executed and the problem is that the studentId predicate is in the wrong place. A very rough version of the query is:
SELECT *
FROM ( SELECT t.*
FROM dbo.Teachers t
WHERE (t.TeacherId = 2)
AND EXISTS ( SELECT 1
FROM dbo.StudentFeedback t0
WHERE (t.TeacherId = t0.TeacherId)
AND (t0.StudentId = 20))) p
LEFT JOIN dbo.StudentFeedback sf ON p.TeacherId = sf.TeacherId
Whereas it should be something like
SELECT *
FROM ( SELECT t.*
FROM dbo.Teachers t
WHERE (t.TeacherId = 2)) p
LEFT JOIN dbo.StudentFeedback sf ON p.TeacherId = sf.TeacherId
AND sf.StudentId = 20
but I don't know how to make that happen. Is there something wrong with the IQueryable predicates I've setup or have I missed some logic in the modelBuilder within the datacontext?
Thank-you.
Edit: I am using Entity Framework Core 5.0.2 and I am also using Automapper with the following code:
query.ProjectTo<TeacherDTO>(_mapper.ConfigurationProvider).AsNoTracking()
Here is what I am getting back currently:
[
{
"teacherid": 2,
"teacherName": "Jane Smith",
"age": 35,
"currentGrade": "One",
"yearsTeaching": 12,
"studentFeedback": [
{
"studentFeedBackId": 12,
"teacherId": 6,
"studentId": 20,
"feedback": "Ms Smith is my favorite teacher"
} ,
{
"studentFeedBackId": 16,
"teacherId": 6,
"studentId": 43,
"feedback": "Ms Smith was so kind to me"
} ,
{
"studentFeedBackId": 21,
"teacherId": 6,
"studentId": 89,
"feedback": "Thank you Mrs Smith for being my teacher. I learned a lot."
}
]
}
]
here is what I want to be getting back:
[
{
"teacherid": 2,
"teacherName": "Jane Smith",
"age": 35,
"currentGrade": "One",
"yearsTeaching": 12,
"studentFeedback": [
{
"studentFeedBackId": 12,
"teacherId": 6,
"studentId": 20,
"feedback": "Ms Smith is my favorite teacher"
}
]
}
]
Thanks #LucianBargaoanu for pointing me in the right direction by saying to have the where in the mapping itself. The solution is to use Parameterization when using Automapper:
The code from this pages shows an example:
string currentUserName = null;
cfg.CreateMap<Course, CourseModel>()
.ForMember(m => m.CurrentUserName, opt => opt.MapFrom(src => currentUserName));
and then
dbContext.Courses.ProjectTo<CourseModel>(Config, new { currentUserName = Request.User.Name });
If you want a join you should use Join method in your linq statement. See https://www.tutorialsteacher.com/linq/linq-joining-operator-join . You got exactly what you wrote in your query. Where(u => u.StudentFeedback.Any(x => x.StudentId == userId));
.Any translates to exists.
If you use Net5 EF you can just add a Students property to the Teacher class:
public class Teacher
{
.....
pubilc ICollection<Student> Students { get; set; }
pubilc ICollection<StudentFeedback> StudentFeedbacks { get; set; }
}
You can use query this way:
var query = _context.Teachers.Include(i=> i.StudentFeedbacks)
.Where(t =>
t.TeacherId == teacherId
&& t.StudentFeedbacks.Any(x => x.StudentId == userId))
.ToArray();
Related
I'm new at LINQ and need your advice.
I have 2 tables like these:
public class Subjects
{
public Subjects()
{
Classes = new List<Classes>();
}
public int Id { get; set; }
public string SubjectName { get; set; }
public virtual List<Classes> Classes { get; set; }
}
public class Classes
{
public Classes()
{
Subjects = new List<Subjects>();
}
public int Id { get; set; }
public string ClassName { get; set; }
public virtual List<Subjects> Subjects { get; set; }
}
And Entity Framework create SubjectClasses
public SubjectsMap()
{
this.HasKey(s => s.Id);
this.Property(s => s.SubjectName)
.IsRequired()
.HasMaxLength(50);
this.ToTable("Subjects");
this.HasMany(c => c.Classes)
.WithMany(s => s.Subjects)
.Map(cs =>
{
cs.MapLeftKey("SubjectId");
cs.MapRightKey("ClassId");
cs.ToTable("SubjectClasses");
});
}
Subject---- SubjectClasses ----- Classes
My Context doesn't have SubjectClasses, so I need to convert the SQL query to Linq or Lambda. But Linqpad doesn't help me or I can't use it. I simply want to take Firstname, SubjectName
SELECT st.Firstname, s.SubjectName
FROM SubjectClasses sc
INNER JOIN Subjects s on s.Id = sc.SubjectId
INNER JOIN Students st on st.ClassId = sc.ClassId
WHERE sc.ClassId = 3
It's working.
(from st in context.Students
from s in context.Subjects
join c in context.Classes
on new { stuId = st.ClassId } equals new { stuId = c.Id }
select new ComplexExamResult
{
Id = c.Id,
Firstname = st.Firstname,
SubjectName = s.SubjectName
}).Where(c => c.Id == classId).AsNoTracking().ToList();
I have User, Contact and ContactOfUser entities in an ASP.NET Core API project. I want to filter users based on input over these tables.
My entity classes are like this:
public class User
{
public int Id { get; set; }
[MaxLength(50)]
public string Name { get; set; }
[MaxLength(50)]
public string Surname { get; set; }
[MaxLength(60)]
public string Username { get; set; }
}
public class Contact
{
public int Id { get; set; }
[MaxLength(50)]
public string Value{ get; set; }
}
public class ContactOfUser
{
public int Id { get; set; }
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public int ContactId { get; set; }
}
I want to get filtered users based on this FilterModel object:
public class FilterModel
{
public string Name { get; set; }
public string Surname { get; set; }
public string Username { get; set; }
public List<int> ContactId { get; set; }
}
How can I make this filtering process in Entity Framework with Linq methods considering that don't apply special filter data when that data will be accepted as null?
I did something like that method but it is not properly working:
List<User> GetFilteredUsers(FilterModel filter)
{
var query1 = dbContext.Users
.Where(u => u.Name.Contains(filter.Name ?? string.Empty) &&
u.Surname.Contains(filter.Surname ?? string.Empty) &&
u.Username.Contains(filter.Username ?? string.Empty));
var query2 = from u in query1
join cu in dbContext.ContactOfUsers on u.Id equals cu.UserId
into res
from item in res.DefaultIfEmpty()
where filter.Contacts.Contains(item.ContactId)
select new InitialUserModel
{
Id = u.Id,
Name = u.Name,
Surname = u.Surname,
Username = u.Username
};
}
You can achieve it in this way
Using GroupBy to get userId and ContactIds accordingly.
var userContactIds = _dbContext.ContactOfUser.GroupBy(p => p.UserId).Select(g => new { UserId = g.UserId, ContactIds = g.Select(p => p.ContactId).ToList() });
Get the result :
var result = _dbContext.User.Select(p => new FilterModel
{
Name = p.Name, Surname = p.Surname, Username = p.Username,
ContactId = userContactIds.Where(c => p.Id == c.UserId).ToList()
});
Updated
List<User> GetFilteredUsers(FilterModel filter)
{
return (from u in _dbContext.User
join c in _dbContext.ContactOfUser on u.Id equals c.UserId
join fContactId in filter.ContactId on c.ContactId equals fContactId
where u.Name == filter.Name && u.Surname == filter.Surname && u.Username == filter.Username
select u).ToList();
}
How can I tell EF Core to not insert related data on post ?
this are my models for Deposito and Sucursal
public class IDeposito
{
[Key]
public int Id { get; set; }
public string Descrip { get; set; }
public string Alias { get; set; }
public Boolean Activo { get; set; }
public ISucursal Sucursal { get; set; }
}
public class ISucursal
{
[Key]
public int Id { get; set; }
public string Descrip { get; set; }
public string Alias { get; set; }
public Boolean Activo { get; set; }
}
This is my controller
[HttpPost]
public IActionResult Create([FromBody] IDeposito postData)
{
if (postData == null)
{
return BadRequest();
}
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);
}
When I post this model
{
"Descrip": "Neuquen",
"Alias": "NQN",
"Activo": true,
"Sucursal": {
"Id": 1,
"Descrip": "Comodoro Rivadavia",
"Alias": "CR",
"Activo": true
}
}
It fails because it tries to insert into table "Sucursal", although there already exists a "Sucursal" with id: 1.
Is there anyway I can tell EF Core to not update the related tables ? thank you very much
First approach if you want connect new IDeposito to existing ISucursal with Id = 1 then use:
postData.Sucursal = _context.Sucursals.Find(postData.Sucursal.Id);
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);
If you want add only IDeposito without any sucursal:
postData.Sucursal = null;
_context.Depositos.Add(postData);
_context.SaveChanges();
return CreatedAtRoute("GetDeposito", new { ID = postData.Id }, postData);
I'm trying to define an index in RavenDb that uses the output of another index as it's input but I can't get it to work.
I have the following entities & indexes defined.
SquadIndex produces the result I expect it to do but SquadSizeIndex doesn't even seem to execute.
Have I done something wrong or is this not supported?
class Country
{
public string Id { get; private set; }
public string Name { get; set; }
}
class Player
{
public string Id { get; private set; }
public string Name { get; set; }
public string CountryId { get; set; }
}
class Reference
{
public string Id { get; set; }
public string Name { get; set; }
}
class SquadIndex : AbstractIndexCreationTask<Player, SquadIndex.Result>
{
public SquadIndex()
{
Map = players => from player in players
let country = LoadDocument<Country>(player.CountryId)
select new Result
{
Country = new Reference
{
Id = country.Id,
Name = country.Name
},
Players = new[]
{
new Reference
{
Id = player.Id,
Name = player.Name
}
}
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
Players = g.SelectMany(x => x.Players)
};
}
internal class Result
{
public Reference Country { get; set; }
public IEnumerable<Reference> Players { get; set; }
}
}
class SquadSizeIndex : AbstractIndexCreationTask<SquadIndex.Result, SquadSizeIndex.Result>
{
public SquadSizeIndex()
{
Map = squads => from squad in squads
select new Result
{
Country = squad.Country,
PlayerCount = squad.Players.Count()
};
Reduce = results => from result in results
group result by result.Country
into g
select new Result
{
Country = g.Key,
PlayerCount = g.Sum(x => x.PlayerCount)
};
}
internal class Result
{
public Reference Country { get; set; }
public int PlayerCount { get; set; }
}
}
No, you can't. The output of indexes are not documents to be indexed.
You can use the scripted index results to chain indexes, but that isn't trivial.
public class Feedback
{
public virtual int Id { get; private set; }
public virtual string ContentText { get; set; }
public virtual DateTime FeedbackDate { get; set; }
public virtual Student student { get; set; }
}
My Feedback Class.
public class Student
{
public virtual int Id { get; private set; }
public virtual int NumberOfStars { get; set; }
public virtual IList<Feedback> Feedbacks { get; private set; }
public Student()
{
Feedback = new List<Feedbacks>();
}
}
My Student Class
public class Course
{
public virtual int Id { get; set; }
// bla bla bla
public virtual IList<Student> Students { get; private set; }
public Course()
{
Students = new List<Student>();
}
public IList<Student> SortBy(string type)
{
// some other sorting
else if (type.Equals("popular")){
sortedStudents = session.CreateCriteria(typeof(Student))
.CreateAlias("Student", "s")
.CreateAlias("s.Feedback", "f")
.AddOrder(Order.Desc( -------- ))
.List();
}
return (IList<Student>) sortedStudents;
}
}
My Course class
I want sort students in a Course with method SortBy :
if type is x i will sort with following rule
(Students.Feedback.Count)*5 + Student.NumberOfStars)
How ?
HQL:
List<Student> sortedStudents = session
.CreateQuery(
#"from Students student
where student.Course == :course
order by size(student.Feedbacks) * 3 + student.NumberOfStars")
.SetEntity("course", course)
.List<Student>();
size is a HQL function. See the chapter "Expressions" in the NH documentation.
You may also select it with Criteria and sort it with Linq.
Edit
Just saw that you use it in a property and you may have the students already in memory.
You don't need a query, just to sort it.
return students
.OrderBy(x => x.Feedback.Count * 5 + x.NumberOfStars)
.ToList();
Query with LINQ
IList sortedStudents = (from student in this.Students
where student.Course == this
orderby (student.Feedbacks.Count*3 + student.NumberOfStars)
select student).ToList();