Joining data with Entity framework - entity-framework

I'm trying to retrieve all employments linked to employees with a given UserID + all employments linked to managers with a given UserID. I'm joining the sets of data the way SQL UNION would do. This is the code I wrote for this purpose, but it seems that INCLUDEs are cumulative (the second works on the set of data retrieved by the first), which is not my intention (the second set of data has to add to the first):
var list = await db.Employment.AsNoTracking()
.Where(x => x.Active)
.Include(x => x.Employee).Where(x => x.Employee.UserID == UserID)
.Include(x => x.Manager).Where(x => x.Manager.UserID == UserID)
.ToListAsync();
Employment model
public class Employment : IHasID, IValidatableObject
{
public int ID { get; set; }
public int EmployeeID { get; set; }
public Employee Employee { get; set; }
public bool Active { get; set; }
public int? ManagerID { get; set; }
public Employee Manager { get; set; }
}
Employee model
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? UserID { get; set; }
public User User { get; set; }
public bool Active { get; set; }
[InverseProperty("Employee")]
public List<Employment> EmploymentEmployee { get; set; }
[InverseProperty("Manager")]
public List<Employment> EmploymentManager { get; set; }
}
Later, I discovered that there's a Union extension, so I tried to use it:
var list = await
db.Employment.Where(x => x.Active).Include(x => x.Employee)
.Include(x => x.Manager).Where(x => x.Employee.UserID == UserID)
.Union(
db.Employment.Where(x => x.Active).Include(x => x.Employee)
.Include(x => x.Manager).Where(x => x.Manager.UserID == UserID))
.ToListAsync();
...but this results in an exception: InvalidOperationException: Warning as error exception for warning 'CoreEventId.IncludeIgnoredWarning': The Include operation for navigation: 'x.Employee' was ignored because the target navigation is not reachable in the final query results.

Related

Entity Framework Core 3 : Pulling data from multiple sources using join

