I am struggling with converting MySQL query to linq syntax in C# (for use of Entity Framework). MySQL query looks like this:
SELECT *
FROM Availability as tableData
WHERE ID = (
SELECT Availability.ID
FROM Availability
WHERE Availability.FrameID = tableData.FrameID
ORDER BY Availability.Date DESC limit 1)
I don't know how to convert this part FROM table AS someName.
So far the only solution I have, is to execute raw SQL query such as:
dataContext.Availability.SqlQuery("SELECT * FROM Availability as tableData WHERE ID = (SELECT ID FROM Availability WHERE FrameID = tableData.FrameID ORDER BY Availability.Date DESC limit 1)").ToArray();
But it would be nice to know if linq can provide such a query.
Thanks in advance, for your answers!
If you need only latest record for every frame id, then use grouping:
dataContext.Availability
.GroupBy(a => a.FrameID)
.Select(g => g.OrderByDescending(a => a.Date).FirstOrDefault());
This query produces required result, though generated sql will be a little different. It will look like
SELECT /* limit1 fields */
FROM (
SELECT DISTINCT tableData.FrameID
FROM Availability as tableData) AS distinct1
OUTER APPLY (
SELECT TOP(1) /* project1 fields */
FROM (SELECT /* extent1 fields */
FROM Availability AS extent1
WHERE Availability.FrameID = distinct1.FrameID) AS project1
ORDER BY project1.Date DESC) AS limit1
NOTE: First() extension is not supported by EF
Take all the Avilabilities, group by FrameId, order each group by date, take the first entry of each group.
The ToList() at the end fetches all the results and puts them in a List.
var tableDate = dataContext.Availability
.GroupBy(x => x.FrameId)
.Select(x => x.OrderByDescending(y => y.Date).FirstOrDefault())
.ToList();
Yes Linq can do this, but you need to have a starting sequence on which the linq should operate. Usually this sequence has the same type as your table, in your case Availability.
From your sql I gather that each record in the Availabilities table has at least properties Id, FrameId and Date:
class Availability
{
public int Id {get; set;}
public int FrameId {get; set;
public DateTime Date {get; set;}
}
Of course this can also be an anonymous type. The importance is that you have somehow a sequence of items having these properties:
IQueryable<Availability> availabilities = ...
You wrote:
I need only one record (with max Date of insert) for every FrameID
So every Availability has a FrameId, and you want for every FrameId the record with the highest Date value.
You could use Enumerable.GroupBy and group by FrameId
var groupsWithSameFrameId = availabilities.GroupBy(availability => availability.FrameId);
The result is a sequence of groups. Every group contains the sequence of all availabilities with the same FrameId. In other words: if you take a group, you'll have a group.Key with a FrameId value and a sequence of all availabilities that have this FrameId value.
We won't use the group.Key.
If you sort the sequence of elements in each group in descending order by Date and take the first element, you'll have the date with the highest value
var recordWithMaxDateOfInsert = groupsWithSameFrameId
.Select(group => group.OrderByDescending(groupElement => groupElement.Date)
.First();
From every group sort all elements of the group by descending Date value and take the first element of the sorted group.
Result: from your original availabilities, you have for every frameId the availability with the highest value for date.
Related
I have model, where properties are
Id, Name, Category, DateCreated, Rating
I have working one decision, but need to rewrite it in a fluent style without from.
return await ( from x in _context.Products
orderby x.DateCreated
group x.Category by x.Category into g
orderby g.Count() descending
select g.Key
).Take(3)
.Select(element => ((Categories)element).ToString())
.ToListAsync();
Could you help me?
So you have a sequence of Products, and you want to make groups of Products. Every group contains Products of one Category. This Category is the Key of the Group.
You don't want all groups, you only want the 3 Groups that has the most Products.
You also are not interested what is in the Groups, you are only interested in the string representation of the Category of all Products in the Group.
Whenever you want to make groups of items, but you want something more than just Groups of items, consider to use the overload of Queryable.GroupBy that has a parameter resultSelector
var result = dbContext.Products.GroupBy(product => product.Category,
// parameter resultSelector: take any found Category, and all Products that have
// this Category to make one new:
(category, productsWithThisCategory) => new
{
Category = category,
Count = productsWithThisCategory.Count(),
})
// only keep the 3 Groups that has the most Products
.OrderByDescending(productGroup => productGroup.Count)
.Take(3)
Well, you are not interested in the Count anymore, you only want the string representation of the Category.
.Select(productGroup => productGroup.Category.ToString());
IMHO it is not wise to convert your Category to a string. You will probably need more bytes to transfer the data to a local process, and if users of your procedure (= software, not operators) want to interpret the fetched data they have to use relatively slow String comparisons. Besides: if you change a Category, the compiler won't complain, but your code will fail.
So consider to return the Category as the type it really is (enum?). Only convert to strings when you need to convert it to readable text (for operators, text files, etc)
.Select(productGroup => productGroup.Category);
Here is a query:
from order in db.tblCustomerBuys
where selectedProducts.Contains(order.ProductID)
select order.CustomerID;
selectedProducts is a list containing some target products IDs, for example it is { 1, 2, 3}.
The query above will return customerIDs where they have bought one of the selectedProducts. for example if someone has bought product 1 or 2, its ID will be in result.
But I need to collect CustomerIDs where they have bought all of the products. for example if someone has bought product 1 AND 2 AND 3 then it will be in result.
How to edit this query?
the tblCustomerBuys are like this:
CustomerID - ID of Customer
ProductID - the product which the customer has bought
something like this:
CustomerID ProdcutID
---------------------------
110 1
110 2
112 3
112 3
115 5
Updated:
due to answers I should do grouping, for some reason I should use this type of query:
var ID = from order in db.tblCustomerBuys
group order by order.CustomerID into g
where (selectedProducts.All(selProdID => g.Select(order => order.ProductID).Contains(selProdID)))
select g.Key;
but it will give this error:
Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.
The updated query is the general LINQ solution of the issue.
But since your query provider does not support mixing the in memory sequences with database tables inside the query (other than Contains which is translated to SQL IN (value_list)), you need an alternative equivalent approach of All method, which could be to count the (distinct) matches and compare to the selected items count.
If the { CustomerID, ProductID } combination is unique in tblCustomerBuys, then the query could be as follows:
var selectedCount = selectedProducts.Distinct().Count();
var customerIDs =
from order in db.tblCustomerBuys
group order by order.CustomerID into customerOrders
where customerOrders.Where(order => selectedProducts.Contains(order.ProductID))
.Count() == selectedCount
select customerOrders.Key;
And if it's not unique, use the following criteria:
where customerOrders.Where(order => selectedProducts.Contains(order.ProductID))
.Select(order => order.ProductID).Distinct().Count() == selectedCount
As your question is written, it is a bit difficult to understand your structure. If I have understood correctly, you have an enumerable selectedProducts, which contains several Ids. You also have an enumeration of order objects, which have two properties we care about, ProductId and CustomerId, which are integers.
In this case, this should do the job:
ver result = db.tblCustomerBuys.GroupBy(order => order.CustomerId)
.Where(group => !selectedProducts.Except(group).Any())
.Select(group => group.Key);
What we are doing here is we are grouping all the customers together by their CustomerId, so that we can treat each customer as a single value. Then we are treating group as a superset of selectedProducts, and using a a piece of linq trickery commonly used to check if one enumeration is a subset of another. We filter db.tblCustomerBuys based on that, and then select the CustomerId of each order that matches.
You can use Any condition of Linq.
Step 1 : Create list of int where all required product id is stored
Step 2: Use Any condition of linq to compare from that list
List<int> selectedProducts = new List<int>() { 1,2 } // This list will contain required product ID
db.tblCustomerBuys.where(o=> selectedProducts .Any(p => p == o.ProductID)).select (o=>o.order.CustomerID); // This will return all customerid who bought productID 1 or 2
I want to be able to execute the following query:
select distinct studentname, subject, grade from studenttable;
The student table has the following fields:
studentid
studentname
subject
grade
The linq query I have now is:
var students=dc.Select(s => new {s.studentname, s.subject, s.grade}).Distinct();
dc is the data context.
This query works, but how do I get the student id while still satisfying the distinct condition on the studentname, subject, grade set?
The issue here is that you've collected three properties of the necessary data which, while they will suffice to pass through Distinct, don't have any link back to their original data, and any such link would break the default implementation of Distinct.
What you CAN do, however, is to use the overload of distinct which takes an IEqualityComparer. This will allow you to compare for equality only on the desired fields, while running Distinct over the entire collection.
var students = dc
.AsEnumerable() // In case you're using a linq-to-sql framework, this will ensure the query execute in-memory
.Distinct(new SpecialtyComparer());
//...
public class SpecialtyComparer : IEqualityComparer<StudentTable>
{
public int GetHashCode(StudentTable s)
{
return s.studentname.GetHashCode()
&& s.subject.GetHashCode()
&& s.grade.GetHashCode();
}
public bool Equals(StudentTable s1, StudenTable s2)
{
return s1.studentname.Equals(s2.studentname)
&& s1.subject.Equals(s2.subject)
&& s1.grade.Equals(s2.grade);
}
}
I believe your design is broken, but I'll answer your specific question....
I'm assuming you're trying to group by name, subject and grade, and retrieve the first representative student of each group.
In this case, you can group by Tuples. A tuple will give you an Equals and GetHashCode method for free, so can be used in Group operations.
IEnumerable<Student> distinctStudents = students
.AsEnumerable()
.GroupBy(s => Tuple.Create
(
s.studentname,
s.subject,
s.grade
)
)
.Select(g => g.First()); /// Returns the first student of each group
Here is a dot net fiddle example: https://dotnetfiddle.net/7K13DJ
When doing a Distinct() on a list of objects, you are aggregating those rows into a smaller number of rows, discarding any duplicates. So, your result will not have the studentid anymore. To preserve the studentid property, you need to use a GroupBy. This will return to you your key (a student of studentname, subject, grade), and a list of original rows. You can see an example in this question here: (GroupBy and count the unique elements in a List).
You will have a list of studentids to choose from, since there might be many rows with the same studentname, subject, and grade. (Otherwise you would not be doing a distinct -- they would be unique and the tuple { sudentname, subject, grade } would be a natural key). So the question you may need to ask yourself is "Which studentid do I need?"
I'd like to make a query through entity framework that unions contacts from two different tables, remove duplicates and orders by date. The issue I'm having is around the different dates making the same contact appear as unique. I don't want to include the date in the distinct but I do need it afterwards for the ordering. I can't do the ordering first, remove the date and then perform the distinct, because the distinct changes the ordering. Neither can I order before the union because that doesn't ensure ordering after the union.
I would like to distinct all fields except the date, which is only required for the ordering.
Ideally I would pass a comparer to the distinct but EF can't translate this to SQL.
db.Packages.Select(p => new Recent()
{
Name = p.Attention, Address1 = p.Address1, ... , Date = ShippingDate
})
.Concat(db.Letters.Select(l => new Recent()
{
Name = l.AddressedTo, Address1 = p.Address1, ..., Date = MarkedDate
})
.Distinct()
.OrderByDescending(r => r.Date);
OR the problem in SQL
SELECT DISTINCT Attention, Address1, ShippingDate
FROM Packages
UNION ALL
SELECT AddressedTo, Address1, MarkedDate
FROM Letters
ORDER BY ShipmentDate DESC
You should be able to use a GroupBy to do what you want, like so (not to mention Group By is more performant than Distinct in EF):
db.Packages.Select(p => new Recent()
{
Name = p.Attention, Address1 = p.Address1, ... , Date = ShippingDate})
.Concat(db.Letters.Select(l => new Recent()
{
Name = l.AddressedTo, Address1 = p.Address1, ..., Date = MarkedDate}))
.GroupBy(p => <parameters to group by - which make the record distinct>)
.Select(g => new {Contact = g.Key, LastShippingDate = g.Max(p => p.ShippingDate)});
I'd be concerned with this approach, even if it was possible distinct would then remove one of the items and leave you with random date out of the two, and then your sort would be totally unpredictable.
I have a table of orders made by persons:
Orders
{
Guid PersonId,
int Priority,
Guid GoodId
}
Priority is some integer number. For example:
AlenId 1 CarId
DianaId 0 HouseId
AlenId 3 FoodId
DianaId 2 FlowerId
I want to retrieve highest priority orders for each person:
AlenId 1 CarId
DianaId 0 HouseId
In T-SQL I'll use ranking, how can I get the same result in Linq-2-Sql ?
Thank you in advance!
Something like this:
var query = from order in context.Orders
orderby order.Priority
group order by order.PersonId into personOrders
select personOrders.First();
I believe that should work, but I don't know how well-defined it is, in terms of the ordering post-grouping. This would also work, although it's slightly uglier:
var query = from order in context.Orders
group order by order.PersonId into personOrders
select personOrders.OrderBy(order => order.Priority).First();
Or using just dot notation:
var query = context.Orders
.GroupBy(order => order.PersonId)
.Select(group => group.OrderBy(order => order.Priority)
.First());