Linq query to include items with on a join object - c#

I'm trying to get a collection of objects using a Linq query that would include child objects. I can do an include on the main table and get results. But if I do an include on one of the tables I join to, I do not get the object that should be returned by the include.
Here are my models:
public class RequestReviewViewModel
{
public Guid RequestId { get; set; }
public Guid ResourceToReviewId { get; set; }
public Guid ReviewRequiredId { get; set; }
public ReviewRequired ReviewRequired { get; set; }
}
public class Required : BaseEntity
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid RequiredId { get; set; }
[Display(Name = "Review Name")]
public int ReviewNameId { get; set; }
[ForeignKey("ReviewNameId")]
public ReviewName ReviewName { get; set; }
}
Here's the linq query I'm trying. The line Required.Include(revr => revr.ReviewName) doesn't seem to do anything:
var requests = (from req in Request.Include(rr => rr.Resource)
join revreq in Required.Include(revr => revr.ReviewName)
on req.ReviewRequiredId equals revreq.RequiredId
where req.IsActive
select new RequestReviewViewModel
{
RequestId = req.RequestId,
ResourceToReviewId = req.ResourceToReviewId,
ReviewRequiredId = req.ReviewRequiredId,
Required = revreq
};
requests.FirstOrDefault().Required.ReviewName.Dump();
While requests.FirstOrDefault().Required.ReviewNameId has a value, the ReviewName object is null. The relationship is in the database, and was created by Code First.

Your ReviewName is not declared virtual which enables eager/lazy loading (and automatic change-tracking to be complete). Add virtual and it should work:
public virtual ReviewName ReviewName { get; set; }

Related

How to Map anonymous object in Auto Mapper

Am trying to Map an Anonymous object in auto mapper but am not getting how to do that. Please find my requirement below
Am getting some data from joining 2 tables with only one common column(Id). Am getting Anonymous type data from this query.
var query = (from _vdata in Table1
join entityFind in Table2 on _vdata.id equals entityFind.id
select new { entityFind.FamilyName, entityFind.LastLogin, entityFind.GivenName,
entityFind.Email, entityFind.EmailVerified, entityFind.Uuid, _vdata.Role,
_vdata.Payers, _vdata.Accounts, _vdata.ModifiedOn }).ToList();
Am getting Anonymous data from above query. I have some more list of data in another variable i need to add those data to this list with limited columns having 4-5 columns.
How to do mapping in this situation in AutoMapper or in any other technique
thanks
Since all anonymous types derived from System.Object, I found a solution (workaround) to add mapping from object to your destination type
//Allow to map anonymous types to concrete type
cfg.CreateMap(typeof(object), typeof(ExternalCandle),
MemberList.None);
But please note that for most scenarios this is not the correct solution
For example, if you want to map ORM types - go with this way: Queryable Extensions
I guess that Jimmy bogard won't recommend this solution because of the same reason that CreateMissingTypeMaps was removed from AutoMappers's API -https://github.com/AutoMapper/AutoMapper/issues/3063
So maybe in a future version of AutoMapper this code won't work (I am using AutoMapper 10.1.1 and it worked for me)
You cannot map anonymous type. To achieve the above functionality you can create a Model like below:
public class ResultantData
{
public string FamilyName { get; set; }
public string LastLogin { get; set; }
public string GivenName { get; set; }
public string Email { get; set; }
public string EmailVerified { get; set; }
public string Uuid { get; set; }
public string Role { get; set; }
public string Payers { get; set; }
public string Accounts { get; set; }
public string ModifiedOn { get; set; }
}
Then you can write the above query as below and return the IQueryable of the result:
var query = (from _vdata in Table1
join entityFind in Table2 on _vdata.id equals entityFind.id
select new ResultantData
{
entityFind.FamilyName,
entityFind.LastLogin,
entityFind.GivenName,
entityFind.Email,
entityFind.EmailVerified,
entityFind.Uuid,
_vdata.Role,
_vdata.Payers,
_vdata.Accounts,
_vdata.ModifiedOn
});
When you want to map this result to actual model then you can use ProjectTo method of Automapper as below:
var result = query.ProjectTo<ResultantDataModel>().ToList();
I have used below class as result model:
public class ResultantDataModel
{
public string FamilyName { get; set; }
public string LastLogin { get; set; }
public string GivenName { get; set; }
public string Email { get; set; }
public string EmailVerified { get; set; }
public string Uuid { get; set; }
public string Role { get; set; }
public string Payers { get; set; }
public string Accounts { get; set; }
public string ModifiedOn { get; set; }
}

Handling Nested Objects in Entity Framework

I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?

Filtering on the Collection Navigation property

I would like to filter my 'TranslationSet' entities, based on their 'Translations' Collection Navigation Property.
E.g.
If a 'Translation' has a 'LanguageId' of 5 (Italian), then the 'TranslationSet' that contains this 'Translation' should be removed from the result.
Here are my Entity classes:
public class Language
{
public int LanguageId { get; set; }
public string NationalLanguage { get; set; }
//Make table multi tenanted.
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public List<Translation> Translation { get; set; } = new List<Translation>();
}
public class Translation
{
public int TranslationId { get; set; }
public string TranslatedText { get; set; }
public int LanguageId { get; set; }
public Language Language { get; set; }
//Make table multi tenanted.
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public int TranslationSetId { get; set; }
public TranslationSet TranslationSet {get; set;}
}
public class TranslationSet
{
public int TranslationSetId { get; set; }
public int TenantId { get; set; }
public ApplicationTenant Tenant { get; set; }
public IEnumerable<Translation> Translations { get; set; }
}
Here is my attempt
From the image you can see that the query fails because a Translation exists with LanguageId of 5.
I have tried many many attempts to resolve this but I can't even get close the LINQ which returns my query correctly.
Please let me know if any further clarification is needed and thanks in advance to anybody who offers help.
My rule of the thumb that nearly always work is: start by querying the entities you want. That will prevent duplicates as you see in your query result. Then add predicates to filter the entities, using navigation properties. That will be:
var sets = TranslationSets // start the query here
.Where(ts => ts.Translations.All(t => t.LanguageId != 5)); // Filter
Or if you like this better:
var sets = TranslationSets // start the query here
.Where(ts => !ts.Translations.Any(t => t.LanguageId == 5)); // Filter
EF will translate both queries as WHERE NOT EXISTS.

LINQ Projection and loading child objects

Having an issue with projection and getting child objects to load. The following is simplified code to represent the logic I'm trying to implement, not the actual code.
public class TicketItem
{
public int TicketItemId { get; set; }
public string TicketReason { get; set; }
public Station Station { get; set; }
public TicketOwner TicketOwner { get; set; }
}
public class Station
{
public int StationId { get; set; }
public string Name { get; set; }
}
public class TicketOwner
{
public int TicketOwnerId { get; set; }
public Employee Employee { get; set; }
public Organization Organization { get; set; }
}
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Organization
{
public int OrganizationId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class CommonReasons
{
public int CommonReasonId { get; set; }
public string Reason { get; set; }
}
public TicketItem GetById(int id)
{
var query = from i in _dataContext.TicketItems
.Include("Station")
.Include("TicketOwner.Employee")
.Include("TicketOwner.Organization")
join r in _dataContext.CommonReasons on i.TicketReason equals r.CommonReasonId.ToString() into r1
from r2 in r1.DefaultIfEmpty()
where i.TicketItemId == id
select new TicketItem {
TicketItemId = i.TicketItemId,
TicketReason = r2.Reason == null ? i.Reason : r2.Reason,
Station = i.Station,
TicketOwner = i.TicketOwner
};
return query
.AsNoTracking()
.FirstOrDefault();
}
Most the code is self-explanatory. The part that is indirectly causing the trouble would be the relationship between TicketItem.TicketReason property (a string) and the CommonReasons entity. From the user interface side, the end-user has an input field of "Reason", and they can select from "common" reasons or input an adhoc reason. They original developer chose to have the TicketReason property contain either the key ID from the CommonReasons table (if the user selected from drop-down) or the adhoc reason typed in.
So, to handle this logic in the linq query, the only way I have found is to do a left join between TicketItem.TicketReason and CommonReasons.CommonReasonId, then use projection to modify the TicketReason column returning either the common reason text or adhoc text. If there is a different way to do this that would get me around the trouble I'm having with projection/include, I'm all ears.
For the "reason" logic, this query works, returning the proper text. The trouble is that none of the "grand-child" objects are returning, i.e. TicketItem.TicketOwner.Employee, TicketItem.TicketOwner.Organization. How do I get those objects to return also?
Changing the structure of the tables would be an absolute last resort, just based on the amount of code that would have to change. There are other spots in the code that are using the above logic but don't need the child objects.
Any help would be appreciated. Hope I've explained enough.

EF Core returns only first record of a list unless FK entities are reset

I am facing the same issue as described in this question. Problem: my method GetAllConferences() returns correctly all the conferences from the DB, but when I return the result to the View from the controller return Ok(tripListVm) inly the first collection item is returned to the client. On the otehr side, by setting to null all the FK references (as pointed out in the SO question above) I can return correctly all the entities to the client, however this does not seem to me the proper way of proceeding.
EDIT: the solution was much simpler than I though. In the code below (I leave it in its original form for others to see it) I was not mapping the FK entities inside the ViewModel to Dto objects, but returning the model entity itself. That was the reason why I needed to null those inner references to make it work. By returning all Dtos objects, it works properly.
I have three entities involved with 1-many relationships:
public class Conference
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Venue> Venues { get; set; }
public int? LocationId { get; set; }
public Location Location { get; set; }
}
public class Venue
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public int? ConferenceId { get; set; }
public Trip Conference { get; set; }
public int? LocationId { get; set; }
public City City { get; set; }
}
public class City
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Conference> Conferences { get; set; }
public ICollection<Venue> Venues { get; set; }
}
In the repository, I have a method that returns the conferences and the related entities (City and Venues):
public IEnumerable<Conference> GetAllConferences()
{
return _context.Conferences
.Include(t => t.Venues)
.Include(t => t.City)
.ToList();
}
In the controller I need to use the following code to return all the results:
var conferences = _repository.GetAllConferences();
if (conferences.Any())
{
var conferenceListVm = trips.ToConferenceVmList();
//Without setting the FK references to null, I can return only the first result of the collection
foreach (var vm in conferenceListVm)
{
foreach (var pm in vm.PoinOfInterests)
{
pm.Trip = null;
}
vm.Location.Conferences = null;
vm.Location.Venues = null;
}
return Ok(conferenceListVm);
}
public static ConferenceViewModel ToConferenceVm(this Conference conference)
{
var confVm = new ConferenceViewModel();
confVm.Name = conference.Name;
confVm.City = conference.City;
confVm.Venues = conference.Venues;
return tripVm;
}
public static IEnumerable<ConferenceViewModel> ToConferenceVmList(this IEnumerable<Conference> conferences)
{
return conferences.Select(c => c.ToConferenceVm()).ToList();
}

Categories