select distinct on model and iterate - c#

I'm having issues getting a distinct record from my DB for one of my models.
I'm using EF to select and return the table contents to the model type which I then iterate over, the below code works just fine:
IQueryable<StudentRecord> students = _dbaccess.GetAllStudents();
Where _dbaccess is a local instance of an interface to DB context and GetAllStudents returns all items of that model from the DB context itself.
foreach (StudentRecord student in students)
{
if (student.Name == "something")
{
}
}
The issue arises when I try to use LINQ to select distinct students:
IQueryable<StudentRecord> students =
_dbaccess.GetAllStudents().Select(x => x.StudentID).Distinct();
Because of the way my loop is structured I need to access various members of my model, but I can't use LINQ when my enumeration variable is of the type of StudentRecord (it works with a string but that then means I cant access the model members in my loop).
Is there an easy way to implement this that I'm not seeing?

Related

Entity framework recursively populate children of DTO

So I have a DataTable object, which I want to populate based on a table from my database. Currently, I do the following:
public Task<IEnumerable<DataDTO>> Loadreferences(int Id)
{
IEnumerable <DataDTO> DTOList = _context.Data.Where(p => p.Id == Id).Select(p => new DataDTO
{
Id = p.Id,
Children = p.Children
});
return Task.FromResult(DTOList);
}
The original table in my database is called Data and contains a lot of other stuff that I have left out for this question. However, the point in constructing this datatable object is basically to reduce the payload towards the server (as it leaves out some of the columns and transform others)
I can successfully populate my DTO, but I have a problem populating the children. Naturally this is because, the list of children is pointing to objects of type Data, and I try to use it in type DataDTO.
Therefore, I get an error stating that I can't convert a list of Data objects to a list of DataDTO objects.
Do I really need a loop to fix this for me, or can I do it with a simple query?
So it turned out, that there was a relatively easy fix to this:
foreach (DataDTO data in datas)
{
data.Children = datas.Where(p => p.ParentId == data.Id).ToList();
}
By doing the following, I didn't have to recursively add new objects in my select statement or do some sort of stored procedure.
The loop simply checks if any items in the list has the current looped item as parentId, whereafter it appends to the children property.

Slapper only maps one object when selecting multiple objects from database

