Linq Group by, Concatenate and load child entities on a list - linq

I'm going to simplify a little bit my model in order to show what I want to do. I know perfectly how do that using SQLbut I'm having problems with EF and LINQ.
Sorry for the long post.
I have the next POCO's:
public class Order
{
public int Id {get; set;}
public double Quantity {get; set;}
public List<Status> StatusChanges{get; set;}
}
public class Status
{
public int Id {get; set;}
public DateTime StatusDate {get; set;}
public StatusType StatusType {get; set;}
}
public class StatusType
{
public int Id {get; set;}
public string Code {get; set;}
public string Description {get; get;}
}
An Order have multiple Status and one Status have only one StatusType
My questions:
1) How to select in a LINQ query a list containg all the orders with the loaded StatusType
I try with
var results= (from o in db.Orders
select new {o.Id,o.Quantity,o.StatusChanges})
.Include(o=>o.StatusChanges.Select(s=>s.StatusType));
But that's not that I want, I want a list which contains, Order, Status and StatusType (even if the Order info is repetead) I know I can make a double foreach but I would like to know if is a single query way (or a double query one to the db and another using linq to entities)
2) How make a list which contains the Order Id and the Codes of all the status concatenated. Example
Orders:
Id Quantity
1 2
Status
Id StatusDate StatusTypeId
1 01-10-2014 1
2 02-10-2014 2
StatusType
Id Code Description
1 E Entered
2 C Confirmed
3 D Deleted
Result:
OrderId Quantity StatusResume
1 2 E,C
I know I can use this question but I don't know how have access to the loaded elements in the list.

The first one from Ilija Dimov's answer should work. The second one is a little bit more tricky, because Aggregate extension method is not supported by LINQ to Entities. That's why you have to download necessary data to memory and perform string concatenation in-memory as LINQ to Objects query.
var results = (from o in db.Orders
select new
{
o.Id,
o.Quantity,
o.StatusCodes.Select(c => c.StatusType.Code)
}).AsEnumerable()
.Select(x => new {
x.Id,
x.Quantity,
StatusResume = string.Join(',', x.StatusCodes)
}).ToList();
}).ToList();

Try the following queries:
1) How to select in a LINQ query a list containg all the orders with the loaded StatusType
var results = (from o in db.Orders
from s in o.StatusChanges
select new
{
OrderId = o.Id,
Quantity = o.Quantity,
StatusId = s.Id,
StatusDate = s.StatusDate,
StatusCode = s.StatusType != null ? s.StatusType.Code : null
}).ToList();
2) How make a list which contains the Order Id and the Codes of all the status concatenated
var results = (from o in db.Orders
select new
{
o.Id,
o.Quantity,
StatusCodes =
o.StatusChanges.Where(c => c.StatusType != null).Select(c => c.StatusType.Code)
}).ToList().Select(x => new
{
x.Id,
x.Quantity,
StatusResume = string.Join(",", x.StatusCodes)
}).ToList();

Related

EF6 Linq Query - How to make a query when two Foreign Key Constraints are in place

