LINQ query JOIN two tables for Web API controller method - linq

I am really poor at LINQ and can't figure out a simple problem. I have a MVC Web API that has a controller. I have a method inside the controller to return back data for comments entered by user for an item.
The data structure is simple - Comments and User tables with UserID column acting as the foreign key
To solve the problem, I have the following method, which has a LINQ query to do a join between Comments and User tables and return back an object in a new extended object that combines the Comments and User details. I cant seem to grab data from the User table. Can someone please help?
public IQueryable<CommentsWithUserDetails> GetReviewsWithUserByItem(int ID)
{
var query = from x in db.Comments
join y in db.Users on x.CommentsUserID equals y.UserID into z
where x.CommentsItemID.Equals(ID)
select new CommentsWithUserDetails
{
CommentsUserID = x.CommentsUserID,
CommentsText = x.CommentsText,
CommentsRating = x.CommentsRating,
CommentsDate = x.CommentsDate,
UserFirstName = y.FirstName,
UserLastName = y.LastName,
UserPictureURL = y.PictureURL
};
return query;
}

The solution is just to remove the 'into z' part from the query, yes as simple as that!
As pointed by #Nilesh and #Gert Arnold

Related

Dynamic Linq on DataTable error: no Field or Property in DataRow, c#

I have some errors using Linq on DataTable and I couldn't figure it out how to solve it. I have to admit that i am pretty new to Linq and I searched the forum and Internet and couldn't figure it out. hope you can help.
I have a DataTable called campaign with three columns: ID (int), Product (string), Channel (string). The DataTable is already filled with data. I am trying to select a subset of the campaign records which satisfied the conditions selected by the end user. For example, the user want to list only if the Product is either 'EWH' or 'HEC'. The selection criteria is dynaically determined by the end user.
I have the following C# code:
private void btnClick()
{
IEnumerable<DataRow> query =
from zz in campaign.AsEnumerable()
orderby zz.Field<string>("ID")
select zz;
string whereClause = "zz.Field<string>(\"Product\") in ('EWH','HEC')";
query = query.Where(whereClause);
DataTable sublist = query.CopyToDataTable<DataRow>();
}
But it gives me an error on line: query = query.Where(whereClause), saying
No property or field 'zz' exists in type 'DataRow'".
If I changed to:
string whereClause = "Product in ('EWH','HEC')"; it will say:
No property or field 'Product' exists in type 'DataRow'
Can anyone help me on how to solve this problem? I feel it could be a pretty simple syntax change, but I just don't know at this time.
First, this line has an error
orderby zz.Field<string>("ID")
because as you said, your ID column is of type int.
Second, you need to learn LINQ query syntax. Forget about strings, the same way you used from, orderby, select in the query, you can also use where and many other operators. Also you'll need to learn the equivalent LINQ constructs for SQL-ish things, like for instance IN (...) is mapped to Enumerable.Contains etc.
With all that being said, here is your query
var productFilter = new[] { "EWH", "HEC" };
var query =
from zz in campaign.AsEnumerable()
where productFilter.Contains(zz.Field<string>("Product"))
orderby zz.Field<int>("ID")
select zz;
Update As per your comment, if you want to make this dynamic, then you need to switch to lambda syntax. Multiple and criteria can be composed by chaining multiple Where clauses like this
List<string> productFilter = ...; // coming from outside
List<string> channelFilter = ...; // coming from outside
var query = campaign.AsEnumerable();
// Apply filters if needed
if (productFilter != null && productFilter.Count > 0)
query = query.Where(zz => productFilter.Contains(zz.Field<string>("Product")));
if (channelFilter != null && channelFilter.Count > 0)
query = query.Where(zz => channelFilter.Contains(zz.Field<string>("Channel")));
// Once finished with filtering, do the ordering
query = query.OrderBy(zz => zz.Field<int>("ID"));

Linq and Lambda expression for a complex sql query involving joins

