I have the scenario where I have a IList<Guid> in a variable called csv which are also in a specific order that I need to keep. I am then doing a select contains like so I can get back all my topics based in the list of guids I have.
The guids are from a lucene search which are ordered by the original score from each LuceneResult. Which is why I need to keep them in this order.
var results = _context.Topic
.Where(x => csv.Contains(x.Id));
However. I lose the order the guids came in as soon as I do this. Any idea how I can do this but keep the same order I hand the list of guids to the context and get the topics back in the same order based on the topid.Id?
I have tried the following as mentioned below, by doing a join but they still come out in the same order? Please note that I am paging these results too.
var results = _context.Topic
.Join(csv,
topic => topic.Id,
guidFromCsv => guidFromCsv,
(topic, guidFromCsv) => new { topic, guidFromCsv }
)
.Where(x => x.guidFromCsv == x.topic.Id)
.Skip((pageIndex - 1)*pageSize)
.Take(pageSize)
.Select(x=> x.topic);
** UPDATE **
So I have moved away from just using and guid and am attempting to pass in my lucene model which has the score property that I want to order by. Here is what I have
public PagedList<Topic> GetTopicsByLuceneResult(int pageIndex, int pageSize, int amountToTake, List<LuceneSearchModel> luceneResults)
{
var results = _context.Topic
.Join(luceneResults,
topic => topic.Id,
luceneResult => luceneResult.Id,
(topic, luceneResult) => new { topic, luceneResult }
)
.Where(x => x.luceneResult.Id == x.topic.Id)
.OrderByDescending(x => x.luceneResult.Score)
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.Select(x => x.topic);
var topicResults = results.ToList();
// Return a paged list
return new PagedList<Topic>(topicResults, pageIndex, pageSize, topicResults.Count);
}
However I am now getting the following error? Is what I am doing possible?
Unable to create a constant value of type 'LuceneSearchModel'. Only primitive types or enumeration types are supported in this context.
If I understand the question correctly, you want to filter the Topics based on the csv and you want to get back the results in the same order as the csv. If so:
var results = csv
.GroupJoin(_context.Topic, guid => guid, topic => topic.Id,
(guid, topics) => topics)
.SelectMany(topics => topics);
It is important to note that this treats the _context.Topic as an IEnumerable<T>; therefore, it will fetch all topics from the database and perform the GroupJoin on the client side, not on the database.
EDIT: Based on the comment below, this answer is NOT what you want. I'll just leave the answer here for documentation.
Related
There is an ASP.NET MVC application with an Order Data table and the table contains the order part ID and the Order Qty.
So somehow the previous developer set the Order Qty as string type.
Now for a report, I want to get a sum of the Qty according to the same Part Id's and show it, but it won't work.
Can you help me with this?
foreach(var item in rData) {
try {
if (db.OrderTable.Any(u => u.PartNo_Id == item.Id)) {
item.TotalOrderQty = db.OrderTable.Where(x => x.PartNo_Id == item.Id).Sum(x => x.OrderQty);
}
} catch (Exception ex) {
throw ex;
}
}
the error is cannot implicitly convert type 'string' to 'long' on the (x=>x.OrderQty)
If your db object is an OR-Mapper to a real database you are making a lot of queries which could be optimized, cause for every item in your list you ask at first, if data is available and at second trying to query these informations. It would be more effective to request the data in one shot (if it is not millions of lines) and compute the sums on the client side.
Maybe this sketch can help you:
// Define matching Ids to read from database
var idsToSearch = rData.Select(item => item.Id);
var matchingOrders = db.OrderTable
// Define criteria, which data has to be fetch.
.Join(idsToSearch, order => order.PartNo_Id, id => id, (order, id) => order)
// Prepare data on the server side to be already being grouped.
.GroupBy(order => order.PartNo_Id)
// Load data from the server to the client
.AsEnumerable()
// Parse and summarize the data on the client side.
.Select(group => (group.Key, group.SelectMany(order => int.Parse(order.OrderQty).Sum())
.ToList();
Depending on your model it could make sense to strip down the really needed data between the .Join() and the .GroupBy() call if the table holds a lot of (in this request unneeded) columns. Also be aware, that this code is not tested and maybe contains some stupid typo or similar, but it should give you a good starting point on how to tackle your problem.
Update (convert string to int on server side)
Thanks to Panagiotis comment, EF supports conversion methods and translates them to the corresponding SQL methods. Due to this fact, everything could be done on the server side. This could probably look something like this:
var matchingOrders = db.OrderTable
.Join(idsToSearch, order => order.PartNo_Id, id => id, (order, id) => order)
.GroupBy(order => order.PartNo_Id)
.Select(group => (group.Key, group.SelectMany(order => Convert.ToInt32(order.OrderQty).Sum())
.ToList();
So for my current project i am trying to show all posts with the same subjects as the user add to favourites.
Now finding all posts for one subject works fine, but i want to get all.
var posts = db.Posts.Where(x => x.SubjectID == currentUser.Favourites.SubjectID);
This is what i tried, but unfortunately that doesn't work, it can't find subjectID, probably because currentUser's Favourites is INumberable.
Is there anyway to compare to the multiple values that might be in the currentUser's Favourites?
You could use Any inside of your Where:
db.Posts.Where(x => currentUser.Favourites.Any(f => f.SubjectID ==x.SubjectID));
Create a list of SubjectId from Favourites and then pass that list to your where clause.
var subjectIDs = currentUser.Favourites.Select(x => x.SubjectID).ToList();
var posts = db.Posts.Where(x => subjectIDs.Equals(x.SubjectID));
Probably a few things wrong with my code here but I'm mostly having a problem with the syntax. Entry is a model for use in Entries and contains a TimeStamp for each entry. Member is a model for people who are assigned entries and contains an fk for Entry. I want to sort my list of members based off of how many entries the member has within a given period (arbitrarily chose 30 days).
A. I'm not sure that the function I created works correctly, but this is aside from the main point because I haven't really dug into it yet.
B. I cannot figure out the syntax of the Linq statement or if it's even possible.
Function:
private bool TimeCompare(DateTime TimeStamp)
{
DateTime bound = DateTime.Today.AddDays(-30);
if (bound <= TimeStamp)
{
return true;
}
return false;
}
Member list:
public PartialViewResult List()
{
var query = repository.Members.OrderByDescending(p => p.Entry.Count).Where(TimeCompare(p => p.Entry.Select(e => e.TimeStamp));
//return PartialView(repository.Members);
return PartialView(query);
}
the var query is my problem here and I can't seem to find a way to incorporate a boolean function into a .where statement in a linq.
EDIT
To summarize I am simply trying to query all entries timestamped within the past 30 days.
I also have to emphasize the relational/fk part as that appears to be forcing the Timestamp to be IEnumerable of System.Datetime instead of simple System.Datetime.
This errors with "Cannot implicitly convert timestamp to bool" on the E.TimeStamp:
var query = repository.Members.Where(p => p.Entry.First(e => e.TimeStamp) <= past30).OrderByDescending(p => p.Entry.Count);
This errors with Operator '<=' cannot be applied to operands of type 'System.Collections.Generic.IEnumerable' and 'System.DateTime'
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp) <= past30).OrderByDescending(p => p.Entry.Count);
EDIT2
Syntactically correct but not semantically:
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp).FirstOrDefault() <= timeComparison).OrderByDescending(p => p.Entry.Count);
The desired result is to pull all members and then sort by the number of entries they have, this pulls members with entries and then orders by the number of entries they have. Essentially the .where should somehow be nested inside of the .count.
EDIT3
Syntactically correct but results in a runtime error (Exception Details: System.ArgumentException: DbSortClause expressions must have a type that is order comparable.
Parameter name: key):
var query = repository.Members.OrderByDescending(p => p.Entry.Where(e => e.TimeStamp <= timeComparison));
EDIT4
Closer (as this line compiles) but it doesn't seem to be having any effect on the object. Regardless of how many entries I add for a user it doesn't change the sort order as desired (or at all).
var timeComparison = DateTime.Today.AddDays(-30).Day;
var query = repository.Members.OrderByDescending(p => p.Entry.Select(e => e.TimeStamp.Day <= timeComparison).FirstOrDefault());
A bit of research dictates that Linq to Entities (IE: This section)
...var query = repository.Members.OrderByDescending(...
tends to really not like it if you use your own functions, since it will try to map to a SQL variant.
Try something along the lines of this, and see if it helps:
var query = repository.Members.AsEnumerable().Where(TimeCompare(p => p.Entry.Select(e => e.TimeStamp).OrderByDescending(p => p.Entry.Count));
Edit: I should just read what you are trying to do. You want it to grab only the ones within the last X number of days, correct? I believe the following should work, but I would need to test when I get to my home computer...
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30);
var query = repository.Members.Where(p => p.Entry.Select(e => e.TimeStamp).FirstOrDefault() <= timeComparison).OrderByDescending(p => p.Entry.Count));
//return PartialView(repository.Members);
return PartialView(query);
}
Edit2: This may be a lack of understanding from your code, but is e the same type as p? If so, you should be able to just reference the timestamp like so:
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30);
var query = repository.Members.Where(p => p.TimeStamp <= timeComparison).OrderByDescending(p => p.Entry.Count));
//return PartialView(repository.Members);
return PartialView(query);
}
Edit3: In Edit3, I see what you are trying to do now (I believe). You're close, but OrderByDescending would need to go on the end. Try this:
var query = repository.Members
.Select(p => p.Entry.Where(e => e.TimeStamp <= timeComparison))
.OrderByDescending(p => p.Entry.Count);
Thanks for all the help Dylan but here is the final answer:
public PartialViewResult List()
{
var timeComparison = DateTime.Today.AddDays(-30).Day;
var query = repository.Members
.OrderBy(m => m.Entry.Where(e => e.TimeStamp.Day <= timeComparison).Count());
return PartialView(query);
}
I'm trying to get a list that displays 2 values in a label from a parent and child (1-*) entity collection model.
I have 3 entities:
[Customer]: CustomerId, Name, Address, ...
[Order]: OrderId, OrderDate, EmployeeId, Total, ...
[OrderStatus]: OrderStatusId, StatusLevel, StatusDate, ...
A Customer can have MANY Order, which in turn an Order can have MANY OrderStatus, i.e.
[Customer] 1--* [Order] 1--* [OrderStatus]
Given a CustomerId, I want to get all of the Orders (just OrderId) and the LATEST (MAX?) OrderStatus.StatusDate for that Order.
I've tried a couple of attempts, but can seem to get the results I want.
private IQueryable<Customer> GetOrderData(string customerId)
{
var ordersWithLatestStatusDate = Context.Customers
// Note: I am not sure if I should add the .Expand() extension methods here for the other two entity collections since I want these queries to be as performant as possible and since I am projecting below (only need to display 2 fields for each record in the IQueryable<T>, but thinking I should now after some contemplation.
.Where(x => x.CustomerId == SelectedCustomer.CustomerId)
.Select(x => new Custom
{
CustomerId = x.CustomerId,
...
// I would like to project my Child and GrandChild Collections, i.e. Orders and OrderStatuses here but don't know how to do that. I learned that by projecting, one does not need to "Include/Expand" these extension methods.
});
return ordersWithLatestStatusDate ;
}
---- UPDATE 1 ----
After the great solution from User: lazyberezovsky, I tried the following:
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.Select(o => new Customer
{
Name = c.Name,
LatestOrderDate = o.OrderStatus.Max(s => s.StatusDate)
});
In my hastiness from my initial posting, I didn't paste everything in correctly since it was mostly from memory and didn't have the exact code for reference at the time. My method is a strongly-typed IQueryabled where I need it to return a collection of items of type T due to a constraint within a rigid API that I have to go through that has an IQueryable query as one of its parameters. I am aware I can add other entities/attributes by either using the extension methods .Expand() and/or .Select(). One will notice that my latest UPDATED query above has an added "new Customer" within the .Select() where it was once anonymous. I'm positive that is why the query failed b/c it couldn't be turn into a valid Uri due to LatestOrderDate not being a property of Customer at the Server level. FYI, upon seeing the first answer below, I had added that property to my client-side Customer class with simple { get; set; }. So given this, can I somehow still have a Customer collection with the only bringing back those 2 fields from 2 different entities? The solution below looked so promising and ingenious!
---- END UPDATE 1 ----
FYI, the technologies I'm using are OData (WCF), Silverlight, C#.
Any tips/links will be appreciated.
This will give you list of { OrderId, LatestDate } objects
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.SelectMany(c => c.Orders)
.Select(o => new {
OrderId = o.OrderId,
LatestDate = o.Statuses.Max(s => s.StatusDate) });
.
UPDATE construct objects in-memory
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.SelectMany(c => c.Orders)
.AsEnumerable() // goes in-memory
.Select(o => new {
OrderId = o.OrderId,
LatestDate = o.Statuses.Max(s => s.StatusDate) });
Also grouping could help here.
If I read this correctly you want a Customer entity and then a single value computed from its Orders property. Currently this is not supported in OData. OData doesn't support computed values in the queries. So no expressions in the projections, no aggregates and so on.
Unfortunately even with two queries this is currently not possible since OData doesn't support any way of expressing the MAX functionality.
If you have control over the service, you could write a server side function/service operation to execute this kind of query.
Context: EF4, C#, .NET4, SQL2008/R2
Tables/entities to repro problem:
Account (long Id, string Name, etc.)
Order (long Id, DateTime
DateToExecute, int OrderStatus, etc.)
AccountOrder (long Id, long
AccountId, long OrderId) <- Yes, one account may have many orders and, likewise, one order may be associated with many accounts.
OrderedItem (long Id, long OrderId, long
ItemId, etc) <- One order may have many items, and we want to eager-load these items (I realize this has performance/data size implications).
Pseudocode (nearly real code) that would be ideal to work:
DateTime startDateInclusive = xxxx;
DateTime stopDateExclusive = yyy;
var query = Db.Accounts.Include(a => a.AccountOrders.Select(ao => ao.Order.Ordereditems.Select(oi => oi.Item)))
.Where(account =>
account.AccountOrders.Where(ao => ao.OrderStatus != 42)
.Max(ao => ao.DateToExecute).IsBetween(startDateInclusive, stopDateExclusive))
.OrderBy(account =>
account.AccountOrders.Where(ao => ao.OrderStatus != 42)
.Max(ao => ao.DateToExecute));
var results = query.Take(5).ToList();
In English, this is looking for the next 5 accounts that have their last order to be executed within a date range. However, there are also Orders that can be cancelled, so we must exclude OrderStatus of 42 when performing that Max.
The problem revolves around this filtered Max date across many-to-many tables. An added complexity is that we need to sort by that filtered max value and we must do all of the above without breaking our eager loading (i.e. joins must be done via projection in the Where and not using a .Join). I’m not sure how to do this query without the result being 10x’s more complex than it should be. I’d hate to do the joins to filter the ao.OrderStatus/Max the DateToExecute 3 times (once for startDate, once for stopDate, and once for the sort). And clearly the IsBetween isn’t functional.
Any ideas on how to perform this query, sorted this way, in a fairly-efficient way for the generated SQL?
It may be helpful to use an anonymous type here:
DateTime startDateInclusive = xxxx;
DateTime stopDateExclusive = yyy;
var query = Db.Accounts
.Select(account => new {
Account = account,
MaxDate = account.AccountOrders.Select(ao => ao.Order).Where(o => o.OrderStatus != 42).Max(o => o.DateToExecute)
})
.Where(a => a.MaxDate >= startDateInclusive && a.MaxDate < stopDateExclusive)
.OrderBy(a => a.MaxDate)
.Select(a => a.Account)
.Include(a => a.AccountOrders.Select(ao => ao.Order.Ordereditems.Select(oi => oi.Item)));
var results = query.Take(5).ToList();
This is untested as I don't have any datasource to test against. But it's probably the simplest approach for what you need to do.