I basically have three tables that I need to query information to get PersonNotes. I am using Entity Framwork Core 3.
Person Table
PersonNote Table
PersonNoteAttachment Table
One person can have many personnotes and one personnote can contain many PersonNoteAttachment.
I need Person table to get the FirstName and LastName which is mapped to the AuthorName in the PersonNote User data model. You can see the mapping section which shows the mapping.
DataModels
namespace Genistar.Organisation.Models.DataModels
{
[Table(nameof(PersonNote), Schema = "common")]
public class PersonNote
{
public int Id { get; set; }
public int PersonId { get; set; }
[ForeignKey("PersonId")]
public Person Person { get; set; }
public string Note { get; set; }
public int AuthorId { get; set; }
[ForeignKey("AuthorId")]
public Person Author { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
}
}
namespace Genistar.Organisation.Models.DataModels
{
[Table(nameof(PersonNoteAttachment), Schema = "common")]
public class PersonNoteAttachment
{
public int Id { get; set; }
public int PersonNoteId { get; set; }
[ForeignKey("PersonNoteId")]
public PersonNote PersonNote { get; set; }
public string Alias { get; set; }
public string FileName { get; set; }
public string MimeType { get; set; }
public int Deleted { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
}
}
User Model - This is the model that I am returning to the client application
namespace Genistar.Organisation.Models.User
{
[Table(nameof(PersonNote), Schema = "common")]
public class PersonNote
{
public int Id { get; set; }
public int PersonId { get; set; }
public string Note { get; set; }
public int AuthorId { get; set; }
public string AuthorName { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
}
}
Mapping
CreateMap<Genistar.Organisation.Models.DataModels.PersonNote, Genistar.Organisation.Models.User.PersonNote>()
.ForMember(t => t.Id, opt => opt.MapFrom(s => s.Id))
.ForMember(t => t.PersonId, opt => opt.MapFrom(s => s.PersonId))
.ForMember(t => t.AuthorName, opt => opt.MapFrom(s => s.Author.FirstName + " " + s.Author.LastName))
.ForMember(t => t.Note, opt => opt.MapFrom(s => s.Note))
.ForMember(t => t.AuthorId, opt => opt.MapFrom(s => s.AuthorId))
.ForMember(t => t.CreatedBy, opt => opt.MapFrom(s => s.CreatedBy))
.ForMember(t => t.Created, opt => opt.MapFrom(s => s.Created));
The following query works but is only pulling data from Person and PersonNote table. I am looking at getting the PersonNoteAttachment as well. How do I do that ? I would basically need FileName & MimeType
field populated in User.PersonNote model. If you see above I have created a PersonNoteAttachment data model
Repository
public IQueryable<PersonNote> GetPersonNotes(int personId)
{
var personNotes = _context.PersonNotes.Include(x => x.Person).Include(x=> x.Author).Where(p => p.PersonId == personId);
return personNotes;
}
API :
[FunctionName(nameof(GetPersonNote))]
[UsedImplicitly]
public Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "person-note/{id}")] HttpRequest req,
int id) => _helper.HandleAsync(async () =>
{
//await _helper.ValidateRequestAsync(req, SecurityPolicies.ViewNotes);
var personNotes = await _organisationRepository.GetPersonNotes(id).ProjectTo<PersonNote>(_mapper.ConfigurationProvider).ToListAsync();
return new OkObjectResult(personNotes);
});
My approach was to do it the following way in the repository but I need to return the PersonNote datamodel in the repository. I cannot add those additional fields in the model because it say invalid columns.How do I approach this ?
var personNotes = _context.PersonNotes
.Include(x => x.Person)
.Include(x => x.Author)
.Where(p => p.PersonId == personId)
.Join(_context.PersonNotesAttachments, c => c.Id, cm => cm.PersonNoteId, (c, cm) => new
{
cm.PersonNote.Id,
cm.PersonNote.PersonId,
cm.PersonNote.Person,
cm.PersonNote.Note,
cm.FileName,
cm.MimeType,
cm.Alias,
cm.PersonNote.AuthorId,
cm.PersonNote.CreatedBy,
cm.PersonNote.Created
});
I have resolved the issue
I just had to add the following line in PersonNote datamodel
public PersonNoteAttachment PersonNoteAttachment { get; set; }
Added the new fields to the PersonNote usermodel and did the following mapping
.ForMember(t => t.FileName, opt => opt.MapFrom(s => s.PersonNoteAttachment.FileName))
.ForMember(t => t.MimeType, opt => opt.MapFrom(s => s.PersonNoteAttachment.MimeType))
.ForMember(t => t.Alias, opt => opt.MapFrom(s => s.PersonNoteAttachment.Alias))

How to bring name from another context by using foreign key?

