Select rows with minumum date in join query - c#

I am using entity Framework and LINQ two perform the join between these two tables:
var query =
orders.Join(
orderdetails,
order => order.Code,
orderdt => orderdt.Order.Code,
(order, orderdt)
=> new
{
Code = order.Code,
WarehouseDivision = orderdt.WarehouseDivision,
Type = order.Type,
SupplierCode = order.SupplierCode,
SupplierDescription = order.SupplierDescription,
ExpectedDeliveryDate = orderdt.ExpectedDeliveryDate
});
the join works fine.
Now for each join row I need to select the row with the minimum ExpectedDelivery Date.
Any hint on how to achieve this?

I see something strange on your Join.
Every Order has a Code. Every OrderDetail has an Order, which also has a Code.
If there is a one-to-many relationship between Orders and OrderDetails, then every Order has zero or more OrderDetails. Every OrderDetail belongs to exactly one Order, namely the Order that the foreign key points to. Is that the same Order as OrderDetail.Order?
In that case Order.Code equals OrderDetail.Order.Code for every OrderDetail of this code. Not very useful to perform a join on these values.
I think that you mean to join on primary key and foreign key.
Either way, whether you join on keys or on property Code, the solution to your problem is to perform a GroupJoin instead of a Join.
If you've followed the entity framework code first conventions, you'll have something similar to this:
class Order
{
public int Id {get; set;} // Primary key
... // other Order properties
// every Order has zero or more OrderDetails (one-to-many)
public virtual ICollection<OrderDetail> OrderDetails {get; set;}
}
class OrderDetail
{
public int Id {get; set;}
public DateTime ExpectedDeliveryDate {get; set;}
... // other OrderDetail properties
// every OrderDetail belongs to exactly one Order, using foreign key
public int OrderId {get; set;}
public virtual Order Order {get; set;}
}
It might be that you used different identifiers for your properties, but the main thing are the virtual properties.
In entity framework the non-virtual properties represent the columns in the table, the virtual properties represent the relations between the tables (one-to-many, many-to-many).
Requirement
From every Order, give me some properties, together with some properties of the OrderDetail with the minimum ExpectedDeliveryDate.
Solution with GroupJoin
GroupJoin on primary / foreign key:
var OrdersWithOldestOrderDetail = dbContext.Orders // GroupJoin Orders
.GroupJoin(dbContext.OrderDetails, // with OrderDetails
order => order.Id, // from every Order take the Id
orderDetail => orderDetail.OrderId, // from every OrderDetail take the OrderId
(order, orderDetailsOfThisOrder) => new // take the Order with all its matchin OrderDetails
{ // to make one new
// Select the Order properties that you plan to use:
...
// Select the OrderDetail with the minimum ExpectedDeliveryDate
// To do this, order by ascending deliveryDate
// then Select the properties you want to used
// and take the FirstOrDefault
EarliestDeliveredOrderDetail = orderDetailsOfThisOrder
.OrderBy(orderDetail => orderDetail.ExpectedDeliveryDate)
.Select(orderDetail => new
{
...
})
.ToList(),
});
If you really want to join on the properties you said:
var result = dbContext.Orders // GroupJoin Orders
.GroupJoin(dbContext.OrderDetails, // with OrderDetails
order => order.Code, // from every Order take the Code
orderDetail => orderDetail.Order.Code, // from every OrderDetail take Order.Code
(order, orderDetailsWithThisCode) => new // take the Order with all its matchin OrderDetails
{
... etc, same as above
Easier Solution using the ICollection
If you are using entity framework and you need to select "All orders with its OrderDetails", "All Schools with its Students", "All Products with its Components", in fact: all Items together with all related sub-items, it is usually way simpler to use the ICollections instead of executing a join yourself:
var OrdersWithOrderDetails = dbContext.Orders.Select(order => new
{
// Select the Order properties that you plan to use:
...
EarliestDeliveredOrderDetail = order.OrderDetails
.OrderBy(orderDetail => orderDetail.ExpectedDeliveryDate)
.Select(orderDetail => new
{
// Select the OrderDetail properties that you plan to use:
...
})
.FirstOrDefault(),
})
See how naturally it feels if you use the ICollections instead of the (Group-)join? Entity framework knows your relations and performs the correct join for you with the correct (foreign) keys.
In fact, I seldom create a Join myself. Most of the time I use the virtual properties.

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 join multiple tables by same column

I have one table Texting, that I need to join with another two tables Student and Staff to search for information about these 2 tables.
Student fields:
Id
Name
... and a bunch of other fields specific to student
Staff fields:
Id
Name
... and a bunch of other fields specific to staff
Texting fields:
Id
PersonId // contains either student ID or staff ID
PersonTypeId // indicates whether PersonId is of type student or staff (student = 1, staff = 2)
Now I need to write a linq query to search the table Texting either by student or staff name but I am stuck on the linq to achieve this.
var query = (from t in texting
join s in studentBo.GetListQuery()
on t.PersonId equals s.Id
join st in staffBo.GetListQuery()
on t.PersonId equals st.Id
where ...
select t);
This joins the tables together but it doesnt care what the PersonId type is so it's all mixed. How do i specify so that it joins the PersonId correctly according to the right PersonTypeId? It seems like nothing else can be appended on the on clause or where clause to make this happen = (.
So you have a name, and you want all Textings that refer to a Student with this name, and all Textings that refer to a member of Staff with this Name.
My advice would be to concat the Student textings with the Staff textings. You could do that in one big LINQ statements, however this would make it quite difficult to understand. So I'll do it in two steps, then Concat it in one query:
const int student = 1;
string name = "William Shakespeare";
var studentTextings = textings.Where(texting => texting.PersonTypeId == student)
.Join(students.Where(student => student.Name == name),
texting => texting.PersonId, // from every Texting take the foreign key
student => student.Id, // from every Student take the primary key
// parameter resultSelector:
// from every texting with its matching student make one new:
(texting, studentWithThisTexting) => new
{
// Select the Texting properties that you plan to use
Id = texting.Id,
...
}
In words: from all Textings, keep only those Textings that refer to a student, so you know that the foreign key refers to a primary key in the table of Students. From all Students keep only those Students that have the requested name.
Join all remaining Textings and the few remaining Students that have this name on primary and matching foreign key.
Do something similar for members of Staff:
const int staff = 2;
var staffTextings = textings.Where(texting => texting.PersonTypeId == staff)
.Join(staffMembers.Where(staffMember => staffMember.Name == name),
texting => texting.PersonId, // from every Texting take the foreign key
staffMember => staffMember.Id, // from every Staff member take the primary key
// parameter resultSelector:
(texting, staffMembers) => new
{
// Select the Texting properties that you plan to use
Id = texting.Id,
...
}
Now all you have to do is Concat these two. Be aware: you can only Concat similar items, so the resultSelector in both Joins should select objects of exactly the same type.
var textingsOfPersonsWithThisName = studentTextings.Concat(staffTextings);
There is room for improvement!
If you look closely, you'll see that the textings table will be scanned twice. The reason for this, is because your database is not normalized.
Can it be, that a Texting for a Student will ever become a Texting for a member of Staff? If not, my advice would be to make two tables: StudentTextings and StaffTextings. Apart from that queries will be faster, because you don't have to check PersonType, this also has the advantage that if later you decide that a StudentTexting differs from a StaffTexting, you can change the tables without running into problems.
If you really think that sometimes you need to change the type of a texting, and you don't want to do this by creating a new texting, you also should have two tables: one with StudentTextings, and one with StaffTextings, both tables having a one-to-one relations with a Texting.
So Students have one-to-many with StudentTextings, which have one-to-one with Textings. Similar for Staff and StaffTextings.
So Student [4] has 3 StudentTextings with Id [30], [34], [37]. Each of these StudentTextings have a foreign key StudentId with value [4]. Each StudentTexting refers to their own Texting with a foreign key: [30] refers to texting [101], so it has foreign key 101, etc.
Now if texting [101] has to become a texting for Staff [7], you'll have to delete the StudentTexting that refers to [101] and create a new StaffTexting that refers to Staff [7] and Texting [101]
By the way, since the combination [StudentId, TextingId] will be unique, table StudentTextings can use this combination as primary key. Similar for StaffTextings
You will have to merge Student and Staff tables, otherwise all your queries will be too complicated, since you will have to use Union
Person
Id
Name
PersonType
Texting
Id
PersonId
and query
var query = (from t in texting
join p in person
on t.PersonId equals p.Id
where ...
select t);
PS if you still want a query with 2 tables instead of one, you will have to post the real code.
You'll need to do these as two separate queries, project to a new type and then union the results. Messy, but here's how.
First get your students:
var textingStudents = (
from s in students
join t in texting on s.Id equals t.PersonId
where t.PersonTypeId == 1
select new { id = s.Id, personTypeId = 1, name = s.Name }).ToList();
Now get your staff in almost the exact same way:
var textingStaff = (
from s in staff
join t in texting on s.Id equals t.PersonId
where t.PersonTypeId == 2
select new { id = s.Id, personTypeId = 2, name = s.Name }).ToList();
Now you can union the two:
var allTextingPeople = textingStudents.Union(textingStaff);
If you need additional properties then add then to the anonymous type declared in the select statement - remember, the type will need to have the same properties in both the textingStudents and textingStaff result. Alternatively, define a class and do a select new MyUnionClass { ... } in both queries.
Edit
You're going to probably get into a world of hurt with the current approach you've outlined. If you're using a relational database (i.e. sql server) you almost certainly are not defining constraints such as foreign keys on your Texting table meaning you'll end up with ID clashes and will definitely end up with bugs later down the road. Best approach is probably to have one table to represent Staff and Student (let's call it Person with a column defining the "type" of person - the column itself will be foreign key link to another table with your list of PersonTypes

Include unrelated table in Linq to entity query

I have the following simplified setup:
Public User
{
//Primary key
public int Id {get;set;}
public string Name {get; set;}
}
Public UserInfo
{
//Primary key
public int Id {get;set;}
//Foreign key to user table
public int userKey {get; set;}
}
The relationship between the tables is one user to Many userInfo
I am trying to select from the user table and include the userInfo table.
I cannot do this:
var users = Context.user.Include(u => u.userInfos);
as there is no reference to the UserInfo table from the user table.
I can do this:
context.userInfo.include(x => x.user)
but if there are no corresponding entries in the userInfo table, this will not return any results, which is not what I want. Also, this will return one row for each userInfo, whereas I want one row for user, with a list of userInfo as a parameter.
Similarly I could join the tables like this:
var users = from us in Context.user
join inf in Context.userInfo
on us.Id equals inf.userKey
select new //etc
But this will also return one row per userInfo entry, the same problem as above.
To summarise, is there a way of including this table to produce a result in the same way as the include function does.
I know I could adjust my setup to all me to include this, but that is not what I am asking here.
I suspect this cannot be done, but from all my googling so far I have not been able to find a definitive answer....
I want one row for user, with a list of userInfo as a parameter
I assume you mean a list of userInfo as a property. My understanding of what you ask it that you're just looking for:
var users = from us in Context.user
join inf in Context.userInfo
on us.Id equals inf.userKey into infg
select new
{
User = us,
UserInfos = infg
};
join ... into amounts to a GroupJoin, i.e. a user entity joined with a group of userinfos.
Better still is to use a navigation property user.userInfos (reluctantly following your naming convention):
var users = Context.user.Include(u => u.userInfos);

Adding multiple Products into same Order table. (M-M relationship)

Doing a shop project. Upon checkout, I create a list of all added product from cart and put it inside Order.Products(M-M relationship):
List<Product> productList = Cart.Select(item => db.Products.SingleOrDefault(x => x.Id == item)).ToList();
var addOrder = new Order
{
Id = Guid.NewGuid(),
UserId = userId,
OrderDate = DateTime.Now,
Products = productList
};
db.Orders.Add(addOrder);
db.SaveChanges();
I debugged my code to make sure the productsList had the right amount.
But after I added it, I only had one of each added products were added in my db. For e.g. if I have 3x cars, 4x books and 2x boat, then I just get one of each into M-M table, meaning total 3 products, even though I had total 9 in productList.
This is how I set up my m-m relationship.
create table Product_Order (
ProductId uniqueidentifier foreign key references [Product](ID) not null,
OrderId uniqueidentifier foreign key references [Order](Id) not null
)
Btw, is it possible to add additional column into Product_Order so called AmountProduct, and set amount value instead making many rows of same products/order? If possible, how do I set amount value inside var addOrder = new Order{? That way would probably be much more effective.

nhibernate: join tables for order by

I have an interesting scenario. I have 2 tables say 'Order' and 'Delivery'.
Order has columns- Orderid, Numberofunits , personID , cost ,orderdate, status
Delivery's Columns- DeliveryID , address, Deliverydate , personID
personID does not have a one to many (or many to many ) property.
mappings and classes of 'order' and 'delivery' are part of the bigger project which cannot be changed.( changing this would mess up lot of other parts of the project.)
Now i have a query
Order orderalias = null;
var order = session.QueryOver<Order>(() => orderalias )
.Where(() => orderalias .Status == orderalias .Ready)
.OrderBy(() =>orderalias.orderdate).Asc
.Take(4)
.List();
But i have to order by deliverydate joing 2 tables. I tried using joinalias but obviously does not allow me to do it. (i got not an association: error)
I tried using 'WithSubquery.WhereExists' but i cannot use deliverydate outside subquery. Is there a way i can join and order this without changing the mapping and the class ?
I would like the query to be like
select delivery.* from delivery
join order on order.personID = delivery.personId
where order.status = 'ready'
order by order.orderdate, delivery.deliverydate

Categories