Let's say I have 2 entities (1-to-many) relation: Component and Part like so:
public Component
{
string CompName { get; set; }
byte[] CompBlob { get; set; }
ICollection<Part> Parts { get; set; }
}
public Part
{
string PartName { get; set; }
byte[] PartBlob { get; set; }
}
When I load a Component, I want to always load its Parts for this particular entity.
I want to know how to select / project a list of components so that EF will not load additional inner properties (eg: only CompName and PartName to be loaded but not CompBlob and inner PartBlob).
Maybe something like below, but how to apply a selector for Parts?
//
dbContext.Components.Include(c => c.Parts).Where(filterComponents).Select(.?.)
//
If needed, I have LazyLoadingEnabled set to false
If you're using entity framework, you need to add primary key for each entity, like below:
public class Component
{
public int Id { get; set; }
public string CompName { get; set; }
public byte[] CompBlob { get; set; }
public ICollection<Part> Parts { get; set; }
}
public class Part
{
public int Id { get; set; }
public string PartName { get; set; }
public byte[] PartBlob { get; set; }
}
Then make the query like:
LazyLoading Disable Case:
var result = dbContext.Components.Include("Parts").Select(m => new
{m.CompName, PartNames = m.Parts.Select(n => n.PartName)}).ToList();
LazyLoading Enabled Case:
var result = dbContext.Components.Select(m => new
{m.CompName, PartNames = m.Parts.Select(n => n.PartName)}).ToList();
If I understand you correctly...
var query = dbContext.Components.Select(x => new {
CompName = x.CompName,
Parts = x.Parts.Select(p => p.PartName)
}).ToList();
Add additional filters and selectors as you require. You may / may not need the Include statement (.Include("Parts"))
Related
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?
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.
I am trying to get away from the Entity Framework since I have to support HANA Databases aside from SQL server Databases in our solution.
I am doing some research with dapper so I created a quick test environment with some fictitious scenario.
I have the following POCOs that resemble my Database schema (I have more but I limited to showing these for simplicity):
public class Adopter
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public State State { get; set; }
public int StateId { get; set; }
public string Zip { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public IEnumerable<Pet> Pets { get; set; }
}
public class State
{
public int Id { get; set; }
public string Name { get; set; }
public string Abreviation { get; set; }
}
public class Pet
{
public int Id { get; set; }
public string IdTag { get; set; }
public string Name { get; set; }
public DateTime AdmitionDate { get; set; }
public Status Status { get; set; }
public int StatusId { get; set; }
public string Notes { get; set; }
public DateTime AdoptionDate { get; set; }
public bool IsAdopted { get; set; }
public int? AdopterId { get; set; }
public int Age { get; set; }
public decimal Weight { get; set; }
public string Color { get; set; }
public Breed Breed { get; set; }
public int BreedId { get; set; }
public Gender Gender { get; set; }
public int GenderId { get; set; }
public IEnumerable<PetImage> PetImages { get; set; }
}
public class Status
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class Gender
{
public int Id { get; set; }
public string Name { get; set; }
}
I am using the following in a repository to return a list of all the adopters:
using (SqlConnection connection = new SqlConnection(_connectionString))
{
var adopters = connection.Query<Adopter>("SELECT a.* FROM Adopters a");
foreach (var adopter in adopters)
{
adopter.State = connection.QueryFirst<State>("Select s.* FROM States s WHERE s.Id = #Id", new { Id = adopter.StateId });
adopter.Pets = connection.Query<Pet>("Select p.* FROM Pets p WHERE p.AdopterId = #Id", new { Id = adopter.Id });
foreach (var pet in adopter.Pets)
{
pet.Status = connection.QueryFirst<Status>("Select s.* FROM Status s WHERE s.Id = #Id", new { Id = pet.StatusId });
pet.Gender = connection.QueryFirst<Gender>("Select g.* FROM Genders g WHERE g.Id = #Id", new { Id = pet.GenderId });
}
}
return adopters;
}
As you can see, I am retrieving the data for each POCO individually based on the previous one and doing the Joins manually in code.
Is this the right way of doing it or should I be doing a big query with multiple joins and mapping the result somehow thru dapper and LINQ?
A possible improvement to your actual solution is through the use of QueryMultiple extension like this:
using (SqlConnection connection = new SqlConnection(_connectionString))
{
string query = #"SELECT * FROM Adopters;
SELECT * FROM States;
SELECT * FROM Pets;
SELECT * FROM Status;
SELECT * FROM Genders;";
using (var multi = connection.QueryMultiple(query, null))
{
var adopters = multi.Read<Adopter>();
var states = multi.Read<State>();
var pets = multi.Read<Pet>();
var statuses = multi.Read<Status>();
var genders = multi.Read<Gender>();
foreach (Adopter adp in adopters)
{
adp.State = states.FirstOrDefault(x => x.Id == adp.StateID);
adp.Pets = pets.Where(x => x.IsAdopted &&
x.AdopterID.HasValue &&
x.AdopterID.Value == adp.AdopterID)
.ToList();
foreach(Pet pet in adp.Pets)
{
pet.Status = statuses.FirstOrDefault(x => x.Id == pet.StatusID);
pet.Gender = genders.FirstOrDefault(x => x.Id == pet.GenderID);
}
}
}
}
The benefit here is that you reach the database just one time and then process everything in memory.
However this could be a performance hit and a memory bottleneck if you have a really big data to retrieve, (and from a remote location). Better to look closely at this approach and try also some kind of Async processing and/or pagination if possible.
I don't like to be negative, but... don't do this! Don't even think like this. You want to dump EF, but you're walking into the trap by wanting to emulate EF. The bridge between your app and your DB is not something to be built once for all time, for every conceivable purpose. Concretely, you shouldn't really ever bring back a whole table, and certainly not to then loop on every row and emit more queries. You may feel unjustly criticised, you were just testing the tools ! If so, perhaps tell us what aspect of the tool your examining, and we'll focus in on that.
Dapper or QueryFirst greatly simplify running queries, and consuming the results, so bring back just what you need, just when you need it. Then denormalize a little, for the specific job in hand. Why are there no joins in your queries? RDBMSs are amazing, and amazingly good at doing joins. If you're joining data outside the DB, crazy is the only word, even if Linq gives you a super (sql-like) syntax for doing it. The unthinking assumption that 1 table corresponds to 1 class is the start of a lot of problems.
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.
I'm seemingly stumped by a likely simple solution. I've typically used lazy loading in EF 4.1, and I'm now trying to use eager loading in my application so I can use the built-in JSON serializer without issues. The problem I am having is that I can't figure out how to use .Include() to load multiple same-level grandchild relationships.
public class Canvas
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<ContentArea> Contents { get; set; }
}
public class ContentArea
{
public int ID { get; set; }
public int CanvasID { get; set; }
public string Name { get; set; }
public ICollection<TextContent> TextContents { get; set; }
public ICollection<ImageContent> ImageContents { get; set; }
}
public class TextContent
{
public int ID { get; set; }
public int ContentAreaID { get; set; }
public string Text { get; set; }
public int Color { get; set; }
}
public class ImageContent
{
public int ID { get; set; }
public int ContentAreaID { get; set; }
public string Filename { get; set; }
}
I've tried the following with no success. How can I write the loading code to load TextContents and ImageContents?
Doesn't compile:
var c = dataContext.Canvases
.Include(ca => ca.Contents.Select(co => co.ImageContents).Select(co => co.TextContents))
.FirstOrDefault();
Doesn't work, second Include overrides first:
var c = dataContext.Canvases
.Include(ca => ca.Contents.Select(co => co.ImageContents))
.Include(ca => ca.Contents.Select(co => co.TextContents))
.FirstOrDefault();
Doesn't work, throws runtime exception:
var c = dataContext.Canvases
.Include(ca => ca.Contents.Select(co => new { co.ImageContents, co.TextContents }))
.FirstOrDefault();
Edit:
I've given up on this approach for now and just made view models based on some other articles and approaches they have taken solving the "serializing Entity models" problem with the ASP.NET MVC built in JSON serialization. This caused me to duplicate my classes, but it was made easy by using the AutoMapper library to transfer all of the data back and forth automatically.
I have succeeded with following codes
var contents = db.Canvases
.Include(c=>c.Contents.Select(co=>co.TextContents))
.Include(c=>c.Contents.Select(co=>co.ImageContents))
.ToList();