Using Linq to Entity (Entity Framework) in MVC 3 project.
My model:
Table - Users
UserID (PK)
...
Table - Clients
ClientID (PK)
Table - PropertyItems
PropertyItemID (PK)
Table - MemberContactPreference (Contains PropertyItems selected by Users- many to many)
UserID(FK)
PropertyItemID(FK)
Table ClientProperties (Contains PropertyItems that belong to Clients - many to many)
ClientID (FK)
PropertyItemID (FK)
I want to list all the distinct users that have selected all the properties selected by clients.
My Approach :
I got a list of all properties for a particular client in
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> UsersMatchingClientProperties = GetAllUsers();
foreach (ClientProperty property in clientproperties)
{
UsersMatchingClientProperties = (from uem in UsersMatchingClientProperties
join ucp in GetAllMemberContactPreferences on
ucp.UserID == uem.UserID
where uem.MemberContactPreferences.SelectMany(
mcp => mcp.PropertyItemID == property.PropertyItemID)
select uem).Distinct;
}
It gives the right result only first time. As it doesn't reduce the number of items in UsersMatchingClientProperties with each iteration. actually it replaces the collection with new resultset. I want to filter out this collection with each iteration.
Also, any suggestions to do this in Lambda expression without using Linq.
Thanks
That generation of an iqueryable in a for loop seems like a dangerous thing, which could end up in a monster sql join being executed at once.
Anyway, I don't think you need that. How about something like this?
// for a given client, find all users
// that selected ALL properties this client also selected
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> allUsers= GetAllUsers();
Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();
Iqueryable<User> UsersMatchingClientProperties = allUsers
.Where(user => allMemberContactProperties
.Where(membP => membP.UserID==user.UserID)
.All(membP => clientProperties
.Select(clientP => clientP.PropertyID)
.Contains(membP.PropertyID)
)
);
Here is an alternative query in case you want the users that selected ANY property for a given client
// for a given client, find all users
// that selected ANY properties this client also selected
Iqueryable<ClientProperty> clientProperties = GetClientProperties(ClientID)
Iqueryable<User> allUsers= GetAllUsers();
Iqueryable<MemberContactPreference> allMemberContactProperties = GetAllMemberContactPreferences();
Iqueryable<User> UsersMatchingClientProperties = clientproperties
.Join(allMembersContactProperties, // join clientproperties with memberproperties
clientP => clientP.PropertyItemID,
membP => membP.PropertyItemID,
(clientP, membP) => membP)) // after the join, ignore the clientproperties, keeping only memberproperties
.Distinct() // distinct is optional here. but perhaps faster with it?
.Join(allUsers, //join memberproperties with users
membP => membP.UserID,
user => user.UserID,
(membP, user) => user)) // after the join, ignore the member properties, keeping only users
.Distinct();
I trust Hugo did a good job suggesting ways to improve your query (+1). But that does not yet explain the cause of your problem, which is the modified closure pitfall.
I think that after your loop there is some code that actually executes the query in UsersMatchingClientProperties. At that moment the query is executed with the last value of the loop variable property! (The loop variable is the closure in each query delegate that is created in an iteration, and it is modified by each iteration).
Change the loop like this:
foreach (ClientProperty property in clientproperties)
{
var property1 = property;
...
and use property1 in the query. That should solve the cause of the problem. But as said, it looks like the whole process can be improved.

LINQ Select with multiple tables fields being writeable

I'm new to LINQ and I'm doing pretty well until now, but now stuck with this.
I've a LINQ object bounded to a DataGridView to let the user edit is contains.
for simple one table query, it go fine, but how to build a LINQ query with multiple table, so the result will still be read/write?
Here a example of what I mean:
GMR.Data.GMR_Entities GMR = new GMR.Data.GMR_Entities();
var dt = from Msg in GMR.tblMessages
join lang in GMR.tblDomVals on 1 equals 1//on Msg.pLangueID equals lang.ID
select Msg;
// select new {lang.DescrFr, Msg.Message,Msg.pLangueID } ;
this.dataGridView1.DataSource = dt;
In this simple query, if I return only "Msg" with the select statement, the grid can be edited. But if I replace the select statement with select new {lang.DescrFr, Msg.Message,Msg.pLangueID } ; the grid will be readable only.
I can easily understand that this is due because the query result is a anonymous type.
But is there a way to let the table tblMessage being writable?
try creating your own class, for example
public class MsgLangInfo
{
public string langDescFr{get;set;}
public int pLangueID{get;set;}
}
And at the select statement create an object of this class with new like below
select new MsgLangInfo {
langDescFr = lang.DescrFr,
langDescFr = Msg.Message,Msg.pLangueID
} ;
This way you can avoid the anonymous type problem.
You need to select the originals rows and explicitly set the grid columns.

Joining multiple one-to-many tables with Linq to SQL

Well I am trying to join 3 tables here is a brief summary of them
user - id,name,.....
contactdetails - id,detail,....,userId
adress - id,adress,.......contactdetailsId
how do I join these 3 tables with linq to sql?
Write something like (I can't read out the entire structure of the DB from your question):
var q = from a in ctx.address
select new {
a.address,
a.concactdetails.detail,
a.contactdetils.user.name
};
When having one-to-many relationships it's easiest to base the query on the table which "is most many". It is possible to do it the other way around and use LoadWith options. Unfortunately linq-to-sql only supports translating two tables into efficient querys when done that way. If you try it with three tables you will get a load of small fetch-one-line-queries hitting the DB dragging performance down terribly (see http://coding.abel.nu/2011/11/always-check-generated-sql/ for an example).
ContactDetail[] ContactDetails = new ContactDetail[0]; // your contact detail entries
Address[] Addresses = new Address[0]; // your address entries
User[] Users = new User[0]; // your user entries
Users.Join(ContactDetails, user => user.ID, cd => cd.ID, (user, cd) => new { User = user, ContactDetail = cd }).Join(Addresses, UserAndCD => UserAndCD.ContactDetail.ID, address=>address.ContactDetailID, (UserAndCD, address)=> new {User = UserAndCD.User, ContactDetail = UserAndCD.ContactDetail, Address = address});
In this case you will get user-contactdetail-address entries. If you want to get a user with contactdetail enumeration, and an address enumeration for each contactdetail, then you have to use GroupJoin:

ef and linq extension method

I have this sql that i want to have written in linq extension method returning an entity from my edm:
SELECT p.[Id],p.[Firstname],p.[Lastname],prt.[AddressId],prt.[Street],prt.[City]
FROM [Person] p
CROSS APPLY (
SELECT TOP(1) pa.[AddressId],a.[ValidFrom],a.[Street],a.[City]
FROM [Person_Addresses] pa
LEFT OUTER JOIN [Addresses] AS a
ON a.[Id] = pa.[AddressId]
WHERE p.[Id] = pa.[PersonId]
ORDER BY a.[ValidFrom] DESC ) prt
Also could this be re-written in linq extension method using 3 joins?
Assuming you have set the Person_Addresses table up as a pure relation table (i.e., with no data besides the foreign keys) this should do the trick:
var persons = model.People
.Select(p => new { p = p, a = p.Addresses.OrderByDescending(a=>a.ValidFrom).First() })
.Select(p => new { p.p.Id, p.p.Firstname, p.p.LastName, AddressId = p.a.Id, p.a.Street, p.a.City });
The first Select() orders the addresses and picks the latest one, and the second one returns an anonymous type with the properties specified in your query.
If you have more data in your relation table you're gonna have to use joins but this way you're free from them. In my opinion, this is more easy to read.
NOTE: You might get an exception if any entry in Persons have no addresses connected to them, although I haven't tried it out.

Resources