EF6 Getting Count In Single Call - c#

Try to do a single database call to get an entity, as well as the count of related child entities.
I know I can retrieve the count using
var count = Context.MyChildEntitySet.Where(....).Count();
or even MyEntity.ListNavigationProperty.Count()
But That means getting the entity first, followed by another call in order to get the count or use an Include which would retrieve the whole list of related entities.
I am wondering is it possible to add a "Computed" column in SQL Server to return the Count of related rows in another table?
If not how do I ask EF to retrieve the related count for each entity in once call?
I am thinking of possibly using Join with GroupBy, but this seems an Ugly solution/hack.
public class MyEntity
{
public uint NumberOfVotes{ get; private set; }
}
which ideally woudl generate SQL Similar to:
SELECT
*,
(SELECT Count(*) FROM ChildTable c WHERE c.ParentId = p.Id) NumberOfVotes
FROM ParentTable p

UPDATED
You can always drop down to using actual SQL in the following way...
string query = "SELECT *,
(SELECT Count(*) FROM ChildTable c
WHERE c.ParentId = p.Id) as NumberOfVotes
FROM ParentTable p";
RowShape[] rows = ctx.Database.SqlQuery<RowShape>(query, new object[] { }).ToArray();
I realize this is not ideal because then you are taking a dependency on the SQL dialect of your target database. If you moved form SQL Server to something else then you would need to check and maybe modify your string.
The RowShape class is defined with the properties that match the ParentTable along with an extra property called NumberOfVotes that has the calculated count.
Still, a possible workaround.

Related

Entity Framework with multiple LEFT OUTER JOINs where one "joins" on to the other

