Adding related table fields to class via Entity Framework - c#

Every building has multiple rooms. I would like to return data from a building that includes a room number from every room owned by said building via a class buildingView, which looks like this right now (with some pseudocode):
public class buildingView
{
public int id { get; set; }
public string name { get; set; }
...
public *something* roomNumbers *something*
}
To that end, I have the following query:
buildingView building = db.buildings.Select(b => new buildingView
{
id = b.id,
name = b.name,
...
roomNumbers = *I have no idea*
})
.Where(b => b.id == id)
.SingleOrDefault();
Room numbers are all strings, if that's relevant.
My questions:
How can I get my list of room numbers?
How can I improve my question's title? I lack the vocabulary right now to do better.
Is there a another way entirely?
Thanks!
P.S. If you are going to downvote, please say why. Otherwise how will I know how to improve my questions?

Assuming you have a class like ...
class Building
{
...
public ICollection<Room> Rooms { get; set; }
}
And that BuildingView is ...
public class BuildingView
{
public int Id { get; set; }
public string Name { get; set; }
...
public IEnumerable<string> RoomNumbers { get; set; }
}
... you can get the concatenated room numbers as follows:
var buildingViews = context.Buildings
.Select(b => new BuildingView
{
Id = b.Id,
Nems = b.Name,
RoomNumbers = b.Rooms
.Select(r => r.Number)
});
Side note: it's really recommended to use C# naming conventions, like PascalCase for class and property names.

Related

Get entity with all its child which meet a certain condition

Just a disclaimer, this might have been already asked, but I didn't really knew what to search for.
So basically I have the following model:
public class Car
{
public int Id { get; set; }
public string UniqueName { get; set; }
public List<Feature> Features { get; set; }
}
public class Feature
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
Lets say I want to get a car which's UniqueName equals Bentle, but only with Features which cost less then 100$.
I could do something like this:
var car = DbContext.Cars.FirstOrDefault(x=> x.UniqueName == "Bentle");
car.Features = car.Features.Where(x=> x.Price <= 100).ToList();
This indeed works, but it seems to me as a lot of unnecessary conversions. Is there any way to shorten this Query?
A few Requirements:
I need the Car Entity itself
The List of Features only contain Features which cost less then 100$
Although I don't see any unnecessary conversion in your query, but you can try the following if you want to execute your request in one line:
var car = DbContext.Cars.Where(x=> x.UniqueName == "Bentle").Select(car =>
new Car()
{
Features = car.Features.Where(x=> x.Price <= 100),
.
.
/*here copy the remaining properties of car object*/
}).FirstOrDefault();

How can I get the count of a list in an Entity Framework model without including/loading the entire collection?

I have a model in Entity Framework Core that goes something like this:
public class Anime
{
public int EpisodeCount { get { return Episodes.Count() } }
public virtual ICollection<Episode> Episodes { get; set; }
}
I'm having the issue of EpisodeCount being 0. The solution currently is to run a .Include(x => x.Episodes) within my EF query, but that loads the entire collection of episodes where it's not needed. This also increases my HTTP request time, from 100ms to 700ms which is just not good.
I'm not willing to sacrifice time for simple details, so is there a solution where I can have EF only query the COUNT of the episodes, without loading the entire collection in?
I was suggested to do this
var animeList = context.Anime.ToPagedList(1, 20);
animeList.ForEach(x => x.EpisodeCount = x.Episodes.Count());
return Json(animeList);
but this also returns 0 in EpisodeCount, so it's not a feasible solution.
You need to project the desired data into a special class (a.k.a. ViewModel, DTO etc.). Unfortunately (or not?), in order to avoid N + 1 queries the projection must not only include the count, but all other fields as well.
For instance:
Model:
public class Anime
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public virtual ICollection<Episode> Episodes { get; set; }
}
ViewModel / DTO:
public class AnimeInfo
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public int EpisodeCount { get; set; }
}
Then the following code:
var animeList = db.Anime.Select(a => new AnimeInfo
{
Id = a.Id,
Name = a.Name,
EpisodeCount = a.Episodes.Count()
})
.ToList();
produces the following single SQL query:
SELECT [a].[Id], [a].[Name], (
SELECT COUNT(*)
FROM [Episode] AS [e]
WHERE [a].[Id] = [e].[AnimeId]
) AS [EpisodeCount]
FROM [Anime] AS [a]

LINQ GroupBy Aggregation with AutoMapper