I'm tying to write an EntityFramework query to bring hospital name by hospital ID from Hospitals Context to Departments context.I tried couple of things like join tables etc. but I couldn't complete to write that correct query.Here my models and context below
Models
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
}
Context
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Hospital> Hospitals { get; set; }
public DbSet<Department> Departments { get; set; }
}
Above you can see that model Department has HospitalId to connect Hospital table.After join I want to get that Hospital Name where department belongs to.Result should be department ID,department Name and its Hospital Name .
My Final Try
public async Task<IEnumerable<Department>> GetDepartment(string input)
{
var departmentWithHospital = _context.Departments
.Where(d => d.Hospital.Id == d.HospitalId)
.Include(d => d.Hospital)
.Select(d => new {
departmentId = d.Id,
departmentName = d.Name,
hospitalName = d.Hospital.Name
});
return await departmentWithHospital;
// Compiler Error:doesnt contain a definition for GetAwaiter and no
//accesible extension for GetAwaiter....
}
Three points to note:
1.The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. like below:
var hospital =await _context.Hospitals.ToListAsync();
return hospital;
2.The relationships between Hospital and Department is one-to-many , you could refer to Relationships to design your model as follows:
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
public Hospital Hospital { get; set; }
}
3.You want to return a new object list which contains department ID,department Name and its Hospital Name, but your return type of the method is IEnumerable<Department> .So you could directly return a Department collection or define a ViewModel with the properties you want
Return type :IEnumerable<Department>
var departmentWithHospital =await _context.Departments
.Include(d => d.Hospital)
.Where(d => d.HospitalId == hospitalId).ToListAsync();
return departmentWithHospital;
DepartmentWithHospital ViewModel
public class DepartmentWithHospital
{
public int departmentId { get; set; }
public string departmentName { get; set; }
public string hospitalName { get; set; }
}
public async Task<IEnumerable<DepartmentWithHospital>> GetDepartment(int hospitalId)
{
var departmentWithHospital =await _context.Departments
.Include(d => d.Hospital)
.Where(d => d.HospitalId == hospitalId)
.Select(d => new DepartmentWithHospital
{
departmentId = d.Id,
departmentName = d.Name,
hospitalName = d.Hospital.Name
}).ToListAsync();
return departmentWithHospital;
}
You need a Hospital in your Departments class, and a collection of Departments in your Hospital class.
public class Hospital
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public virtual ICollection<Department> Departments { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int HospitalId { get; set; }
public Hospital Hospital { get; set; }
}
For the query, try this (Been awhile since I messed with EF, and this is for EF6). I can't remember if you need the include or not, but this should get you an anonymous object with the properties you requested.
This code is not tested.
var departmentWithHospital = context.Departments
.Where(d => d.Hospital.Id == hospitalId)
.Include(d => d.Hospital)
.Select(d => new {
departmentId = d.Id,
departmentName = d.DepartmentName,
hospitalName = d.Hospital.HospitalName
})
.ToList();
If I understood your question correctly, you are looking for this:
var departmentId = "123";
var result = from department in _context.Departments
join hospital in _context.Hospitals
on hospital.Id equals department.HospitalId
where department.Id == departmentId
select new
{
DepartmentID = departmentId,
DepartmentName = department.Name,
HospitalName = hospital.Name
};

EF core join using include

I am trying to join related data using Include but I am having some difficulties. My models are the following
public partial class GBTObject
{
public uint Id { get; set; }
public uint OrganizationId { get; set; }
public string Name { get; set; }
public virtual Device Device { get; set; }
public uint? DeviceId { get; set; }
}
public partial class Device
{
public uint Id { get; set; }
public uint OrganizationId { get; set; }
public string UUID { get; set; }
public bool? Enabled { get; set; }
}
public partial class DeviceState
{
public uint Id { get; set; }
public uint OrganizationId { get; set; }
public uint DeviceId { get; set; }
public string State { get; set; }
public DateTime? Timestamp { get; set; }
public byte? Event { get; set; }
public ulong TotalDistance { get; set; }
public string UserAgent { get; set; }
}
var data = _context.GBTObject
.Where(x => x.DeviceId != null && x.OrganizationId == _user.OrganizationId)
.Include(x => x.Device)
.Include(x => x.State)
Then I tried to create a shadow property inside Device
[ForeignKey("Id")]
public virtual DeviceState State{ get; set; }
var data = _context.GBTObject
.Where(x => x.DeviceId != null && x.OrganizationId == _user.OrganizationId)
.Include(x => x.Device)
.ThenInclude(x => x.State)
But it doesn't work cause the it joins using the the DeviceId from GBTObject with Id from DeviceState. Changing the foreign key to DeviceId results in weird naming errors(it names the GBTObject.DeviceId to GBTObject.DeviceId1 and then it complains that it doesn't exist and looks like a bug).
Am I doing this wrong?
Try the following:
var data = from gbt in _context.GBTObject
join ds in _context.DeviceState
on gbt.DeviceId equals ds.DeviceId
where gbt.DeviceId != null && gbt.OrganizationId == _user.OrganizationId
select gbt;
Also check this link for further info about joining in LINQ:
What is the syntax for an inner join in LINQ to SQL?