For the most part EF seems to handle itself quite well, using the following query in linq, I am able to get all the related table data using FK's without having to specify the one to many relationship.
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subpat in AP.DefaultIfEmpty()
orderby s.ClinicalAssetID descending
select new ClinicalASSPATVM
{
ClinicalAssetID = s.ClinicalAssetID,
ProductName = s.ProductName,
ModelName = s.ModelName,
SupplierName = s.SupplierName,
ManufacturerName = s.ManufacturerName,
SerialNo = s.SerialNo,
PurchaseDate = s.PurchaseDate,
PoNo = s.PoNo,
Costing = s.Costing,
TeamName = s.TeamName,
StaffName = s.StaffName,
InspectionDocumnets = subpat.InspectionDocumnets ?? String.Empty,
InspectionOutcomeResult = subpat.InspectionOutcomeResult
});
the above code pulls in the relationship data from the ViewModel.
public Product ProductName { get; set; }
public InspectionOutcome InspectionOutcomeResult { get; set; }
public Model ModelName { get; set; }
public BudgetCode Code { get; set; }
public AssetType AssetTypeName { get; set; }
public Manufacturer ManufacturerName { get; set; }
public Staff StaffName { get; set; }
public Team TeamName { get; set; }
public Supplier SupplierName { get; set; }
I have a new problem which I have created for myself. I wanted to add an ID Field to the Models entity this helps me filter the data in a drop down list. I called the Field Name: ModelAssetAsignmentID And when someone adds a new ModelName from the Clinical Controller the ModelAssetAsignmentID gets a value of two.
So when i added the Field ModelAssetAsignmentID to the model "Models" i created a second FK as such:
My Original Linq Query is now broken, it no longer displays the modelname. I'm guessing this is due to the two FK Constraints.
Making the following change did not work, the InnerException is null.
var ClinicalASSPATVM = (from s in db.ClinicalAssets
where (s.ModelAssetAssignmentID.Equals(2))
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subpat in AP.DefaultIfEmpty()
orderby s.ClinicalAssetID descending
The solution is to remove the second foreign key constraint ModelAssetAssignmentID and use a viewmodel to create a value in the ModelAssetAssignmentID, thus you then do not need to modify the linq query's.

mvc3 best way to convert anonymous type to an ienumerable

I have a model that uses an IEnumerable to return multiple rows using a foreach statement in the view. The issue is that I am using a Join because I need data from 2 different tables which turns the ienumerable into an anonymous type and is causing errors; whats the best way I can fix this?
var Ieposts = (from t in db.Threadposts join p in db.profiles on t.profileID equals p.profileID where t.threadID == id select new
{
firstname = p.firstname,
lastname = p.lastname,
articles = p.articlecount,
city = p.city,
state = p.state,
post = t.post
}).ToList();
I am only getting out the fields that I need because that increases database performance. Any suggestions would be great
I think you really need to learn a bit more about Entity Framework and Linq in general.
Joins do not create anonymous types, nor is there any problem with getting an enumerable from an anonymous type. Just call .AsEnumerable() from the query.
However, I suspect this is not what you're trying to do. What you actually want is a concrete return type. In That case, you probably want to create a type for this:
public class IEPost {
public string FirstName {get;set;}
public string LastName {get;set;}
public int Articles {get;set;}
public string City {get;set;}
public string State {get;set:}
public Post Post {get;set;}
}
Then you create your query like so (note the "new IEPost" part):
var Ieposts = (from t in db.Threadposts
join p in db.profiles on t.profileID equals p.profileID
where t.threadID == id select new IEPost
{
FirstName = p.firstname,
LastName = p.lastname,
Articles = p.articlecount,
City = p.city,
State = p.state,
Post = t.post
}).ToList();

Join LINQ queries from multiple contexts

I am trying to create a ViewModel for data that I want to display in my view. The issue is, the data being displayed is spread across 2 databases and multiple tables within each. I've read that you cannot join Linq queries across multiple contexts, which makes sense, and I've also read that we can't use Code-First in EF5 to use Stored Procedures....which led me to using 3 different Linq queries and attempt to combine them into 1 ViewModel...I'm just not sure how to get there.
Here's my Linq queries:
var csdContext = new CSDContext(CustomerCode);
var masterContext = new MasterContext();
//Only returns 1 row - which is what we want.
List<Site> sites = (from s in csdContext.Sites
join sa in csdContext.SiteAddresses
on s.SiteID equals sa.SiteID
join a in csdContext.Addresses
on sa.AddressID equals a.AddressID
join spv in csdContext.SiteProductVersions
on s.SiteID equals spv.ProductVersionID
where s.SiteID == id
select s).ToList();
//List
List<States> states = (from s in masterContext.StatesTable
select s).ToList();
My ViewModel looks like this:
public class SiteDetailsViewModel
{
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string StateCode { get; set; }
public string ZipCode { get; set; }
public string OfficePhone { get; set; }
public string MobilePhone { get; set; }
public string AlternativePhone { get; set; }
public int ProductVersionID { get; set; }
}
Basically, I need the following data from these tables:
csdContext - Address
Address
Address2
City
ZipCode
csdContext - Sites
OfficePhone
MobilePhone
AlternativePhone
csdContext - SiteProductVersions
ProductVersionID
masterContext - States
StateCode
Here's how the tables are joined in SQL:
SELECT csd_a.Address, csd_a.Address2, csd_a.City, mstr_st.StateCode, csd_a.ZipCode, csd_s.OfficePhone, csd_s.MobilePhone,
csd_s.AlternativePhone, csd_spv.ProductVersionID
FROM CSD.dbo.Sites AS csd_s
INNER JOIN CSD.dbo.SiteAddress AS csd_sa ON csd_sa.SiteID = csd_s.SiteID
INNER JOIN CSD.dbo.Address AS csd_a ON csd_a.AddressID = csd_sa.AddressID
INNER JOIN CSD.dbo.SiteProductVersions AS csd_spv ON csd_s.SiteID = csd_spv.SiteID
INNER JOIN MasterDB.dbo.States AS mstr_st ON mstr_st.StateID = csd_a.StateID
I can't figure out how to merge these 3 results to create the ViewModel data for SiteDetailsViewModel. Can anyone help?
If you materialize your queries as described in OP you can use this:
var query = sites.Join(
states,
si => si.StateID,
st => st.StateID,
(si, st) => new SiteDetailsViewModel
{
Address = si.Address,
Address2 = si.Address2,
City = si.City,
StateCode = st.StateCode,
ZipCode = si.ZipCode,
OfficePhone = si.OfficePhone,
MobilePhone = si.MobilePhone,
AlternativePhone = si.AlternativePhone,
ProductVersionID = siProductVersionID
});
One possible alternate solution is to create views in DB #2 of the tables in DB #1. Then you can model/map those views in EF for DB #2. You would of course still need a context for DB #1 if you need to update any of the tables. But the benefit of this solution is that you can do all of your read access on a single context and get joins at the server instead of in memory.
So why don't you write it like this:
from ...
...
where s.SiteID == id
select new SiteDetailsViewModel()
{
Address = sa.Address,
Address2 = as.Address2,
..
}

LINQ query returning a List<> as a class member

Given the follow data class,
public class EmployeeMenu
{
public int ID { get; set; }
public string HeaderName { get; set; }
public List<string> ItemNames { get; set; }
}
how can I get a sub-query into the ItemNames field?
My current query of
IQueryable<EmployeeMenu> retValue =
from mh in menuHeaders
select new EmployeeMenu
{
ID = mh.ID,
HeaderName = mh.HeaderName,
ItemNames = (from mhi in mh.MenuItems
select mhi.MenuItemName).ToList<string>()
};
doesn't seem to be doing the trick...
The data structure is
MenuHeaders MenuItems
----------- ---------
ID ID
HeaderName <-(FK)--MenuHeaderID
MenuItemName
I ended up just changing from a List to IEnumerable. This fixed it.
Wouldnt you want to just put a where in your sub-select to filter that down to all the menu items with the MenuHeaderID equals mh.HeaderName. You can just .Equals() with the StringComparison type if you want as well.
Here is an example...
IQueryable<EmployeeMenu> retValue =
from mh in menuHeaders
select new EmployeeMenu
{
ID = mh.ID,
HeaderName = mh.HeaderName,
ItemNames = (from mhi in mh.MenuItems
select mhi.MenuItemName where mhi.MenuHeaderID = mh.HeaderName).ToList<string>()
};
My guess is that your not initiliazing the list within your class. I basing this off the experience I was having with Nhibernate.
public class EmployeeMenu
{
public int ID { get; set; }
public string HeaderName { get; set; }
public List<string> ItemNames { get; set; }
public EmployeeMenu()
{
ItemNames=new List<string>();
}
}
Hope this helps.
Okay. Try replacing
(from mhi in mh.MenuItems
select mhi.MenuItemName).ToList<string>()
by
mh.MenuItems
.AsEnumerable()
.Select(mhi => mhi.MenuItemName)
.ToList()
I question if you want a where clause in there somewhere, but this should get you past the runtime exception.
Any time you see an error message of the form "LINQ to Entities does recognize the method ... and this method can not be translated into a store expression" LINQ to Entities is telling you that it can't figure out how to translate part of the expression tree into a SQL statement. This means you need to pull things client side so that LINQ to Entities doesn't try to translate something that it can't translate.

Linq2Sql relationships and WCF serialization problem

here is my scenario
i got
Table1
id
name
Table2
id
family
fid
with one to many relationship set between Table1. id and Table2.fid
now here is my WCF service Code
[OperationContract]
public List<Table1> GetCustomers(string numberToFetch)
{
using (DataClassesDataContext context = new DataClassesDataContext())
{
return context.Table1s.Take(int.Parse(numberToFetch)).ToList( );
}
}
and my ASPX page Code
<body xmlns:sys="javascript:Sys"
xmlns:dataview="javascript:Sys.UI.DataView">
<div id="CustomerView"
class="sys-template"
sys:attach="dataview"
dataview:autofetch="true"
dataview:dataprovider="Service2.svc"
dataview:fetchParameters="{{ {numberToFetch: 2} }}"
dataview:fetchoperation="GetCustomers">
<ul>
<li>{{family}}</li>
</ul>
</div>
though i set serialization mode to Unidirectional in Linq2Sql designer i am not able to get the family value and all what i get is this in firebug
{"d":[{"__type":"Table1:#","id":1,"name":"asd"},{"__type":"Table1:#","id":2,"name":"wewe"}]}
any help would be totally appreciated
Well, the point is: your method GetCustomers only ever selects from Table1 - I don't see any reference at all to Table2, where your Family column is located......
You need to check into Linq-to-SQL JOIN's and how to fetch data from a joined table into your result set.
Something along the lines of:
[DataContract]
class JoinedResult
{
[DataMember]
public int Table1ID { get; set; }
[DataMember]
public string Table1Name { get; set; }
[DataMember]
public string Table2Family { get; set; }
}
[OperationContract]
public List<JoinedResult> GetCustomers(int numberToFetch)
{
using (DataClassesDataContext context = new DataClassesDataContext())
{
var q = from t1 in context.Table1
join t2 in context.Table2 on t1.id = t2.fid
select new JoinedResult
{ Table1ID = t1.ID,
Table1Name = t1.Name,
Table2Family = t2.Family };
return q.Take(numberToFetch).ToList();
}
}
and btw: you should really make numberToFetch an INT parameter! Let the caller make the conversion......
UPDATE: if you don't want to explicitly include the second table to be queried, you could instead add a DataLoadOption to your context:
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Table1>(t => t.Table2);
context.LoadOptions = dlo;
In that case, you tell LINQ to always include all elements from Table2 when it loads anything from Table1 - that should work, too.

Resources