I'm relatively new to EF, so my apologies if this is an EF 101 type of question. I have a SQL query that I'm trying to convert to C#. The following SQL query returns 290,903 records:
SELECT clcl.CLCL_ID, clcl.CLST_MCTR_REAS, clcl.CLCL_LOW_SVC_DT, clcl.CLCL_ME_AGE, grgr.CICI_ID, clcl.NWNW_ID
FROM [FACETS].[dbo].[CMC_CLCL_CLAIM] clcl WITH(NOLOCK)
INNER JOIN [FACETS].[dbo].[CMC_CLED_EDI_DATA] cled WITH(NOLOCK)ON clcl.CLCL_ID = cled.CLCL_ID
INNER JOIN [FACETS].[dbo].[CMC_PRPR_PROV] prpr WITH(NOLOCK)ON clcl.PRPR_ID = prpr.PRPR_ID
INNER JOIN [FACETS].[dbo].[CMC_SBSB_SUBSC] sbsb WITH(NOLOCK)ON clcl.SBSB_CK = sbsb.SBSB_CK
INNER JOIN [FACETS].[dbo].[CMC_SBAD_ADDR] sbad WITH(NOLOCK)ON clcl.SBSB_CK = sbad.SBSB_CK
AND sbsb.SBAD_TYPE_HOME = sbad.SBAD_TYPE
INNER JOIN FACETS.dbo.CMC_GRGR_GROUP grgr WITH(NOLOCK)ON grgr.GRGR_CK = clcl.GRGR_CK
LEFT OUTER JOIN [FACETS].[dbo].[CMC_CLHP_HOSP] clhp WITH(NOLOCK)ON clcl.CLCL_ID = clhp.CLCL_ID
LEFT OUTER JOIN [FACETS].[dbo].[CMC_HBCD_BILL_DESC] hbcd WITH(NOLOCK)ON hbcd.HBCD_ID = clhp.CLHP_FAC_TYPE + clhp.CLHP_BILL_CLASS
WHERE clcl.CLCL_CUR_STS IN ('11', '15')
I'm trying to convert it to C# with the following code:
ppoClaims = (from clcl in _context.CMC_CLCL_CLAIM
join cled in _context.CMC_CLED_EDI_DATA on clcl.CLCL_ID equals cled.CLCL_ID
join prpr in _context.CMC_PRPR_PROV on clcl.PRPR_ID equals prpr.PRPR_ID
join sbsb in _context.CMC_SBSB_SUBSC on clcl.SBSB_CK equals sbsb.SBSB_CK
join sbad in _context.CMC_SBAD_ADDR on clcl.SBSB_CK equals sbad.SBSB_CK
join grgr in _context.CMC_GRGR_GROUP on clcl.GRGR_CK equals grgr.GRGR_CK
join clhp in _context.CMC_CLHP_HOSP on clcl.CLCL_ID equals clhp.CLCL_ID into SUBclhp
from z in SUBclhp.DefaultIfEmpty()
join hbcd in _context.CMC_HBCD_BILL_DESC on z.CLHP_FAC_TYPE + z.CLHP_BILL_CLASS equals hbcd.HBCD_ID
where sbsb.SBAD_TYPE_HOME == sbad.SBAD_TYPE
&& staticVars.CLCL_CUR_STS.Contains(clcl.CLCL_CUR_STS)
However it only return 48,930 records, so clearly I'm doing something wrong.
I think my problem could lie in one of two spots. Either the
AND sbsb.SBAD_TYPE_HOME = sbad.SBAD_TYPE
being put in my where clause in the C#. Or my attempt at the left outer joins here
join clhp in _context.CMC_CLHP_HOSP on clcl.CLCL_ID equals clhp.CLCL_ID into SUBclhp
from z in SUBclhp.DefaultIfEmpty()
join hbcd in _context.CMC_HBCD_BILL_DESC on z.CLHP_FAC_TYPE + z.CLHP_BILL_CLASS equals hbcd.HBCD_ID
Or, maybe it's a combination of both? I feel like I'm pretty close, just need one or two small alterations. Any help is much appreciated.
Edit: The staticVars.CLCL_CUR_STS in the C# is an array that contains "11" and "15"
Firstly, EF is an ORM not merely a replacement for ADO. You map entities to tables, but more importantly, you map the relations between these entities so that EF can work out the joins for you automatically. The fact that EF's Linq queries have a join operation is merely for exceptional scenarios. The goal with EF isn't to just give you a different way to write SQL queries, it is to avoid writing SQL-like queries all-together.
To that end, each entity that contains relations to other entities should contain references and collections for the various many-to-one or one-to-many relationships, or many-to-many or one-to-one relationships respectively.
Your job with EF starts with mapping out these relationships and creating the appropriate navigation properties. This also gives you the opportunity to give your entities a more friendly, readable name where the database table conforms to legacy naming conventions/requirements. For example, looking briefly at your schema:
[Table("CMC_CLCL_CLAIM")]
public class Claim
{
[Key, Column("CLCL_ID")]
public int Id { get; set; }
[Column("CLCL_CUR_STS")]
public string CurrentStatus { get; set; }
// ... other columns ...
public virtual ICollection<Group> Groups { get; set; }
// ... and other references, collections, etc.
}
[Table("CMC_GRGR_Group")]
public class Group
{
[Key, Column("GRGR_ID")]
public int Id { get; set; }
//... Other group fields...
[ForeignKey("Claim"), Column("CLCL_ID")]
public int ClaimId { get; set; }
public virtual Claim Claim { get; set; }
// Probaby a FK to another table?
public int CiCiId { get; set; }
}
EF can automatically work out many relationships, but there can be cases where it helps to do it a bit more explicitly with attributes and/or EntityTypeConfiguration / DbContext.OnModelCreating which can help streamline entities to avoid bi-directional references if not needed, or declaring FK properties. (That's a bit more advanced material to research, but for a start focus on setting up the relations in the first place)
Once these navigation properties are set up, then you build your Linq expressions around the entities and let EF build your SQL. From that you can compare the results and determine if all of the relationships are working properly.
For instance you can load the entity and its related data, or project the data from this entity graph (the entity and related entities) into a desired view model or anonymous type.
So for instance in your example query, it doesn't really make sense to be joining all of those tables when the SELECT is only pulling data from the CLCL Claim and one column from the GRGR Group which joins off the Claim, none of the other tables.
The query would look more like:
var data = _context.Claims
.Where(x => staticVars.ActiveStatuses.Contains(x.CurrentStatus))
.SelectMany(x => x.Groups.Select( g => new
{
x.Reason,
x.LowServiceDate,
x.MedianAge,
g.CiCiId,
x.NwnwId
}).ToList();
Based on the joins you want a list of these GroupIds with the relevant details from the claim where one claim can have multiple Groups. Again the entity property names are guesses at whatever the original schema fields mean. It's more for demonstration that you can use more meaningful names in your entities to make your code easier to understand rather than propagating archaic naming conventions.
Ultimately the Joins in the original tables can possibly be resulting in further Cartesian products. If the goal in the original query with the unused INNER JOINS was just to enforce limiting data for Null-able FKs to return data where those FKs are populated (rather than using non-nullable FKs) then the Where clause above can be updated to AND (&&) in conditions where the associated related entities are != null.
In any case, when working with EF, it is better to set up the relationships between the entities, then approach the requirements as "I want to return this data from the entity graph in this format with these filters etc." rather than "I want to reproduce this SQL query".
If instead you just want to port SQL into C# then I'd recommend just writing Stored Procedures or Views and mapping read-only entities to those outputs rather than trying to map tables and trying to build Linq versions of those queries.

Linq how to use a subSelect as a virtual table in FROM

There may be similar questions out here but none that I could find for doing a subSelect in the FROM clause as a virtual table.
Most of the columns I need are in one table. There are a few columns needed from different tables that I cannot join on without getting a Cartesian join.
Here is my SQL query:
SELECT meter_name, a.loc_id, a.loc_name, a.facility_name, meter_type
FROM meter_table, (SELECT loc_id, loc_name, facility_name
FROM facility_table
WHERE id = 101) a
WHERE meter_id = a.fac_id
I have no idea how to convert this into Linq and it must be done tonight for a demo in the morning.
Assume this represents your meter_table within your database
in this case each element of the list represents a record in the database table holding the appropriate attributes
i.e the table columns will become the properties of each object
List<Meter> meter_table = new List<Meter>();
Assume this represents the facility_table table you want to join with.
same goes here, each element of the list represents a record in the database table holding the appropriate attributes
i.e the table columns will become the properties of each object
List<Facility> facility_table = new List<Facility>();
then perform the inner join like so:
var query = from m in meter_table
join a in facility_table on m.meter_id equals a.fac_id
where a.id == 101
select new { meter_name = m.MeterName,
loc_id = a.LocId,
facility_name = a.FacilityName,
meter_type = m.MeterType
};
where m.MeterName, a.LocId, a.FacilityName, m.MeterType are properties of their respective types.
it's also worth noting the variable query references an IEnumerable of anonymous types. However, if you want to return an IEnumerable of strongly typed objects then feel free to define your own type with the appropriate properties then just change select new to:
select new typeName { /* assign values appropriately */}
of the above query.

How to set the count property of a related entity without having to perform an extra query or include unnecessary data

I have a class "Campaign" that has a navigation property "Students". The campaign class has an attribute "StudentsCount" to store the amount of students. I do not want to include all the students in the query results. How can I query for campaigns while attaching their respective student counts? I ideally do not want to iterate through all my campaigns after the initial query to grab the counts.
IQueryable<TEntity> query = this._objectSet.AsQueryable(); //this is my campaigns object set
query = query.Where(c => c.UserId == id);
query = query.Include("");
return query.ToArray();
Update: --
Please note that my initial query is grabbing more than one campaign
I'm thinking maybe I could do something with a select but I am not exactly sure how to accomplish this
Querying for counts without loading the collection of child items is called an extra lazy query if you need the term to allow you to Google around this.
In this case, you would do something like this:
var campaign = query.Single();
var studentsQuery = context.Entry(campaign).Collection(c => c.Students).Query();
var count = studentsQuery.Count();
This will materialise the count of entities without bringing them all back.
I ended up adding a computed column with sql onto the Campaign Table.
CREATE FUNCTION dbo.getStudentCount(#studentCount int)
RETURNS int
AS
BEGIN
DECLARE #r int
select #r = COUNT(*) from Student where CampaignId = #studentCount
RETURN #r
END
GO
ALTER TABLE Campaign ADD StudentCount AS dbo.getStudentCount(Id)
this automatically sets the column to be a generated attribute in the EDMX.

Too many select calls to database for linq Many-To-Many join table

My problem is for a complex database so I have used a simplified version below.
I have 3 tables. Lets call them:
Course
- id
- name
Student
- id
- name
StudentCourses
- id
- student_id
- course_id
StudentCourses is set to relate to both the other tables, a fairly normal many-to-many pattern.
I have extended my Student object using partials, and added a fairly basic method to my Student class in order to retrieve all of their classes.
// Partial to add helper method to LINQ generated Student class
public partial class Student {
// Get courses that this student is enrolled.
public IQueryable<Course> GetCourses(){
return this.StudentCourses.Select(d=>d.Course);
}
}
I expected this might use a join internally, but having logged the SQL queries I can see that it actually makes one "SELECT * FROM Courses" for each join.
What would be a better way to perform this behaviour? One select per row seems terribly inefficient!
Further Investigation
After a few comments I decided to investigate further. I have found the following:
MyDataContext db = new MyDataContext();
var student = db.Students.Single(i=>i.id);
var testOne = db.StudentCourses.Where(u=>u.student_id == student.id).
Select(d=>d.Courses).ToList();
var testTwo = student.StudentCourses.Select(d=>d.Courses).ToList();
In test one, the SQL performed uses a JOIN and is therefore a single database call.
In test two, the SQL is one select PER course. Which is a problem.
Is this just expected behaviour? I can refactor to the first type I guess, but I prefer the logic of the second test.
For LinqToSql use DataLoadOptions
Example :
public IQueryable<Course> GetCourses(){
var dlo = new DataLoadOptions();
dlo.LoadWith<StudentCourses>(d => d.Course);
this.LoadOptions = dlo;
return this.GetTable<StudentCourses>().Select(d=>d.Course);
}
Hope this will help !!

How do I encapsulate this data from sql using c#?

I have the script:
select
fk.name,
object_name(fk.parent_object_id) 'Parent table',
c1.name 'Parent column',
object_name(fk.referenced_object_id) 'Referenced table',
c2.name 'Referenced column'
from
sys.foreign_keys fk
inner join
sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
inner join
sys.columns c1 ON fkc.parent_column_id = c1.column_id and c1.object_id = fkc.parent_object_id
inner join
sys.columns c2 ON fkc.referenced_column_id = c2.column_id and c2.object_id = fkc.referenced_object_id
And I know that I get a result set back with 5 columns. Is there is a slick and efficient way to store this data in a linq type object or an iQueryable object? I want to be able to iterate through it...
There are a few ORMs that use IQueryable: Entity Framework, LINQ to SQL, NHibernate, Subsonic, etc. I recommend trying one out.
For a very lightweight ORM, you can use the DataContext's ExecuteQuery:
class YourRow
{
public string Col1 { get; set; }
public string Col2 { get; set; }
...
}
// DataContext takes a connection string as parameter
var db = new DataContext("Data Source=myServerAddress;" +
"Initial Catalog=myDataBase;User Id=myUsername;Password=myPassword;");
var rows = db.ExecuteQuery<YourRow>(#"
select fk.name,
object_name(fk.parent_object_id) 'Parent table',
...
");
If you can store your SQL query in a view, you can drag the view to a DBML file to have LINQ create the wrapper class for you.
If you execute this script by using a DbCommand and a DataReader you can iterate over the rows that are returned and add them to a list of a custom object that will hold the 5 columns.
You can then use Linq To Objects to filter the list even more.
Or you can use an ORM to map your entities to the database. If you create a Database View for your query you can map an entity to the view with for example the Entity Framework and use that to execute further queries.
You can store this into DataTable. You have add assembly System.Data.Exetensions in your project to used DataTable as IQueryable.
from tbl in dataTable.AsEnumerable()
//where clause
select tbl;
DataView view = tbl.AsDataView();

Categories