I have some C# classes which represent database objects, some of which contain one or more other custom objects or enumerables of custom objects. I'm using dapper for queries, and slapper to map to the custom objects. It works great for single object. I can easily grab a parent object with a specific ID from the database, do some inner joins, and map it and all the things it "owns" to my custom objects in C#. Problem comes when I want to do a select over multiple parent-IDs.
Some context, let's say I have a person, that person has a list of hobbies which have an ID and a Description, a list of days they're available which also have an ID and Description, and maybe another custom field such as whether they have or are even willing to be around children which can also boil down to a simple ID and Description. We'll call that last field child status. I'd write a select statement like this:
SELECT
,person.id as Id
,person.first_name as FirstName
,person.last_name as LastName
,hobby.Id as Hobbies_Id
,hobby.Description as Hobbies_Description
,avail.Id as Availabilities_Id
,avail.Description as Availabities_Description
,child.Id as ChildStatus_Id
,child.Description as ChildStatus_Description
FROM
users.users person
JOIN
users.userhobbies uhobby
ON
person.id = uhobby.UserId -- one-to-many with relational table
JOIN
users.avail hobby
ON
uhobby.HobbyId = hobby.Id
JOIN
users.useravailabilities uavail
ON
person.id = uavail.UserId -- one-to-many with relational table
JOIN
users.availabilities avail
ON
uavail.AvailId = avail.Id
JOIN
users.childstatuses child
ON
person.ChildStatusId = child.Id
Then I want this mapped to a user like this:
class User
{
public Guid Id {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
public IEnumerable<Hobby> Hobbies {get; set;}
public IEnumerable<Availability> Availabilities {get; set;}
public ChildStatus ChildStatus {get; set;}
}
Since I'm using exact naming conventions and everything here, the query from Dapper and the Automapping work great just like this:
// Using the above sql in a variable
var data = Connection.Query<dynamic>(sql);
var dataReal = Slapper.AutoMapper.MapDynamic<User>(data);
return dataReal;
This works great, but it only ever returns one user. I have a similar method which takes an ID and all of my test users can be retrieved perfectly by passing the ID. I've tried scouring the internet, looking through documentation, and all I found was this: https://github.com/SlapperAutoMapper/Slapper.AutoMapper/issues/57 who seemed to just slip through the cracks. I also tried mapping the dynamic data to various other structures with no luck. Thanks in advance!
Update:
I've come up with a somewhat brutal, "sledgehammer" type solution. I'm not sure if, at this point, I'm forcing myself to use Slapper when there might be a more convenient solution. However, I wanted to ensure anyone in a similar situation might have a chance at making it work. Here's the new C# section:
var data = Connection.Query<dynamic>(sql);
IEnumerable<Guid> Ids = data.Select(row => (Guid)row.id).Distinct();
List<User> results = new List<User>();
foreach (Guid Id in Ids)
{
IEnumerable<dynamic> rows = data.Where(x => { return ((Guid) x.id).Equals(Id); });
User model = (Slapper.AutoMapper.MapDynamic<User>(rows, false) as IEnumerable<User>).FirstOrDefault();
if (model != null)
{
results.Add(model);
}
}
return results;
As you can see, I'm generating a list of unique "primary object" ID's and selecting those rows into their own lists, which I then pass to Slapper. I've passed the "cache = false" parameter to avoid squeezing unrelated data into every object after the first. I could probably get around this by actually keeping the UserHobby/UserAvailability/UserPhoto Ids in place, but I don't like the way that makes my object look. Hopefully this helps someone.
I'm not familiar with Slapper, but I'll show you what I've done with Dapper to construct a complex graph of objects with bi-directional references.
In short, construct a Dictionary or KeyedCollection prior to calling connection.Query<>, then reference it inside the Dapper lambda expression.
This method returns a list of service calls. Each service call is assigned to one technician and one customer. However, a technician may be assigned multiple service calls to multiple customers. And a customer may have multiple technicians on-site.
public ServiceCallResponse GetServiceCallsDapper(ServiceCallRequest Request)
{
var queryParameters = new {statuses = Request.Statuses, createDate = Request.CreateDate};
const string splitOn = "Number,Id"; // Id indicates beginning of second class (Technician). Number indicates begining of third class (Customer).
// Note multiple columns are named "Number". See note below about how Dapper maps columns to class properties.
// Note Dapper supports parameterized queries to protect against SQL injection attacks, including parameterized "where in" clauses.
const string query = #"sql query here..."
ServiceCallResponse response = new ServiceCallResponse(); // Keyed collection properties created in constructor.
using (IDbConnection connection = new SqlConnection("DB connection string here..."))
{
connection.Open();
// Dapper adds a generic method, Query<>, to the IDbConnection interface.
// Query<(1)ServiceCall, (2)Technician, (3)Customer, (4)ServiceCall> means
// construct a (1)ServiceCall, (2)Technician, and (3)Customer class per row, add to an IEnumerable<(4)ServiceCall> collection, and return the collection.
// Query<TFirst, TSecond, TThird, TReturn> expects SQL columns to appear in the same order as the generic types.
// It maps columns to the first class, once it finds a column named "Id" it maps to the second class, etc.
// To split on a column other than "Id", specify a splitOn parameter.
// To split for more than two classes, specify a comma-delimited splitOn parameter.
response.ServiceCalls.AddRange(connection.Query<ServiceCall, Technician, Customer, ServiceCall>(query, (ServiceCall, Technician, Customer) =>
{
// Notice Dapper creates many objects that will be discarded immediately (Technician & Customer parameters to lambda expression).
// The lambda expression sets references to existing objects, so the Dapper-constructed objects will be garbage-collected.
// So this is the cost of using Dapper. We trade unnecessary object construction for simpler code (compared to constructing objects from IDataReader).
// Each row in query results represents a single service call.
// However, rows repeat technician and customer data through joined tables.
// Avoid constructing duplicate technician and customer classes.
// Refer to existing objects in global collections, or add Dapper-mapped objects to global collections.
// This avoid creating duplicate objects to represent same data.
// Newtonsoft JSON serializer preserves object instances from service to client.
Technician technician;
Customer customer;
if (response.Technicians.Contains(Technician.Id))
{
technician = response.Technicians[Technician.Id];
}
else
{
response.Technicians.Add(Technician);
technician = Technician;
}
if (response.Customers.Contains(Customer.Number))
{
customer = response.Customers[Customer.Number];
}
else
{
response.Customers.Add(Customer);
customer = Customer;
}
// Set object associations.
ServiceCall.Technician = technician;
ServiceCall.Customer = customer;
technician.ServiceCalls.Add(ServiceCall);
if (!technician.Customers.Contains(customer))
{
technician.Customers.Add(customer);
}
customer.ServiceCalls.Add(ServiceCall);
if (!customer.Technicians.Contains(technician))
{
customer.Technicians.Add(technician);
}
return ServiceCall;
}, queryParameters, splitOn: splitOn));
}
return response;
}
Using this technique requires you to set PreserveReferencesHandling = true on the JsonSerializer class so object references are preserved on the client-side. Otherwise, Json.NET will construct duplicate objects and technician.Customers.Count will always == 1.
For example, if John Doe is assigned a service call at Acme and another at Contoso, his technician.Customers.Count will equal 1 if you leave PreserveReferencesHandling == false (Json.NET will construct two Technician objects each named John Doe).

How should I disable Entity Framework table reference(foreign) list from each objects?

I'm using Sqlite database and System.Data.SQLite 1.0.92
There is 2 table here:
Table Person:
PersonId
PersonName
Table Student:
StudentId
PersonId(reference table Person FK)
StudentNo
Now every time I get the Persons Collection in EF5:
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
There is also has AllPersons.student collection will include in the result;
But I don't need it. Of course that's just an example, There is a lot of big table has so many references, it always has performance problems here because of that.
So I'm trying to do not let it in my result. So I change it:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
}
Now fine, because AllPersons.student collection will always be null
But now I found: If I get Person and Student together:
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
AllStudents = ctx.Student.ToList();
}
Now the reference still include in.
So Is there anyway to don't let the reference include in any time in this situation?
Thank you.
Update
For some friends request, I explain why I need it:
1: When I convert it to json it will be a dead loop. even I already use Json.net ReferenceLoopHandling, the json size very big to crash the server.(if no references, it's just a very small json)
2:Every time I get the client data and need to save, it will display exception about model state, until I set it to null.
Example:
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
Person model= ThisIsAModel();
model.students = null; // This is a key, I need set the students collection references to null , otherwise it will throw exception
ctx.Entry(model).State = EntityState.Modified;
ctx.SaveChanges();
}
3: This is More important problem. I already get all data and cache on the server. But It will let the loading time very long when server start. (because the data and references are so many, that is the main problem), I don't know I'll meet what kind of problem again....
public List<Person> PersonsCache; // global cache
public List<Student> StudentsCache; // global cache
using (myEntities ctx = new myEntities())
{
ctx.Configuration.LazyLoadingEnabled = false;
ctx.Configuration.ProxyCreationEnabled = false;
// There is so many references and data, will let it very slow , when I first time get the all cache. even I only get the Person model, not other , just because some Collection has some references problem. It will very slow....
PersonsCache = ctx.Persons.ToList();
StudentsCache= ctx.Student.ToList();
}
The Problem
As you said, when you load both of Parent and Child lists even when LazyLoading is disabled, and then look in parent.Childs you see child items has been loaded too.
var db = new YourDbContext();
db.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var childList= db.YourChildSet.ToList();
What happened? Why childs are included in a parent?
The childs under a parent entity, are those you loaded using db.YourChildSet.ToList(); Exactly themselves; In fact Entity Framework never loads childs for a parent again but because of relation between parent and child in edmx, they are listed there.
Is that affect Perforemance?
According to the fact that childs only load once, It has no impact on perforemance because of loading data.
But for serialization or something else's sake, How can I get rid of it?
you can use these solutions:
Solution 1:
Use 2 different instance of YourDbContext:
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var db2 = new YourDbContext();
db2.Configuration.LazyLoadingEnabled = false;
var childList= db.YourChildSet.ToList();
Now when you look in parent.Childs there is no Child in it.
Solution 2:
use Projection and shape your output to your will and use them.
var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet
.Select(x=>new /*Model()*/{
Property1=x.Property1,
Property2=x.Property2, ...
}).ToList();
This way when serialization there is nothing annoying there.
Using a custom Model class is optional and in some cases is recommended.
Additional Resources
As a developer who use Entity Framework reading these resources is strongly recommended:
Performance Considerations for Entity Framework 4, 5, and 6
Connection Management
I'll focus on your third problem because that seems to be your most urgent problem. Then I'll try to give some hints on the other two problems.
There are two Entity Framework features you should be aware of:
When you load data into a context, Entity Framework will try to connect the objects wherever they're associated. This is called relationship fixup. You can't stop EF from doing that. So if you load Persons and Students separately, a Person's Students collection will contain students, even though you didn't Include() them.
By default, a context caches all data it fetches from the database. Moreover, it stores meta data about the objects in its change tracker: copies of their individual properties and all associations. So by loading many objects the internal cache grows, but also the size of the meta data. And the ever-running relationship fixup process gets slower and slower (although it may help to postpone it by turning off automatic change detection). All in all, the context gets bloated and slow like a flabby rhino.
I understand you want to cache data in separate collections for each entity. Two simple modifications will make this much quicker:
Evade the inevitable relationship fixup by loading each collection by a separate context
Stop caching (in the context) and change tracking by getting the data with AsNoTracking.
Doing this, your code will look like this:
public List<Person> PersonsCache;
public List<Student> StudentsCache;
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
PersonsCache = ctx.Persons
.AsNoTracking()
.ToList();
}
using (myEntities ctx = new myEntities())
{
ctx.Configuration.ProxyCreationEnabled = false;
StudentsCache= ctx.Student
.AsNoTracking()
.ToList();
}
The reason for turning off ProxyCreationEnabled is that you'll get light objects and that you'll never inadvertently trigger lazy loading afterwards (throwing an exception that the context is no longer available).
Now you'll have cached objects that are not inter-related and that get fetched as fast as it gets with EF. If this isn't fast enough you'll have to resort to other tools, like Dapper.
By the way, your very first code snippet and problem description...
using (var ctx = new myEntities)
{
AllPersons = ctx.Persons.ToList();
}
There is also has AllPersons.student collection will include in the result;
...suggest that Entity Framework spontaneously performs eager loading (of students) without you Include-ing them. I have to assume that your code snippet is not complete. EF never, ever automatically executes eager loading. (Unless, maybe, you have some outlandish and buggy query provider).
As for the first problem, the serialization. You should be able to tackle that in a similar way as shown above. Just load the data you want to serialize in isolation and disable proxy creation. Or, as suggested by others, serialize view models or anonymous types exactly containing what you need there.
As for the second problem, the validation exception. I can only imagine this to happen if you initialize a students collection by default, empty, Student objects. These are bound to be invalid. If this is not the case, I suggest you ask a new question about this specific problem, showing ample detail about the involved classes and mappings. That shouldn't be dealt with in this question.
Explicitly select what you want to return from the Database.
Use Select new. With the select new clause, you can create new objects of an anonymous type as the result of a query and don't let the reference include in. This syntax allows you to construct anonymous data structures. These are created as they are evaluated (lazily). Like this:
using (var ctx = new myEntities())
{
var AllPersons = ctx.People.Select(c => new {c.PersonId, c.PersonName}).ToList();
}
And even you don't need to disable lazy loading anymore.
After running query above:
This query currently allocates an anonymous type using select new { }, which requires you to use var. If you want allocate a known type, add it to your select clause:
private IEnumerable<MyClass> AllPersons;//global variable
using (var ctx = new myEntities())
{
AllPersons = ctx.People
.Select(c => new MyClass { PersonId = c.PersonId, PersonName = c.PersonName }).ToList();
}
And:
public class MyClass
{
public string PersonId { get; set; }
public string PersonName { get; set; }
}
If entities are auto generated, then copy paste it to own code and remove the relation generated like child collection and Foreign key. Or you don't need all this kind of the functionality might be can user lightweight framework like dapper
In normally your student collection doesn't fill from database. it's fill when you reach to property. In addition if you use ToList() method so Entity Framework read data from data to fill your collection.
Pls check this.
https://msdn.microsoft.com/en-us/data/jj574232.aspx#lazy
https://msdn.microsoft.com/en-us/library/vstudio/dd456846(v=vs.100).aspx
Is there anyway to don't let the reference include in any time in this situation?
The solution to this seems to be very simple: don't map the association. Remove the Student collection. Not much more I can say about it.
Decorate any properties with [IgnoreDataMember] if you are using 4.5+
https://msdn.microsoft.com/en-us/library/system.runtime.serialization.ignoredatamemberattribute(v=vs.110).aspx
Also sounds like you are trying to do table inheritance which is a different problem with EF
http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/implementing-inheritance-with-the-entity-framework-in-an-asp-net-mvc-application
http://www.entityframeworktutorial.net/code-first/inheritance-strategy-in-code-first.aspx
If I understand you correctly, you're just trying to make sure you only get what you specifically ask for right?
This was mentioned a little above, but to do this correctly you just want to select an anonymous type.
var students = from s in _context.Students
select new{
StudentId,
StudentNo};
Then, when you want to update this collection/object, I'd recommend use GraphDiff. GraphDiff really helps with the problems of disconnected entities and updates (https://github.com/refactorthis/GraphDiff)
So your method would look similar to this:
void UpdateStudent(Student student){
_context.UpdateGraph(student, map =>
map
.AssociatedEntity(c => c.Person));
_context.SaveChanges();
}
This way, you're able to update whatever properties on an object, disconnected or not, and not worry about the association.
This is assuming that you correctly mapped your entities, and honestly, I find it easier to declare the object as a property, not just the ID, and use a mapping file to map it correctly.
So:
class Person{
int Id{get;set;}
string Name{get;set}
}
class Student{
int Id{get;set;}
string StudentNo{get;set;}
Person Person{get;set;}
public class StudentMap : EntityTypeConfiguration<Student>
{
public StudentMap()
{
// Primary Key
HasKey(t => t.Id);
// Table & Column Mappings
ToTable("Students");
Property(t => t.Id).HasColumnName("StudentId");
// Relationships
HasRequired(t => t.Person)
.HasForeignKey(d => d.PersonId);
}
}
Hopefully that makes sense. You don't need to create a view model, but you definitely can. This way does make it easier to map disconnected items back to the database though.
I had exact same situation.
All I did to solve it was ask for the Student.ToList() before I asked for Persons.ToList()
I didn't have to disable lazy loading. Just need to load the table that has reference to other table first after that you can load the other table and first table results are already in memory and don't get "fixed" with all the references.
They are automatically linked in the ObjectContext by there EntityKey. Depending on what you want to do with your Persons and Students, you can Detach them from the ObjectContext :
using (var ctx = new myEntities)
{
ctx.Configuration.ProxyCreationEnabled = false;
ctx.Configuration.LazyLoadingEnabled = false;
AllPersons= ctx.Persons.ToList();
foreach(var c in AllPersons)
{
ctx.Detach(c);
}
AllStudents = ctx.Student.ToList();
foreach(var c in AllStudents )
{
ctx.Detach(c);
}
}

C#, Linq2SQL - tricks to fetch a ViewModel object with relation data?

I don't know Linq2Sql so well yet and I was wondering if there is a trick for this probably common MVVM scenario. I have Linq2Sql data context containing Domain models, but I am fetching data for my customized ViewModel object from it.
var query = from ord in ctx.Table_Orders
select new OrderViewModel()
{
OrderId = ord.OrderId,
OrderSum = ord.OrderSum,
OrderCurrencyId = ord.OrderCurrencyId,
OrderCurrencyView = ord.Currency.CurrencyText
};
So i want my ViewModel to inculde both CurrencyId from domain object and the CurrencyText from related table to show it nicely in the View.
This code works great. It generates one DB call with join to fetch the CurrencyText. But the model is simplified, real one has many more fields. I want to make the code reusable because I have many different queries, that returns the same ViewModel. Now every minor change to OrderViewModel requires lots of maintainance.
So I moved the code to OrderViewModel itself as a constructor.
public OrderViewModel(Table_Order ord)
{
OrderId = ord.OrderId,
OrderSum = ord.OrderSum,
OrderCurrencyId = ord.OrderCurrencyId,
OrderCurrencyView = ord.Currency.CurrencyText
}
And call it like this.
var query = from ord in ctx.Table_Orders
select new OrderViewModel(ord);
The Problem: The join is gone DB query is no more optimised. Now I get 1+N calls to database to fetch CurrencyText for every line.
Any comments are welcome. Maybe I have missed different great approach.
This is how far i could get on my own, to get the code reusability. I created a function that does the job and has multiple parameters. Then I need to explicitly pass it everything that has crossed the line of entity.
var query = ctx.Table_Orders.Select(m =>
newOrderViewModel(m, m.Currency.CurrencyText));
The DB call is again optimized. But it still does not feel like I am there yet! What tricks do You know for this case?
EDIT : The final solution
Thanks to a hint by #Muhammad Adeel Zahid I arrived at this solution.
I created an extension for IQueryable
public static class Mappers
{
public static IEnumerable<OrderViewModel> OrderViewModels(this IQueryable<Table_Order> q)
{
return from ord in q
select new OrderViewModel()
{
OrderId = ord.OrderId,
OrderSum = ord.OrderSum,
OrderCurrencyId = ord.OrderCurrencyId,
OrderCurrencyView = ord.Currency.CurrencyText
};
}
}
Now i can do this to get all list
var orders = ctx.Table_Order.OrderViewModels().ToList();
or this to get a single item, or anything in between with Where(x => ..)
var order = ctx.Table_Order
.Where(x => x.OrderId == id).OrderViewModels().SingleOrDefault();
And that completely solves this question. The SQL generated is perfect and the code to translate objects is reusable. Approach like this should work with both LINQ to SQL and LINQ to Entities. (Not tested with the latter) Thank You again #Muhammad Adeel Zahid
Whenever we query the database, we mostly require either enumeration of objects (more than one records in db) or we want a single entity (one record in db). you can write your mapping code in method that returns enumeration for whole table like
public IEnumerable<OrderViewModel> GetAllOrders()
{
return from ord in ctx.Table_Orders
select new OrderViewModel()
{
OrderId = ord.OrderId,
OrderSum = ord.OrderSum,
OrderCurrencyId = ord.OrderCurrencyId,
OrderCurrencyView = ord.Currency.CurrencyText
};
}
Now you may want to filter these records and return another enumeration for example on currencyID
public IEnumerable<OrderViewModel> GetOrdersByCurrency(int CurrencyID)
{
return GetAllOrders().Where(x=>x.CurrencyId == CurrencyID);
}
Now you may also want to find single record out of all these view models
public OrderViewModel GetOrder(int OrderID)
{
return GetAllOrders().SingleOrDefault(x=>x.OrderId == OrderID);
}
The beauty of IEnumerable is that it keeps adding conditions to query and does not execute it until it is needed. so your whole table will not be loaded unless you really want it and you have kept your code in single place. Now if there are any changes in ViewModel Mapping or in query itself, it has to be done in GetAllOrders() method, rest of code will stay unchanged
You can avoid the N+1 queries problem by having Linq2SQL eagerly load the referenced entites you need to construct your viewmodels. This way you can build one list of objects (and some referenced objects) and use it to construct everything. Have a look at this blog post.
One word of warning though: This technique (setting LoadOptions for the Linq2SQL data context) can only be done once per data context. If you need to perform a second query with a different eager loading configuration, you must re-initalize your data context. I automated this with a simple wrapper class around my context.

Entity Framework 4.0 - Returning List of Model objects + Count of Children Per Object

I'm running into a common need in my project to return collections of my model objects, plus a count of certain types of children within each, but I don't know if it is possible or how to model a "TotalCount" property in a Model class and populate it as part of on single Entity Framework query, preferably using LINQ queries. Is it possible to do this whilst being able to use the Entity Framework .Include("Object") and .Skip() and .Take()? I'm new to the Entity Framework so I may be missing tons of obvious stuff that can allow this...
I would like to be able to paginate on the dynamically constructed count properties as well. I'm thinking that the most scalable approach would be to store the counts as separate database properties and then simply query the count properties. But for cases where there are small row counts that I'm dealing with, I'd rather do the counts dynamically.
In a model like this:
Table: Class
Table: Professor
Table: Attendee
Table: ClassComment
I'd like to return a list of Class objects in the form of List, but I would also like the counts of Attendees and Class comments to be determined in a single query (LINQ preferred) and set in two Class properties called AttendeeCount and ClassCommentCount.
I have this thus far:
var query = from u in context.Classes
orderby tl.Name
select u;
List<Class> topics = ((ObjectQuery<Class>)query)
.Include("ClassComments")
.Skip(startRecord).Take(recordsToReturn).ToList();
Any suggestions or alternative query approaches that can still allow the use of .Include() and pagination would be much much appreciated, in order to produce a single database query, if at all possible. Thank you for any suggestions!
Try this:
public class ClassViewModel {
public Class Class { get; set; }
public int AttendeeCount { get; set; }
public int ClassCommentCount { get; set; }
}
var viewModel = context.Classes.Select(clas =>
new ClassViewModel {
Class = clas,
AttendeeCount = clas.ClassAttendes.Count,
ClassCommentCount = clas.ClassComments.Count}
).OrderBy(model => model.ClassCommentCount).Skip(startRecord).Take(recordsToReturn).ToList();
You don't have to include comments to get count.
It will not work this way. The easiest approach is to use projection into anonymous (or custom) non entity type. I would try something like this:
var query = context.Classes
.Include("ClassComments") // Only add this if you want eager loading of all realted comments
.OrderBy(c => c.Name)
.Skip(startRecord)
.Take(recordsToReturn)
.Select(c => new
{
Class = c,
AttendeeCount = c.Attendees.Count(),
ClassCommentCount = c.ClassComments.Count() // Not needed because you are loading all Class comments so you can call Count on loaded collection
});
The problem in your requirement are AttendeeCount and ClassCommentCount properties. You can't easily add them to your model because there is no corresponding column in database (unless you define one and in such case you don't need to manually count records). You can define them in partial Class implementation but in such case you can't use them in Linq-to-entities query.
The only way to map this in EF is to use DB view and create special read only entity to represent it in your applicaiton or to use DefiningQuery which is custom SQL command defined in SSDL instead of DB table or view.

Categories