I have a Test entity:
public class Test
{
public Test()
{
PupilsTests = new HashSet<PupilTest>();
TestTypeTests = new HashSet<TestTypeTest>();
SchoolclassTests = new HashSet<SchoolclassTest>();
SubjectTests = new HashSet<SubjectTest>();
}
public int Id { get; set; }
public DateTime Date { get; set; }
public int Number { get; set; }
public ISet<PupilTest> PupilsTests { get; set; }
public ISet<TestTypeTest> TestTypeTests { get; set; }
public ISet<SchoolclassTest> SchoolclassTests { get; set; }
public ISet<SubjectTest> SubjectTests { get; set; }
public GradingKey ScoreGradeKey { get; set; }
public Schoolyear Schoolyear { get; set; }
public int SchoolyearId { get; set; }
}
I need to fetch all Test entities filtered by schoolyearId including the junction tables SchoolclassTests, SubjectTests, TestTypeTests.
But with these junction tables I also have to include their principal tables Schoolclass, Subject, TestType.
This is what I tried:
public async Task<IEnumerable<Test>> GetTestsAsync(int schoolyearId)
{
return await context.Tests.Where(t => t.SchoolyearId == schoolyearId)
.Include(t => t.SchoolclassTests)
.Include(t => t.SubjectTests)
.Include(t => t.TestTypeTests)
// How to include all 3 Principal tables? ThenInclude does not workk
// over all the 3...
.ToListAsync();
}
Whatever combinations I try for .Include or ThenInclude I never get all 3 principal tables with the junction tables in ONE query.
How can I do that?
Using Select method, you can include the principal tables of your join tables:
public async Task<IEnumerable<Test>> GetTestsAsync(int schoolyearId)
{
return await context.Tests.Where(t => t.SchoolyearId == schoolyearId)
.Include(t => t.SchoolclassTests.Select(s => s.Schoolclass))
.Include(t => t.SubjectTests.Select(s => s.Subject))
.Include(t => t.TestTypeTests.Select(t => t.TestType))
.ToListAsync();
}
Related
I have the following tables:
public class Team
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<UserTeam> UserTeams { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<UserTeam> UserTeams { get; set; }
}
public class UserTeam
{
public long UserId { get; set; }
public User User { get; set; }
public long TeamId { get; set; }
public Team Team { get; set; }
}
the many to many relationship is defined in context:
modelBuilder.Entity<UserTeam>()
.HasKey(bc => new { bc.UserId, bc.TeamId });
modelBuilder.Entity<UserTeam>()
.HasOne(bc => bc.User)
.WithMany(b => b.UserTeams)
.HasForeignKey(bc => bc.UserId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<UserTeam>()
.HasOne(bc => bc.Team)
.WithMany(c => c.UserTeams)
.HasForeignKey(bc => bc.TeamId)
.OnDelete(DeleteBehavior.Restrict);
I am trying to delete some users from a team with the following code:
public async Task RemoveUsersFromTeam(int teamId, List<long> users)
{
Team existingTeam = await dbContext.Team.Include(x => x.UserTeams).FirstOrDefaultAsync(x => x.Id == teamId);
foreach (var user in users)
{
existingTeam.UserTeams.Remove(new UserTeam { UserId = user });
}
await dbContext.SaveAsync();
}
but this query is not deleting the users that I pass. Anyone knows why this happens?
You can delete objects by Id using the new and attach method, or by passing the actual entity.
Passing Entity
public async Task RemoveUsersFromTeam(int teamId, List<long> userIds)
{
Team existingTeam = await dbContext.Team.Include(x => x.UserTeams).FirstOrDefaultAsync(x => x.Id == teamId);
foreach (var userId in userIds)
{
var userTeam = existingTeam.UserTeams.FirstOrDefault(x => x.UserId == userId);
if(userTeam != null)
{
existingTeam.UserTeams.Remove(userTeam);
}
}
await dbContext.SaveAsync();
}
Delete By Id
public async Task RemoveUsersFromTeam(int teamId, List<long> userIds)
{
Team existingTeam = await dbContext.Team.FirstOrDefaultAsync(x => x.Id == teamId);
foreach (var userId in userIds)
{
var userTeam = new UserTeam { UserId = userId });
dbContext.UserTeams.Attach(userTeam);
existingTeam.UserTeams.Remove(userTeam);
}
await dbContext.SaveAsync();
}
Option two does not require selecting UserTeams from the database and will be slightly more efficient in that respect. However option one may be more understandable. You should choose which best fits your situation.
Personally I prefer option two, as include will select the whole entity and in some cases that could be a lot more than necessary.
I am working with the following technologies: C#, SQL Server, ASP.NET and Entity Framework and Linq.
I have a many to many relation , using eager load. I want to get all the courses where a student is inscribed. As you can see I have a one to many relation from student to inscribe table.
The model classes:
public class Courses
{
[Required]
public int Id { get; set; }
//more properties here
public student stud { get; set; }
}
public class Enroll
{
[Key]
public intId { get; set; }
//properties here
[Required]
public string StudentId{ get; set; }
public Courses Courses{ get; set; }
}
public class student{
public intId { get; set; }
//other properties
public Inscripe Inscription {get;set}
}
This is what my controller:
public IEnumerable<course> GetCoursesStudent(Int studentId)
{
//some code here to validate
var result = _dbContext
.Enroll.Include(c => c.Courses)
.Where(c => c.StudentId == studentId)
.SelectMany(c => c.Courses).ToList();
}
problem :
I receive an error from the SelectMany: the type aregument for method Queribly.selectMany(IQueryableExpression>> can not be infered from the usage.
How can I fix it?
The type you specified for the IEnumerable is wrong. It should be "Courses" instead of "course":
public IEnumerable<Courses> GetCoursesStudent(Int studentId)
{
var result = _dbContext
.Enroll
.Include(c => c.Courses)
.Where(c => c.StudentId == studentId)
.SelectMany(c => c.Courses)
.ToList();
}
And the "Courses" property of Enroll class should be an enumerable:
public class Enroll
{
[Key]
public intId { get; set; }
[Required]
public string StudentId { get; set; }
public IEnumerable<Courses> Courses { get; set; }
}
I need to create a eager loading query that will perform these tasks:
Get parent Entity by id
Filter child entities by criteria
Sort child list of entities
My non-eager query looks like this:
var company = _dbContext.Companies.FirstOrDefault(c => c.Id == companyId);
if (company != null)
{
company.CompanyProducts =
company.CompanyProducts
.Where(cp => cp.IsBuyable && cp.Product.IsPublished)
.OrderByDescending(c => c.Product.PublishDate)
.ThenBy(c => c.Product.Name)
.ToList();
}
return company;
Where Entities have this structure:
public class Company
{
public long Id { get; set; }
public string Name { get; set; }
[ForeignKey("CompanyId")]
public virtual ICollection<CompanyProduct> CompanyProducts { get; set; }
}
public class CompanyProdcut
{
public long Id { get; set; }
public long CompanyId { get; set; }
public long ProductId { get; set; }
public bool IsBuyable { get; set; }
public virtual Company Company { get; set; }
public virtual Product Product { get; set; }
}
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime PublishDate { get; set; }
public bool IsPublished { get; set; }
[ForeignKey("ProductId")]
public virtual ICollection<CompanyProduct> CompanyProducts { get; set; }
}
public class MyDbContext : DbContext
{
public MyDbContext() : base("name=connectionString")
{
Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}
public virtual IDbSet<Product> Products { get; set; }
public virtual IDbSet<Company> Companies { get; set; }
public virtual IDbSet<CompanyProduct> CompanyProducts { get; set; }
}
This is classic many to many relation that uses an entity in the middle to store addtional data.
Company -|---------|< CompanyProduct >|---------|- Product
How in this case I can rewrite the non-eager query to eager query, that will do everything in one SQL call? I tried to do it myself using the .Include() but I failed.
Any ideas?
I wouldn't consider it the best approach but you can have a .Include() statement in the two positions I have shown below. If you include it in the first query then I think you'd be fine to use your method for the second one but I havn't tested to not certain about that.
The reason why you can't use it in the positions in your example is becasue it can only be used on a IQueryable() object, of which Company and ICollection is not.
var _dbContext = new MyDbContext();
var company = _dbContext.Companies.Include("").SingleOrDefault(c => c.Id == companyId);
var products = _dbContext.Companies.Include("").SingleOrDefault(c => c.Id == companyId)
?.CompanyProducts
.Where(cp => cp.IsBuyable && cp.Product.IsPublished)
.OrderByDescending(c => c.Product.PublishDate)
.ThenBy(c => c.Product.Name)
.ToList();
if(company != null)
{
company.CompanyProducts = products;
}
return company;
var company = _dbContext.Companies.Include(x => x.CompanyProducts).Includex => x.CompanyProducts.Select(y => y.Company).Includex => x.CompanyProducts.Select(y => y.Product).FirstOrDefault(c => c.Id == companyId);
if (company != null)
{
company.CompanyProducts =
company.CompanyProducts
.Where(cp => cp.IsBuyable && cp.Product.IsPublished)
.OrderByDescending(c => c.Product.PublishDate)
.ThenBy(c => c.Product.Name)
.ToList();
}
return company;
To get the lambda include make sure to add reference to System.Data.Entity.
https://msdn.microsoft.com/en-us/library/dn176380(v=vs.113).aspx
I have three SQL tables that are represented by classes and I would like to have Entity Framework 6 join these tables so I get all the details of the Exam, Test and UserTest tables where the UserTest.UserID is 0 or X.
I have already set up a respository and this works for simple queries however I am unable to join the UserTest class in the LINQ at the bottom of the question.
Here's my classes:
public class Exam
{
public int ExamId { get; set; }
public int SubjectId { get; set; }
public string Name { get; set; }
public virtual ICollection<Test> Tests { get; set; }
}
public class Test
{
public int TestId { get; set; }
public int ExamId { get; set; }
public string Title { get; set; }
public virtual ICollection<UserTest> UserTests { get; set; }
}
public class UserTest
{
public int UserTestId { get; set; }
public string UserId { get; set; }
public int TestId { get; set; }
public int QuestionsCount { get; set; }
}
What I would like to do is to have a query that looks something like this:
var exams = _examsRepository
.GetAll()
.Where(q => q.SubjectId == subjectId)
.Include(q => q.Tests )
.Include(q => q.Tests.UserTests) // Error on this line
.ToList();
But it's not letting me include UserTests in VS2013.
Update:
Here is the query I first tried:
var userTests = _userTestsRepository
.GetAll()
.Include(t => t.Test)
.Include(t => t.Test.Exam)
.Where(t => t.UserId == "0" || t.UserId == userId);
This one seemed to work however when I looked at the output I saw something like this:
[{"userTestId":2,
"userId":"0",
"testId":12,
"test":{
"testId":12,"examId":1,
"exam":{
"examId":1,"subjectId":1,
"tests":[
{"testId":13,"examId":1,"title":"Sample Test1",
"userTests":[
{"userTestId":3,
"userId":"0",
Note that this starts to repeat and bring back a lot more data than I expected
That's because Tests is a collection and not just a single object, so it doesn't have a UserTests property. You use a lambda to specify grandchildren of multiple children rather than a single child:
var exams = _examsRepository
.GetAll()
.Where(q => q.SubjectId == subjectId)
.Include(q => q.Tests.Select(t => t.UserTests))
.ToList();
Note that there's no need for two Include calls because the children are implicitly included if you're including the grandchildren.
I have build reporting system where user can select data and display as report. Report is saved in three tables (split entity). But when I try to edit report and save again I get following error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker
My Entity:
public class Report
{
[Key]
public int ReportId { get; set; }
public string Title { get; set; }
public int? DateRange { get; set; }
public int Layout { get; set; }
public DateTime? DateFrom { get; set; }
public DateTime? DateTo { get; set; }
public int OwnerId { get; set; }
public DateTime DateCreated { get; set; }
public bool Active { get; set; }
public virtual List<ReportCharts> ReportCharts { get; set; }
public virtual List<ReportElements> ReportElements { get; set; }
}
My EF repository:
//Save Report to Database
public void Save(Report report)
{
assignSettingsToEntity(report);
assignElementsToEntity(report);
assignChartsToEntity(report);
int found = Reports
.Select(r => r.ReportId)
.Where(id => id == report.ReportId)
.SingleOrDefault();
if (found == 0)
{
context.Reports.Add(report);
}
else
{
context.Entry(report).State = EntityState.Modified; // Here I get error
}
context.SaveChanges();
}
My DBContext
class EFDbContext : DbContext
{
//Get Lines data from Lines table
public DbSet<Line> Lines { get; set; }
//Get Shifts data from Shifts table
public DbSet<Shift> Shifts { get; set; }
//Get list of Charts from Charts table
public DbSet<Graph> Graphs { get; set; }
//Get Reports data from Reports table
public DbSet<Report> Reports { get; set; }
// Report entity mapping
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Report>().Property(t => t.ReportId).HasColumnName("ReportId");
modelBuilder.Entity<Report>().Property(t => t.Title).HasColumnName("Title");
modelBuilder.Entity<Report>().Property(t => t.DateRange).HasColumnName("DateRange");
modelBuilder.Entity<Report>().Property(t => t.Layout).HasColumnName("Layout");
modelBuilder.Entity<Report>().Property(t => t.DateFrom).HasColumnName("DateFrom");
modelBuilder.Entity<Report>().Property(t => t.DateTo).HasColumnName("DateTo");
modelBuilder.Entity<Report>().Property(t => t.OwnerId).HasColumnName("OwnerId");
modelBuilder.Entity<Report>().Property(t => t.DateCreated).HasColumnName("DateCreated");
modelBuilder.Entity<Report>().Property(t => t.Active).HasColumnName("Active");
modelBuilder.Entity<Report>().HasMany(t => t.ReportElements).WithRequired().HasForeignKey(c => c.ReportId).WillCascadeOnDelete(true);
modelBuilder.Entity<Report>().HasMany(t => t.ReportCharts).WithRequired().HasForeignKey(p => p.ReportId).WillCascadeOnDelete(true);
modelBuilder.Entity<ReportElements>().Property(c => c.ElementName).HasColumnName("ElementName");
modelBuilder.Entity<ReportElements>().HasKey(c => new { c.ReportId, c.ElementName, c.Active });
modelBuilder.Entity<ReportCharts>().Property(p => p.ChartId).HasColumnName("ChartId");
modelBuilder.Entity<ReportCharts>().HasKey(c => new { c.ReportId, c.ChartId, c.Active });
}
}
Thanks for any help.
Entity Framework attaches every entity to one context and all changes can only be performed on the context it is attached to it, you are probably using multiple context. You should detach and attach entity obtained from different context.
Otherwise, the best practice is not to use multiple instances of context, only keep one context throughout your changes.
Sounds like the object was attached to another context and not detached.
//Save Report to Database
public void Save(Report report)
{
using(EFDbContext context=new EFDbContext ())
{
assignSettingsToEntity(report);
assignElementsToEntity(report);
assignChartsToEntity(report);
int found = context.Reports
.Select(r => r.ReportId)
.Where(id => id == report.ReportId)
.SingleOrDefault();
// Insert Flow
if (found == 0)
{
context.Reports.Add(report);
}
// Update flow
else
{
context.Reports.Attach(report);
context.Entry(report).State = EntityState.Modified;
}
context.SaveChanges();
}
}