Linq and Query's syntax is one of my weakest skills. I'm having issues with getting the desired result.
I have two tables/ collections. One is filled with DocumentTypes, the other with Notifications. These are the fields they hold that matter, I omitted the ones that don't.
DocumentTypes
ID
Name
SupplierID
Notifications
ID
DocumentTypeID
UserID
ProductID
Last_Sequence
I have three parameters; userID, supplierID and productID.
I need the supplierID to get a list of all the DocumentTypes tied to that supplier. Then I need the userID and ProductID to get a list of notifications tied to those.
Then I need to join those two lists, every notification will have a documentTypeID to which it is linked. When there is a notification for a certain document type, it needs to include the fields Last_Sequence and make a new bool field that is set to true.
When there is no notification, the Last_sequence can be left empty and the bool is created and set to false.
So the result would be a list with objects that have these types.
DocumentTypeID
DocumentTypeName
BoolField (true of there is a notification tied to this one)
NotificationID (only if there is one)
Last_sequence (only if there is one)
What I have so far.
The previous version only needed the bool field added, along with the documentType information. For that I had this statement, but I can't seem to add to that what I need:
List<DocumentTypeNotification> docTypes = repository.Get<Domain.DocumentType>().Where(d => d.SuppID == SuppId).Select(d => new DocumentTypeNotification
{
DocTypeID = d.Id,
DocTypeName = d.Name,
Subscribed = notifications.Any(n => n == d.Id)
}).ToList();
What I have tried for the new one is this, but it only gives data back, when there is a notification tied. When there isn't one, it doesn't give back that documentType data.
var temptest = from notif in repository.Get<Domain.Notification>()
join doctype in repository.Get<Domain.DocumentType>() on notif.DocTypeId equals doctype.Id
select new DocumentTypeNotification { DocTypeID = doctype.Id, DocTypeName = doctype.Name, Subscribed = true, NotifID = notif.Id, last_sequence = notif.Last_Sequence};
EDIT: here is an example of something I tried, but does not work. The issue here is that n does not exist when I try to do n.last_sequence.
List<DocumentTypeNotification> docTypes = repository.Get<Domain.DocumentType>().Where(d => d.SuppID == SuppId).Select(d => new DocumentTypeNotification
{
DocTypeID = d.Id,
DocTypeName = d.Name,
Subscribed = notifications.Any(n => n == d.Id),
last_sequence = test.Where(n => n.DocTypeId == d.Id).Select(n.Last_Sequence).FirstOrDefault()
//from n in test
//where n.DocTypeId == d.Id
//select n.Last_Sequence
}).ToList();
I was wondering how I should solve this. Do I need to make a collection of all the right DocumentTypes first, and then join that with the new collection that I made, or is there a better way to solve this?
What about left join
from d in repository.Get<Domain.DocumentType>()
join n in repository.Get<Domain.Notification>()
on d.Id equals n.DocTypeId
into temp
from notific in temp.DefaultIfEmpty()
where d.SuppID == SuppId
select new
{
d.Name,
last_sequence = notific != null ? notific.Last_Sequence : null,
Subscribed = notific != null
}
There is no concrete code samples, so at first I will define some variables.
Let's suppose that we have list of document types with name documentTypes and list of notifications with name notifications. If I am understanding your problem right, than the following linq query will do what you want
documentTypes.Where(d => d.SupplierID == SuppId)
.GroupJoin(notifications,
d => d.ID,
n => n.DocumentTypeID,
(document, notification) => new {document, notification}
)
.Select(join =>
{
var anyNotification = join.notification.FirstOrDefault();
return new
{
DocTypeID = join.document.ID,
DocTypeName = join.document.Name,
Subscribed = join.notification.Any(),
NotificationID = anyNotification != null ? anyNotification.ID : null,
Last_sequence = anyNotification != null ? anyNotification.Last_Sequence: null,
};
}).ToList();
Related
This question already has answers here:
Sequence contains no elements?
(7 answers)
Closed 10 months ago.
I try to get some data from few table with joining. This query is give an error as System.InvalidOperationException , Message=" Sequence contains no elements ". How can I avoid this error.
var assignments = (from s in _db.SubmissionLinks
join a in _db.Assignments on s.AssignmentID equals a.AssignmentID
join p in _db.Projects on a.ProjectID equals p.ID
join s2 in _db.SystemUsers on p.SystemUserFK equals s2.ID
select new AssignmentViewModel()
{
SubmissionlinkID = s.SubmissionLinkID,
SubmissionlinkName = s.SubmissionName,
ProjectID = p.ID,
ProjectName = p.ProjectName,
Deadline = s.Deadline,
Userid = s2.ID,
Assignmentid = a.AssignmentID,
IsActive = s.ActiveStatus
}).Where(s =>s.Userid == ID && s.IsActive == 0).Distinct().First();
I try to avoid that by checking returning objects inside of the where clause but it didn't work
var assignments = (from s in _db.SubmissionLinks
join a in _db.Assignments on s.AssignmentID equals a.AssignmentID
join p in _db.Projects on a.ProjectID equals p.ID
join s2 in _db.SystemUsers on p.SystemUserFK equals s2.ID
select new AssignmentViewModel()
{
SubmissionlinkID = s.SubmissionLinkID,
SubmissionlinkName = s.SubmissionName,
ProjectID = p.ID,
ProjectName = p.ProjectName,
Deadline = s.Deadline,
Userid = s2.ID,
Assignmentid = a.AssignmentID,
IsActive = s.ActiveStatus
}).Where(s => s != null && s.Userid == ID && s.IsActive == 0).Distinct().First();
Agree with Panagiotis; its supposed to be that SubmissionLink has a .Assignment property that returns the parent Assignment, Assignment has a .Project prop that returns the Project etc, and then you just do:
_db.SubmissionLinks
.Where(s =>s.Userid == ID && s.IsActive == 0)
.Select(s => s.Select(s => new AssignmentViewModel()
{
SubmissionlinkID = s.SubmissionLinkID,
SubmissionlinkName = s.SubmissionName,
ProjectID = s.Assignment.Project.ID,
ProjectName = s.Assignment.Project.ProjectName,
Deadline = s.Deadline,
Userid = s.Assignment.Project.SystemUser.ID,
Assignmentid = s.Assignment.AssignmentID,
IsActive = s.ActiveStatus
})
.SomethingThatRunsTheQueryLike_First_FirstOrDefault_ToList_etc
EF works out the rest from how it seens you use the navigations (s.Assignment.Project.ProjectName etc). Starting from the many end and going up to the one end is quite easy, because it's just a single object at the end of the nav. Going down through collections is a bit more of a nuisance, so think carefully about where you start your query from for the easiest life. For example if you wanted the Project and its most recent submission, you might:
db.Projects.Select(p => new Something{
p.ProjectName,
MostRecentLinkId = p.SubmissionLinks.OrderByDescending(sl => sl.CreatedDate).First().Id
})
That p.SubmissionLinks.OrderByDescending(sl => sl.CreatedDate).First().Id would be translated (by recent EFC) to something like ... ROW_NUMBER() OVER(PARTITION BY projectid ORDER BY createddate DESC) rn ... WHERE rn=1 in a subquery, but working with collections is usually that little more tricky because you're performing aggregations to make them make sense in the context of a single parent. If you can start from a different place so youre not digging into collections it's simpler
I have created two tables: Claim and ClaimAttachments.
I'm trying to join them on ClaimID in order to get the filtered data from both the tables.
public ActionResult Index(int? search)
{
if (search!=null)
{
var Product = (from P in db.Claims
join C in db.ClaimAttachments on
P.ClaimID equals C.ClaimID
select new Claim
{
ClaimID = P.ClaimID,
ClaimBatchID = P.ClaimBatchID,
PatientControlNumber = P.PatientControlNumber,
PatientFirstName = P.PatientFirstName,
PatientLastName = P.PatientLastName,
ServiceFromDate = P.ServiceFromDate,
ServiceToDate = P.ServiceToDate,
});
return View(db.Claims.Where(x => x.ClaimID == search).ToList());
}
else
{
return View(db.Claims.ToList());
}
I'm able to get the searched result but from single table. The join is not working.
Currently you're only selecting from the Claims data:
return View(db.Claims.Where(x => x.ClaimID == search).ToList());
You have a join query just above that line of code:
var Product = (from P in db.Claims
join C in db.ClaimAttachments on
P.ClaimID equals C.ClaimID
select new Claim
{
ClaimID = P.ClaimID,
ClaimBatchID = P.ClaimBatchID,
PatientControlNumber = P.PatientControlNumber,
PatientFirstName = P.PatientFirstName,
PatientLastName = P.PatientLastName,
ServiceFromDate = P.ServiceFromDate,
ServiceToDate = P.ServiceToDate
});
But you don't do anything with the results of that query. It sounds like you meant to use the results of that query (which is in the Product variable, which incidentally should probably have a plural name since it's a collection) instead of just selecting from db.Claims. Something like this:
return View(Product.Where(x => x.ClaimID == search).ToList());
Note however that you're still only selecting data from one table. Though the join operation may alter the results of that selection. But the selection itself is here:
select new Claim
{
ClaimID = P.ClaimID,
ClaimBatchID = P.ClaimBatchID,
PatientControlNumber = P.PatientControlNumber,
PatientFirstName = P.PatientFirstName,
PatientLastName = P.PatientLastName,
ServiceFromDate = P.ServiceFromDate,
ServiceToDate = P.ServiceToDate
}
Notice how every value selected is from the P alias, which is defined here:
from P in db.Claims
So you're successfully joining the two tables, but only selecting data from one of the two tables. If you want to also select data from the other table then, well, you need to select data from the other table. For example, if there's a property on that table called SomeProperty that you want to select then you'd need to select it, and into an object which has that property.
For example, you might create a view model (let's call it ClaimViewModel as an example) which represents a combined record of the two tables, containing the properties you want from each. Then you'd select into that type:
select new ClaimViewModel
{
ClaimID = P.ClaimID,
ClaimBatchID = P.ClaimBatchID,
PatientControlNumber = P.PatientControlNumber,
PatientFirstName = P.PatientFirstName,
PatientLastName = P.PatientLastName,
ServiceFromDate = P.ServiceFromDate,
ServiceToDate = P.ServiceToDate,
SomeProperty = C.SomeProperty // <--- here
}
This would select the combined data into a list of ClaimViewModel objects, which you'd then filter based on your "search" and return to your view just like you do with the Claims objects now. And of course that view would need to be updated to expect a collection of ClaimViewModel objects instead of a collection of Claim objects.
I have a list that are generated from Linq to SQL. For simplicity it looks like this:
var myItems = (from my in db.MyTable
select new
{
UniqueID = my.ID,
UserName = my.UserName,
CreatedOn = my.CreatedOn
}).ToList();
This list contains 4 items.
And I have another:
var grid = (from q in AnotherLinqQuery
select new
{
UniqueID = q.ID,
Department = q.Department,
Comments = q.Comments
}).ToList();
This list contains 20 items.
All the ID's in myItems appear in grid.
Now I want to join it up with a left join.
var q = from A in grid
from B in myItems.Where(x => x.UniqueID == grid.UniqueID).DefaultIfEmpty()
select new
{
UniqueID = A.UniqueID,
Department = A.Department,
CreatedOn = B.CreatedOn
}
When I execute this, I get
Object reference not set to an instance of an object.
I've also tried other joins such as
from A in grid
from B in myItems.Where(x => x.UniqueID != null && x.UniqueID == grid.UniqueID).DefaultIfEmpty()
You are not joining correctly. Try this:
var q = from A in grid
join B in myItems on A.UniqueId equals B.UniqueId into LB
from B in LB.DefaultIfEmpty()
select new
{
UniqueID = A.UniqueID,
Department = A.Department,
CreatedOn = B.CreatedOn
};
You may want to refer to the documentation for further info on joining in linq.
Since you are doing a left join instead of an inner join, there will be no items from myItems for the 16 elements that are only in grid but not in myItems.
B will be null in that cases (as DefaultIfEmpty() creates a sequence with one null element), so you have to check for null here:
var q = from A in grid
from B in myItems.Where(x => x.UniqueID == grid.UniqueID).DefaultIfEmpty()
select new
{
UniqueID = A.UniqueID,
Department = A.Department,
CreatedOn = B?.CreatedOn ?? DateTime.MinValue // or whatever default value you like
}
I writing a email system where we have a table of users "tblUsers" and a table of messages. A user can have many messages (from other users in tblusers) in his or her inbox (one:many).
In tblUsers table, I have a column called ImageURL (string) that contains the URL to the user's avatar. In this case, I'm looping through the messages in an inbox belonging to a user and what I'm trying to do is, once I get the message, walk up the tree to the tblUser and get the value in the ImageURL column for the owner of that message as marked "SenderAvatar" below.
Here's what I tried. The problem is that the sub linq for SenderAvatar below is throwing a nullpointer exception even though I have confirmed that there is a value for ImageURL (this is dev so there's only three users). Somehow my logic and linq's logic is at odds here. Can someone please help? Thanks!
Edit
I found two bugs. The first bug is Dzienny pointed me to the right direction where I was comparing apples and oranges. The second bug is FromUserId = ux.tblUserId, where I'm setting the current user id to FromUserId Guys, thank you for all your help on this.
public List<UserInboxMsg> GetUserInboxMsg(IKASLWSEntities conx, int userid)
{
var u = (from m in conx.tblUsers where m.Id == userid select m).FirstOrDefault();
if (u != null)
{
return (from ux in u.tblInboxes
orderby ux.CreationTS descending
select new UserInboxMsg
{
CreationTS = ux.CreationTS,
ExpirationDate = ux.ExpirationDate,
FromUserId = ux.tblUserId,
HasImage = ux.HasImage,
ImageId = ux.ImageId ?? 0,
IsDeleted = ux.IsDeleted,
IsRead = ux.IsRead,
MsgId = ux.Id,
MsgSize = ux.MessageSize,
ParentId = ux.ParentId,
Title = ux.Title,
ToUserId = userid,
FromUserName = ux.Title,
SenderAvatar = conx.tblMessages.Where(mu=>mu.Id == ux.Id).FirstOrDefault().tblUser.ImageURL,
Message = ux.Message
}).ToList<UserInboxMsg>();
}
else
{
return new List<UserInboxMsg>();
}
}
}
If in the entity-framework, there is a foreign key reference between the two tables you could probably do this:
SenderAvatar = conx.tblMessages.FirstOrDefault( mu=>mu.Id == ux.Id).ImageURL,
Try this.
public List<UserInboxMsg> GetUserInboxMsg(IKASLWSEntities conx, int userid)
{
var u = (from m in conx.tblUsers where m.Id == userid select m).FirstOrDefault();
if (u != null && conx != null)
{
return (from ux in u.tblInboxes
orderby ux.CreationTS descending
select new UserInboxMsg
{
...
...
SenderAvatar = conx.tblMessages.Any(mu=>mu.Id == ux.Id) ? (conx.tblMessages.First(mu=>mu.Id == ux.Id).tblUser != null? conx.tblMessages.First(mu=>mu.Id == ux.Id).tblUser.ImageURL : null) : null,
Message = ux.Message
}).ToList<UserInboxMsg>();
}
else
{
return new List<UserInboxMsg>();
}
}
}
if you are getting null for the Avatar, it is either because there are no entries in tblMessages where mu.Id equals ux.Id or the tblMessage entry is there but the tblUser property is null
There are several problems here.
The first is that the second statement is executed in memory, while it's possible to make the whole query run as SQL:
from u in conx.tblUsers where m.Id == userid
from ux in u.tblInboxes
orderby ux.CreationTS descending
select new UserInboxMsg
{
CreationTS = ux.CreationTS,
ExpirationDate = ux.ExpirationDate,
FromUserId = ux.tblUserId,
HasImage = ux.HasImage,
ImageId = ux.ImageId ?? 0,
IsDeleted = ux.IsDeleted,
IsRead = ux.IsRead,
MsgId = ux.Id,
MsgSize = ux.MessageSize,
ParentId = ux.ParentId,
Title = ux.Title,
ToUserId = userid,
FromUserName = ux.Title,
SenderAvatar = conx.tblMessages.Where(mu => mu.Id == ux.Id)
.FirstOrDefault().tblUser.ImageURL,
Message = ux.Message
}
This has three benefits:
you fetch less data from the database
you get rid of the null reference exception, because SQL doesn't have null references. It just returns null if a record isn't found.
you can return the result of this statement without the if-else.
Second, less important, is that you should use a navigation property like Inbox.Messages in stead of joining (sort of) the inbox and its messages. This makes it less likely that you use the wrong join variables and it condenses your code:
SenderAvatar = ux.Messages.
.FirstOrDefault().User.ImageURL,
Now if there is no avatar, there is no avatar. And there's no null reference exception.
(By the way, you can see that I hate these prefixes in class and property names).
I can only guess this part of your code is wrong : SenderAvatar = conx.tblMessages.Where(mu=>mu.Id == ux.Id).FirstOrDefault().tblUser.ImageURL
I think for example you should use (mu=>mu.UserId == ux.Id) instead of (mu=>mu.Id == ux.Id). In your code, you are comparing "Id" of a table to "Id" of another table which normally in one to many relations is wrong. (only works in one to one relations)
I said I can guess because you didn't mention any information about tblInboxes and tblMessages fields. If you could provide me more information about their structure, I could answer in more detail.
By the way to make your code more clear you can use:
var u = conx.tblUsers.FirstOrDefault(m=>m.Id == userid);
instead of
var u = (from m in conx.tblUsers where m.Id == userid select m).FirstOrDefault();
OR
conx.tblMessages.FirstOrDefault(mu=>mu.Id == ux.Id)
instead of
conx.tblMessages.Where(mu=>mu.Id == ux.Id).FirstOrDefault()
How do you suppose I tackle this? Basically, I have this inital query:
var orders = (from order in _dbContext.Orders
join orderDetail in _dbContext.OrderDetails on order.ID equals orderDetail.OrderID
where order.StoreID == storeID
select new Order
{
ID = order.ID,
No = order.ID,
Type = "", // Notice that this is empty; this one needs updating
Quantity = order.Quantity,
// more properties here
}).AsQueryable();
After this query, I need to loop through the result and update the Type property based on different criteria like this:
string type = "";
foreach (OrderDetailDto order in orders)
{
if (order.UserID != null)
type = "UserOrder";
else if (order.UserID == null)
type = "NonUserOrder";
else if (order.Cook == null && (order.Option == "fiery"))
type = "FieryCook";
else if (check if this has corresponding records in another table) // this part I don't know how to effectively tackle
type = "XXX";
// Update.
order.Type = type;
}
The problem is one of my criteria needs me to check if there are existing record in the database. I would use JOIN but if I have to loop thru several hundreds or thousands of records and then JOIN each one of them then check on db just to get one value, I think that would be very slow.
I can't do the JOIN on the initial query because I might do a different JOIN based on a different criterion. Any ideas?
You could just join all the lookup tables you might possibly need in left join type way:
from o in Orders
from c in Cooks.Where(x => x.OrderId == m.OrderId).DefaultIfEmpty()
from u in Users.Where(x => x.OrderId == o.OrderId).DefaultIfEmpty()
select new
{
Order = m,
Cook = c,
User = u
}
or depending on your usage patterns you could build the required tables into local Lookups or Dictionaries for linear time searching thereafter:
var userDict = Users.ToDictionary(x => x.UserId);
var userIdDict = Users.Select(x => x.UserId).ToDictionary(x => x);
var cooksLookup = Cooks.ToLookup(x => x.Salary);