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.
Related
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.
EDIT: I originally worded this question very poorly, stating the problem was with JSON serialization. The problem actually happens when I'm converting from my base classes to my returned models using my custom mappings. I apologize for the confusion. :(
I'm using .NET Core 1.1.0, EF Core 1.1.0. I'm querying an interest and want to get its category from my DB. EF is querying the DB properly, no problems there. The issue is that the returned category has a collection with one interest, which has one parent category, which has a collection with one interest, etc. When I attempt to convert this from the base class to my return model, I'm getting a stack overflow because it's attempting to convert the infinite loop of objects. The only way I can get around this is to set that collection to null before I serialize the category.
Interest/category is an example, but this is happening with ALL of the entities I query. Some of them get very messy with the loops to set the relevant properties to null, such as posts/comments.
What is the best way to address this? Right now I'm using custom mappings that I wrote to convert between base classes and the returned models, but I'm open to using any other tools that may be helpful. (I know my custom mappings are the reason for the stack overflow, but surely there must be a more graceful way of handling this than setting everything to null before projecting from base class to model.)
Classes:
public class InterestCategory
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<Interest> Interests { get; set; }
}
public class Interest
{
public long Id { get; set; }
public string Name { get; set; }
public long InterestCategoryId { get; set; }
public InterestCategory InterestCategory { get; set; }
}
Models:
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public long? InterestCategoryId { get; set; }
}
Mapping functions:
public static InterestCategoryModel ToModel(this InterestCategory category)
{
var m = new InterestCategoryModel
{
Name = category.Name,
Description = category.Description
};
if (category.Interests != null)
m.Interests = category.Interests.Select(i => i.ToModel()).ToList();
return m;
}
public static InterestModel ToModel(this Interest interest)
{
var m = new InterestModel
{
Name = interest.Name,
Description = interest.Description
};
if (interest.InterestCategory != null)
m.InterestCategory = interest.InterestCategory.ToModel();
return m;
}
This is returned by the query. (Sorry, needed to censor some things.)
This is not .NET Core related! JSON.NET is doing the serialization.
To disable it globally, just add this during configuration in Startup
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}));
edit:
Is it an option to remove the circular references form the model and have 2 distinct pair of models, depending on whether you want to show categories or interests?
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Note that each of the models has a nested class for it's child objects, but they have their back references removed, so there would be no infinite reference during deserialization?
I'm trying to get data in a suitable format for an api
What I would like is
Place
--Rating1
---RatingImage1.1
---RatingImage1.2
---UserName
---UserId
--Rating2
---RatingImage2.1
---RatingImage2.2
---UserName
---UserId
In a nutshell im trying to fetch a place, with its ratings(and rating images), with the names of the users who did the rating given the googlePlaceId
Tried this but it goes and does some circular fetching where once it fetches the user it then fetches the user rating and the response becomes massive
context.Places
.Include(x => x.Ratings.Select(y => y.User))
.Include(x => x.Ratings.Select(c => c.RatingImages))
.Single(x => x.GooglePlaceId == googlePlaceId);
I think projection or linq joins must be the way, but i havent had any success yet.
here are my POCOS
Place Poco
public class Place
{
public Place()
{
Ratings = new List<Rating>();
Favourites = new List<Favourite>();
}
public int Id { get; set; }
public string Name { get; set; }
public string GooglePlaceId { get; set; }
public ICollection<Rating> Ratings { get; set; }
public ICollection<Favourite> Favourites { get; set; }
}
Rating POCO
public class Rating
{
public Rating()
{
RatingImages = new List<RatingImage>();
}
public int Id { get; set; }
public float RatingValue { get; set; }
public string RatingComment { get; set; }
public int PlaceId { get; set; }
public Place Place { get; set; }
public string UserId { get; set; }
public AspNetUser User { get; set; }
public ICollection<RatingImage> RatingImages { get; set; }
}
User POCO
public partial class AspNetUser
{
public string UserName { get; set; }
public string Id { get; set; }
// the rest of the fields are omitted
}
Although you've omitted the definition of AspNetUser, I'm guessing it has a navigation property back to Ratings. Is this required anywhere else in your application? It won't affect the structure of your database, and removing it would allow your projection to work exactly as you've got it here. You'd still be able to display all ratings by a single user using a separate query - you've got to optimise for your most common scenario though.
I am finding this very hard to understand and where to start, so I was hoping that some one would be able to point in the correct direction. I have a list(customers) inside which there are arrays/lists. Basically I want to flatten all the results of the list into a flat version if the list.
public class Customer : EntityBase
{
public Phonenumber[] PhoneNumbers { get; set; }
public Contact BillToContact { get; set; }
public Terms Terms { get; set; }
}
public class Contact
{
public Phonenumber[] PhoneNumbers { get; set; }
public Address Address { get; set; }
public Key Key { get; set; }
public string CompanyName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
}
public class Phonenumber
{
public string Number { get; set; }
public int Key { get; set; }
}
public class Terms
{
public int DueDays { get; set; }
public int DiscountDays { get; set; }
}
public class Address
{
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public abstract class EntityBase
{
public Guid Id { get; set; }
public string Status { get; set; }
public int Rev { get; set; }
}
I have tried many approaches and just keep getting more confused. So if anyone could help or even point me in the right direction I would be extremely grateful. below is one of the approaches I have tried.
public IEnumerable<Customer> Find (Func<Customer , bool> predicate) {
foreach (var p in Customer.SelectMany(p => p)) {
if(predicate(p)) {
yield return p;
}
}
}
I am Deserializing a jason string into a list but then want to display in a datagrid, but igGrid does not support binding to nested(complex) properties. So I need to flatten the list so that there is no sub levels of the list.
To select an array of PhoneNumber from List<Customer> use SelectMany:
List<Customer> customers = [data];
PhoneNumber phoneNumbers = customers.SelectMany(x=>x.PhoneNumbers).ToArray();
It's not clear at all from your question what output you actually want. Do you just want a list of all the phone numbers? Or do you want to preserve the other Customer information, such that you get multiple instances of the Customer information, each instance with a separate phone number?
You can accomplish the former with something like this:
IEnumerable<Phonenumber> numbers =
customers.SelectMany(
customer => customer.PhoneNumbers
.Concat(BillToContact.PhoneNumbers));
If you only want the Customer.PhoneNumbers numbers and not those in the BillToContact object, just leave the .Concat(BillToContact.PhoneNumbers) out of the above.
If you want to preserve one or more values from the original Customer object, you can do something like this:
var numbers = customers.SelectMany(
customer => customer.PhoneNumbers.Select(
number => new
{
Number = number,
FirstName = customer.BillToContact.FirstName,
Email = customer.BillToContact.Email
}));
The above will generate an enumeration of anonymous type objects, each having a single phone number, along with the corresponding FirstName and Email values from the associated Contact object. You can of course mix and match (e.g. use .Concat(...) to include phone numbers from the BillToContact object), and include whichever specific Customer or Contact members you want.
I have a method in my repository to retrieve All records for Items
public IQueryable<Item> GetAll()
{
//The following causes a circular reference if you attempt to serialize it via an API call.
IQueryable<Item> items = context.Items.Include(c => c.UserProfile).Include(c => c.UserProfile1).AsQueryable();
return items;
}
This causes issues with Kendo Grid and serialization because of how I am including the foreign tables User Profile twice to be able to get the full name of the user whom created and modified the Item record.
Instead of Include(c => c.UserProfile) is there a way to only include the UserProfile.FullName column?
Today I am handling this in my ViewModel and creating a new subclass (this example is for Locations, not Items):
public class LocationsListViewModel
{
public IEnumerable<LocationsGrid> Locations { get; set; }
public IEnumerable<Facility> Facilities { get; set; }
public IEnumerable<string> AreaOptions { get; set; }
public int LocationCount { get; set; }
public class LocationsGrid
{
public int Id { get; set; }
public string DisplayLocation { get; set; }
public string Area { get; set; }
public string Zone { get; set; }
public string Aisle { get; set; }
public string Bay { get; set; }
public string Level { get; set; }
public string Position { get; set; }
public string Barcode { get; set; }
}
}
and then having to populate that in my Tasks or App Services layer (sits between controller and repository) like this:
viewModel.Locations = from l in locations.ToList()
select new LocationsListViewModel.LocationsGrid
{
Id = l.Id,
DisplayLocation = l.DisplayLocation,
Area = l.Area,
Zone = l.Zone,
Aisle = l.Aisle,
Bay = l.Bay,
Level = l.Level,
Position = l.Position,
Barcode = l.BarcodeValue
};
This seems like a lot of extra code and maintenance for each entity going forward. I'm sure there is a more efficient way to do this.
I typically use a Data-Transfer Object (basically just a class that has the exact data you're looking for, then returning objects of that type from your data-access method.
public IQueryable<ItemSummary> GetAll()
{
IQueryable<ItemSummary> items = context.Items
.Select(c => new ItemSummary {
FirstProfileName = c.UserProfile.FullName,
SecondProfileName = c.UserProfile1.FullName,
ScalarProp1 = c.ScalarProp1,
...
})
.AsQueryable();
return items;
}
I'm not sure if that will work the way you want it to, since I'm not familiar with Kendo Grid and such, but it may be useful.