Trying to get a query to work, but honestly not sure how (or if it's even possible) to go about it as everything I have tried hasn't worked.
Querying a total of 6 tables: Person, PersonVote, PersonCategory, Category, City, and FirstAdminDivision.
PersonVote is a user review table for people and contains a column called Vote that is a decimal accepting a value from 1-5 (5 being "best"). FirstAdminDivision would be synonymous with US states, like California. Person table has a column called CityId which is the foreign key to City. The other tables I believe are mostly self-explanatory so I won't comment unless needed.
My goal is create a query that returns a list of the "most popular" people which would be based on the average of all votes on the PersonVote table for a particular person. For instance, if a person has 3 votes and all 3 votes are "5" then they would be first in the list...don't really care about secondary ordering at this point...eg...like most votes in a tie would "win".
I have this working without AutoMapper, but I love AM's ability to do projection using the ProjectTo extension method as the code is very clean and readable and would prefer to use that approach if possible but haven't had any luck getting it to work.
Here is what I have that does work....so basically, I am trying to see if this is possible with ProjectTo instead of LINQ's Select method.
List<PersonModel> people = db.People
.GroupBy(x => x.PersonId)
.Select(x => new PersonModel
{
PersonId = x.FirstOrDefault().PersonId,
Name = x.FirstOrDefault().Name,
LocationDisplay = x.FirstOrDefault().City.Name + ", " + x.FirstOrDefault().City.FirstAdminDivision.Name,
AverageVote = x.FirstOrDefault().PersonVotes.Average(y => y.Vote),
Categories = x.FirstOrDefault().PersonCategories.Select(y => new CategoryModel
{
CategoryId = y.CategoryId,
Name = y.Category.Name
}).ToList()
})
.OrderByDescending(x => x.AverageVote)
.ToList();
By looking at your code sample I tried to determine what your models would be in order to setup an example. I only implemented using a few of the properties to show the functionality:
public class People
{
public int PeronId { get; set; }
public string Name { get; set; }
public City City { get; set; }
public IList<PersonVotes> PersonVoes { get; set; }
}
public class PersonVotes
{
public int Vote { get; set; }
}
public class City
{
public string Name { get; set; }
}
public class FirstAdminDivision
{
public string Name { get; set; }
}
public class PersonModel
{
public int PersonId { get; set; }
public string Name { get; set; }
public string LocationDisplay { get; set; }
public double AverageVote { get; set; }
}
To use the ProjectTo extension I then initialize AM through the static API:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<IEnumerable<People>, PersonModel>()
.ForMember(
model => model.LocationDisplay,
conf => conf.MapFrom(p => p.FirstOrDefault().City.Name))
.ForMember(
model => model.AverageVote,
conf => conf.MapFrom(p => p.FirstOrDefault().PersonVoes.Average(votes => votes.Vote)));
});
So given the following object:
var people = new List<People>()
{
new People
{
PeronId = 1,
City = new City
{
Name = "XXXX"
},
PersonVoes = new List<PersonVotes>
{
new PersonVotes
{
Vote = 4
},
new PersonVotes
{
Vote = 3
}
}
}
};
I would then a have query:
var result = people
.GroupBy(p => p.PeronId)
.Select(peoples => peoples)
.AsQueryable()
.ProjectTo<PersonModel>();
I'm just using in memory objects so that is why I convert to IQueryable to use the ProjectTo extension method in AM.
I'm hoping this was what you're looking for. Cheers,
UPDATED FOR LINQ TO ENTITIES QUERY:
var result = db.People
.GroupBy(p => p.PersonId)
.ProjectTo<PersonModel>(base.ConfigProvider) // AM requires me to pass Mapping Provider here.
.OrderByDescending(x => x.AverageVote)
.ToList();

Assign aggregate result to the entity property not pulling all subquery rows

I have a Comment and Votes related to the comment.
[Table("QAComment")]
public class QaComment : IEntity
{
[Key, Column("QACommentID")]
public int Id { get; set; }
// ...
public virtual ICollection<QaCommentVote> Votes { get; set; }
[NotMapped]
public int OverallVote { get; set; }
}
[Table("QACommentVote")]
public class QaCommentVote : IEntity
{
[Key, Column("QACommentVoteID")]
public int Id { get; set; }
[ForeignKey("QAComment")]
public int QaCommentId { get; set; }
public int Value { get; set; }
public virtual QaComment QaComment { get; set; }
}
I need to get comments with the sum of their votes, not pulling all votes to the application.
The ways I can see to achive this:
1. Make a database view for Commment and calc votes sum in there.
Cons: dont wanna make extra-views
2. Via LINQ:
var comments =
Set<QaComment>()
.Select(c => new QaComment() {/* assign every property once again and calc OverallVote */});
Cons: don't like to assign allproperties once again.
Is there a better way devoid of that cons?
UPDATE
This is what I want as a result of LINQ:
SELECT
qac.*,
(SELECT SUM(v.Value)
FROM QACommentVote v
WHERE v.QACommentID = qac.QACommentID) as OverallVote
FROM QAComment qac
You can fetch QaComment and the sum you're looking for separately as anonymous type and merge them into one object using LINQ to Objects:
var comments
= Set<QaComment>()
.Select(c => new { c, sum = c.Votes.Sum(v => v.Value))
.AsEnumerable() // to make next query execute as LINQ to Objects query
.Select(x => { x.c.OverallVote = x.sum; return x.c; })
.ToList();
But to make point clear: I haven't tested that :)

How to query a child object

public class Employee
{
public int Id { get; set; }
public string Title { get; set;}
//....other fields....
//......
//public Topics Interest { get; set; }
public IList<Topics> Interests { get; set; }
}
public class Topics
{
public int Id { get; set; } ;
public string Name { get; set; } ;
//other fields
}
public static IQueryable<EmployeeObject> QueryableSQL()
{
IQueryable<EmployeeObject> queryable = EmployeeRepository.GetAllEmployee();
}
My above data structure has Employee and within it has multiple interests and each interest has multiple topics
My Question is:
How would i search Employee.Interests.Name ?
//i need help construct the linq....
//the below will not work and look for something in the `EmployeeObject` rather in `Interests`
IList<EmployeeObject> _emps = QueryableSQL().Where(x => x.Name== "Chess").ToList();
It depends on what you want. Do you want items where any of their interests match a given value?
var query = QueryableSQL().Where(employee =>
employee.Interests.Any(interest => interest.Name == "Chess"));
When you've been able to explain in English the query that you want the translation to LINQ will be a lot easier.
You can use Any on the child collection to find matching EmployeeObjects
IList<EmployeeObject> _emps =
QueryableSQL().Where(x => x.Interests
.Any(i => i.Name== "Chess"))
.ToList();

Categories

Resources