I have a database table with many lookup tables:
OrderType
ShippingType
etc.
My Order table is referencing each one of these tables:
Order
OrderID
OrderTypeID
ShippingTypeID
I am using the Entity Framework as my data access layer. I have a page that needs to display information for an Order. I am struggling to figure out the best/right way to use these entities.
My page should be displaying the data like:
Order #1000000
Shipping Type: UPS
Order Type: Online
Etc Type: Etc.
Is it better to create a view in the database that brings back the data I need and then add it to my entity model, and just use that directly so I don't have to write joins in my queries? Or is it better to create an intermediate class like so:
class OrderView
{
public int OrderNumber { get; set; }
public string OrderType { get; set; }
public string ShippingType { get; set; }
}
var order = from o in db.Orders
join ot in db.OrderTypes on o.OrderTypeID equals ot.OrderTypeID
join st in db.ShippingTypes on o.ShippingTypeID equals st.ShippingTypeID
select new OrderView
{
OrderNumber = o.OrderNumber,
ShippingType = st.Description,
OrderType = ot.Description
};
What is the better way here?
You don't need join, per se. What you can do is use Navigation Properties for OrderType and ShippingType in order to access them without the need for joins. You'll have something like:
var order = from o in db.Orders
select new OrderView
{
OrderNumber = o.OrderNumber,
ShippingType = o.ShippingType.Description,
OrderType = o.OrderType.Description
};
I don't see any advantage to doing this in a view.
You can use Linq-to-entities. I usually switch to SQL or View when I have something which I can't write in L2E (like Common table expression and hiearchical queries) or when L2E performance is bad. If you do not have these problems you should be happy with L2E.
Btw. your query can be rewritten without joins - damn #Craig was faster.
Views can be good for several reasons:
They can insulate you from changes in the underlying table structure
They can abstract away normalisation details (the joins)
Yoi can revoke all permissions on tables, and provide restricted access through views.
Do whatever leads to more understandable code.
using a database view can hinder the SQL optimizer's ability to get you the best execution plan. so if the EF-generated query is not a horror (use SQL Profiler to make sure!)
i`d go for EF.
Related
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.
Still trying to get my head around the list and navigation concept rather than basic SQL join language. I have a slightly complex query that requires a fair amount of joins, though the bulk of information are navigable tables (Or have been in the past).
To explain the tables and directions of the queries.
At the top level there is a table called Company with a one-to-many relationship with Areas.
Areas has a many-to-one relationship with a table called Shifts.
Another table called Events stores a ShiftID and it was my thinking once the link between Event and Shift was made I could then navigate up the chain of relationships.
There are some other tables begin joined in which makes a pure lambda queries (At my current level of understanding) difficult.
My code thus far is
var UserEvents = from e in _context.Events
join s in _context.Shifts on e.ShiftID equals s.SHFID
where e.UID == userID
select new UserEvents
{
EVTID = e.EVTID,
UID = e.UID,
ShiftID = e.ShiftID,
EVTDate = e.EVTDate,
Notes = e.Notes,
AreaID = s.Area.AreaID,
AreaDesc = s.Area.AreaDesc,
CPYDesc = s.Area.Company.CPYDesc,
StartTime = s.StartTime,
EndTime = s.EndTime,
RequiredResources = s.RequiredResources,
ShiftDesc = s.ShiftDesc,
ShiftDayOfWeek = s.ShiftDayOfWeek
};
The Error that is being given is that Shifts does not contain a definition for Areas. Which in code talk is true, the Shifts.cs does not have a declared field for Area. Though Area has a public List<Shift> SHFID { get; set; }
This Query works fine in LINQ PAD but not in VS
Any help or clarification would be appreciated.
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.
I annoys me very much when programmers use var instead being precise and write the correct type. In Microsoft tutorial: http://msdn.microsoft.com/en-us/library/bb397927.aspx
I have found this explanation of inner join(below). What is probably the type of innerJoinQuery because it looks like mash up of 2 strings but we don't know what is the result type.
Quote:
Join operations create associations between sequences that are not explicitly modeled in the data sources. For example you can perform a join to find all the customers and distributors who have the same location. In LINQ the join clause always works against object collections instead of database tables directly.
C#
var innerJoinQuery =
from cust in customers
join dist in distributors on cust.City equals dist.City
select new { CustomerName = cust.Name, DistributorName = dist.Name };
In LINQ you do not have to use join as often as you do in SQL because foreign keys in LINQ are represented in the object model as properties that hold a collection of items. For example, a Customer object contains a collection of Order objects. Rather than performing a join, you access the orders by using dot notation:
EDIT: Let's rephrase this question. What else I can put instead of var here?
The result of this linq query is a sequence of objects of an anonymous type with two properties. One of them is called CustomerName and the other is called DistributorName. Both of them are quite possible of type string.
In order you avoid the var, you have to declare a class with these two properties, like below:
class ClassName
{
public string CustomerName { get; set; }
public string DistributorName { get; set; }
}
Then you have to change you linq query to the following one:
IEnumerable<ClassName> innerJoinQuery = from cust in customers
join dist in distributors
on cust.City equals dist.City
select new ClassName
{
CustomerName = cust.Name,
DistributorName = dist.Name
};
However I don't see any benefit in the above approach if you jsut want to get the results of this query and iterate through them or something else. In other words, I think that var is used correctly here. I mean it doesn't createa any ambiguities of what you do.
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 !!