LINQ Query using Foreign Keys - c#

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.

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.

Writing a subquery using LINQ in C#

I would like to query a DataTable that produces a DataTable that requires a subquery. I am having trouble finding an appropriate example.
This is the subquery in SQL that I would like to create:
SELECT *
FROM SectionDataTable
WHERE SectionDataTable.CourseID = (SELECT SectionDataTable.CourseID
FROM SectionDataTable
WHERE SectionDataTable.SectionID = iSectionID)
I have the SectionID, iSectionID and I would like to return all of the records in the Section table that has the CourseID of the iSectionID.
I can do this using 2 separate queries as shown below, but I think a subquery would be better.
string tstrFilter = createEqualFilterExpression("SectionID", strCriteria);
tdtFiltered = TableInfo.Select(tstrFilter).CopyToDataTable();
iSelectedCourseID = tdtFiltered.AsEnumerable().Select(id => id.Field<int>("CourseID")).FirstOrDefault();
tdtFiltered.Clear();
tstrFilter = createEqualFilterExpression("CourseID", iSelectedCourseID.ToString());
tdtFiltered = TableInfo.Select(tstrFilter).CopyToDataTable();
Although it doesn't answer your question directly, what you are trying to do is much better suited for an inner join:
SELECT *
FROM SectionDataTable S1
INNER JOIN SectionDataTable S2 ON S1.CourseID = S2.CourseID
WHERE S2.SectionID = iSectionID
This then could be modeled very similarily using linq:
var query = from s1 in SectionDataTable
join s2 in SectionDataTable
on s1.CourseID equals s2.CourseID
where s2.SectionID == iSectionID
select s1;
When working in LINQ you have to think of the things a bit differently. Though you can go as per the Miky's suggestion. But personally I would prefer to use the Navigational properties.
For example in your given example I can understand that you have at-least 2 tables,
Course Master
Section Master
One Section must contain a Course reference
Which means
One Course can be in multiple Sections
Now if I see these tables as entities in my model I would see navigational properties as,
Course.Sections //<- Sections is actually a collection
Section.Course //<- Course is an object
So the same query can be written as,
var lstSections = context.Sections.Where(s => s.Course.Sections.Any(c => c.SectionID == iSectionID)).ToList();
I think you main goal is, you are trying extract all the Sections where Courses are same as given Section's Courses.

Entity Framework - Database View or Joins in Linq to Entities

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.

SQL giving me headache - multiple joins

First I'll explain my scenario. Here's the 2 general tables in use:
Accounts:
LegacyID = The identifer of the
account
InvoiceAccount = An account can have
multiple subaccounts in the same
table. This field is the LegacyID
of its parent account
Customer = There can be two different
types of accounts, and they can share
ID's - so this column differentiates
between the 2. For the purposes of my
SQL I always want it to be true rather
than false
AllocatedUser = The username of the
person who needs to see this
deliveries. This is only populated on
the parent account, so I need to link
back to get this for the subaccounts
Deliveries:
LegacyID = The deliveries identifier
Customer = The LegacyID of the account
related to the delivery (can be a
subaccount)
OnHold = A flag which for the purposes
of my query needs to be 'true'
Now that's explained, basically I need an SQL which returns any deliveries that are 'OnHold', but only for deliveries for accounts that are allocated the logged in user. The query for selecting the AllocatedUser if the delivery links to a parent account was simple, but I'm having issues with returning rows if the delivery is linked to a subaccount - it's simply not returning any. Here is the SQL below:
SELECT Deliveries_1.LegacyID, Deliveries_1.TripDate, Deliveries_1.OnHoldReason, Account_2.AllocatedUser
FROM Deliveries AS Deliveries_1 INNER JOIN
Account AS Account_1 ON Deliveries_1.Customer = Account_1.InvoiceAccount INNER JOIN
Account AS Account_2 ON Account_1.InvoiceAccount = Account_2.LegacyID
WHERE (Deliveries_1.OnHold = #OnHold) AND (Account_2.Customer = 'True') AND (Account_2.AllocatedUser = #AllocatedUser)
My mind is frazzled from trying to work out why it don't work at the moment - I'd really appreciate any advice.
Thanks!
It sounds like you're looking for records in the Deliveries table that are associated with a user's account, the user's direct parent's account, and the user's direct child's account. If this is true modifying your query as follows should do the trick.
SELECT DISTINCT d.LegacyID, d.TripDate, d.OnHoldReason,
case
when child.LegacyID = d.Customer then child.AllocatedUser
when parent.LegacyID = d.Customer then parent.AllocatedUser
else this.AllocatedUser
end as 'Delivery_AllocatedUser'
FROM Account this
LEFT JOIN Account parent on parent.LegacyID = this.InvoiceAccount
LEFT JOIN Account child on this.LegacyID = child.InvoiceAccount
JOIN Deliveries d on (d.Customer = this.LegacyID AND this.Customer = 'True')
OR (d.Customer = parent.LegacyID AND parent.Customer = 'True')
OR (d.Customer = child.LegacyID AND child.Customer = 'True')
WHERE d.OnHold = #OnHold
AND this.AllocatedUser = #AllocatedUser
First comment is that you should build the joins in steps if you are having issues.
Second comment is that in the query you posted there is unnecessary join - you are not filtering nor selecting nor joining on anything specific from table aliased as Account_1.
Maybe that is an indication of what you are doing wrong. Otherwise, the query looks(!) ok (assuming that all the joins are correct; your descriptions are not exact - for example for Accounts.Customer you say that 'For the purposes of my SQL I always want it to be true rather than false' - well the Customer is not true/false field is it? What do you mean? There are other such inconsistencies...)
Some sample data and a sample of the result could shed some more light.
ON Deliveries_1.Customer = Account_1.InvoiceAccount
ON Account_1.InvoiceAccount = Account_2.LegacyID
If both of these criteria are true, then Delivery.Customer would equal Account2.LegacyId (delivery would have to be on the parent account). I don't think that's what you intended.
Perhaps you meant
ON Deliveries_1.Customer = Account_1.LegacyID
ON Account_1.InvoiceAccount = Account_2.LegacyID

Categories