Why this exception "Operation is not valid due to the current state of the object" in Entity Framework core occured?

I have simple Forum application that each topic has many post, and each post may liked by several users. application Entity Models are as following:
public class ForumTopic
{
public int ForumTopicId { get; set; }
public string Subject { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<ForumPost> ForumPosts { get; set; }
}
public class ForumPost
{
public int ForumPostId { get; set; }
public string Message { get; set; }
public int ForumTopicId { get; set; }
public virtual ForumTopic ForumTopic { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<ForumPostFavorite> ForumPostFavorites { get; set; }
}
public class ForumPostFavorite
{
public string UserId { get; set; }
public User User { get; set; }
public int ForumPostId { get; set; }
public ForumPost ForumPost { get; set; }
}
I want take current user favorite posts as following. but this query throws "Operation is not valid due to the current state of the object" Exception. I am sure that this query run in entity framework 6 without exception, but in Entity Framework core this exception occurs.
public IActionResult Favorite()
{
var userId = _userManager.GetUserId(User);
var list = _dbContext.ForumPostFavorites
.Where(e => e.UserId == userId)
.Select(e => e.ForumPost)
.Take(10)
.Select(e => new ForumPostFavoriteVm
{
ForumPostId = e.ForumPostId,
ForumTopicId = e.ForumTopicId,
Message = e.Message,
Subject = e.ForumTopic.Subject,
}).ToList();
return View(list);
}
When I remove this line "Subject = e.ForumTopic.Subject" from query, the query runs without any exceptions.
Try to put Skip() & Take() after projection:
var list = _dbContext.ForumPostFavorites
.Where(e => e.UserId == userId)
.Select(e => e.ForumPost)
.Select(e => new ForumPostFavoriteVm
{
ForumPostId = e.ForumPostId,
ForumTopicId = e.ForumTopicId,
Message = e.Message,
Subject = e.ForumTopic.Subject,
})
.Take(10)
.ToList();

Query does not return child collections

I still struggle with this, why each of 'Category' items returns null 'Task' collections. I do have data in the database, what am I missing?
public class ApplicationUser : IdentityUser
{
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public DateTime Timestamp { get; set; }
public ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int TaskId { get; set; }
public string Name { get; set; }
public DateTime Timestamp { get; set; }
}
And here is the query:
public IEnumerable<Category> GetAllForUser(string name)
{
return _ctx.Users.Where(x => x.UserName == name)
.SelectMany(x => x.Categories)
.Include(x => x.Tasks).ToList();
}
Your query is falling into Ignored Includes case:
If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.
As explained in the link, if you add the following to your DbContext OnConfiguring:
optionsBuilder.ConfigureWarnings(warnings => warnings.Throw(CoreEventId.IncludeIgnoredWarning));
then instead null collection you'll get InvalidOperationException containing something like this inside the error message:
The Include operation for navigation: 'x.Tasks' was ignored because the target navigation is not reachable in the final query results.
So how to fix that? Apparently the requirement is to start the query from the entity for which you want to add includes. In your case, you should start from _ctx.Categories. But in order to apply the same filter, you need to add the reverse navigation property of the Application.Users to the Category class:
public class Category
{
// ...
public ApplicationUser ApplicationUser { get; set; }
}
Now the following will work:
public IEnumerable<Category> GetAllForUser(string name)
{
return _ctx.Categories
.Where(c => c.ApplicationUser.UserName == name)
.Include(c => c.Tasks)
.ToList();
}
Try this:
public IEnumerable<Category> GetAllForUser(string name)
{
return _ctx.Users
.Include(u => u.Categories)
.Include(u => u.Categories.Select(c => c.Tasks))
.Where(x => x.UserName == name)
.SelectMany(x => x.Categories)
.ToList();
}
public virtual ICollection<Task> Tasks { get; set; }