the equivalent query of mutual friends in linq or lambda syntax - c#

I have two tables tblFriends and tblUsers where I store user's id and friend's id.
Now I would like to find mutual friends of user1 and user2 in table 'tblFriends' AND details of them e.q. nickname,age ...
tblUsers:
Username nvarchar
Avatar nvarchar
Age int
tblFriends:
IdUser1 nvarchar
IdUser2 nvarchar
FriendStatus int
I find the bellow solution in sql and it works fine but I need the equivalent of this query in LINQ or Lambda
the solution that I find is here ( https://www.codeproject.com/Questions/280296/query-to-display-mutual-friends )
SELECT P1.Name
FROM dbo.Friendship AS F1
JOIN dbo.Person AS P1 ON P1.ID = F1.FriendID
WHERE F1.PersonID = 1 AND
F1.FriendID IN (SELECT F2.FriendID
FROM dbo.Friendship AS F2
WHERE F2.PersonID = 2)

No need for Join:
var mutualFriendsNames = db.Users
.Where(u =>
db.FriendShips.Any(f => f.FriendId == u.Id && f.UserId == 1) &&
db.FriendShips.Any(f => f.FriendId == u.Id && f.UserId == 2))
.Select(p => p.Name);
Just notice that this will work only if friendship relation is one way relationship (if user1 is friend with user2 then user2 is not necessary friend with user1).
If the relation is two ways, then you have two options:
Define the relationship as two rows in DB (each one describes a way).
The relationship is implicitly a two-ways relationship, then you should refactor the LINQ query above:
var mutualFriendsNames = db.Users
.Where(u =>
db.FriendShips.Any(f =>
(f.FriendId == u.Id && f.UserId == 1) ||
(f.FriendId == 1 && f.UserId == u.Id)) &&
db.FriendShips.Any(f =>
(f.FriendId == u.Id && f.UserId == 2) ||
(f.FriendId == 2 && f.UserId == u.Id)))
.Select(p => p.Name);

This example is equivalent to the given SQL query
using System;
using System.Collections.Generic;
using System.Linq;
namespace Friends
{
class Program
{
static void Main(string[] args)
{
var data = new GenerateExampleData();
var lstFriendId = (from f in data.lstFriend
where f.PersonId == 2
select f.FriendId
);
var lstMutualFriend = (from f in data.lstFriend
join p in data.lstPerson on f.FriendId equals p.Id
where lstFriendId.Contains(f.FriendId) && f.PersonId == 1
select p.Name
);
foreach (var item in lstMutualFriend)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
public class GenerateExampleData
{
public List<Person> lstPerson;
public List<Friendship> lstFriend;
public GenerateExampleData()
{
lstPerson = new List<Person>
{
new Person
{
Id = 1,
Name ="Person1"
},
new Person
{
Id = 2,
Name ="Person2"
},
new Person
{
Id = 3,
Name ="Person3"
},
new Person
{
Id = 4,
Name ="Person4"
},
};
lstFriend = new List<Friendship>
{
new Friendship
{
PersonId = 1,
FriendId = 2
},
new Friendship
{
PersonId = 1,
FriendId = 4
},
new Friendship
{
PersonId = 2,
FriendId = 4
},
};
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Friendship
{
public int PersonId { get; set; }
public int FriendId { get; set; }
}
}

Related

How to use ORDER BY COUNT in LINQ and SELECT COUNT it?

I have a query and I don't know change it to LINQ
select distinct m.id,m.name, sch.id as schedule, COUNT(tk.id) as tiketSold
from movies m, schedules sch, tickets tk
where m.id = sch.movie_id and sch.id = tk.schedule_id
group by m.id,m.name, sch.id
order by COUNT(tk.id) desc
I'm trying:
var hotMovie = from m in _db.movies
from sch in _db.schedules
from tk in _db.tickets
where m.id == sch.movie_id && sch.id == tk.schedule_id
group m by m.id into g
orderby g.Count()
select new { Movie = g};
I do not have your database, so, I have created 3 classes like your table as I can anticipate. Then I have created 3 list like you table in the "TestMethod". In the linq query, I have joined the 3 list as you shown in sql query segment "where m.id = sch.movie_id and sch.id = tk.schedule_id" and then I perform the group by, order by an select. Here is my code, please try it and let me know it works or not.
public class movies
{
public int id { get; set; }
public string name { get; set; }
}
public class schedules
{
public int id { get; set; }
public int movie_id { get; set; }
}
public class tickets
{
public int id { get; set; }
public int schedule_id { get; set; }
}
void TestMethod()
{
//Add Movies to the list
List<movies> moviesItems = new List<movies>();
moviesItems.Add(new movies() { id = 1, name = "A" });
moviesItems.Add(new movies() { id = 2, name = "B" });
//Add Schedules to the list
List<schedules> schedulesItems = new List<schedules>();
schedulesItems.Add(new schedules() { id = 1, movie_id = 1 });
schedulesItems.Add(new schedules() { id = 2, movie_id = 2 });
schedulesItems.Add(new schedules() { id = 3, movie_id = 1 });
schedulesItems.Add(new schedules() { id = 4, movie_id = 2 });
//Add Tickets to the list
List<tickets> ticketsItems = new List<tickets>();
ticketsItems.Add(new tickets() { id = 1, schedule_id = 1 });
ticketsItems.Add(new tickets() { id = 2, schedule_id = 1 });
ticketsItems.Add(new tickets() { id = 3, schedule_id = 2 });
ticketsItems.Add(new tickets() { id = 4, schedule_id = 2 });
ticketsItems.Add(new tickets() { id = 5, schedule_id = 2 });
ticketsItems.Add(new tickets() { id = 6, schedule_id = 3 });
ticketsItems.Add(new tickets() { id = 7, schedule_id = 3 });
ticketsItems.Add(new tickets() { id = 8, schedule_id = 3 });
ticketsItems.Add(new tickets() { id = 9, schedule_id = 3 });
ticketsItems.Add(new tickets() { id = 10, schedule_id = 4 });
var query = from final in (from m in moviesItems
join sch in schedulesItems on m.id equals sch.movie_id
join tk in ticketsItems on sch.id equals tk.schedule_id
select new { movieID = m.id, movieName = m.name, schID = sch.id, tkID = tk.id })
group final by new { final.movieID, final.movieName, final.schID } into g
orderby g.Count() descending
select new { g.Key.movieID, g.Key.movieName, g.Key.schID, tiketSold = g.Count() };
}
This query is closest to your SQL but probably you need LEFT JOIN. Also it can be simplified using navigation properties if you provide model.
var hotMovie =
from m in _db.movies
join sch in _db.schedules on m.id equals sch.movie_id
join tk in _db.tickets on sch.id equals tk.schedule_id
group tk by new { movieID = m.id, movieName = m.name, scheduleId = sch.id } into g
orderby g.Sum(x => x.id != null ? 1 : 0) descending
select new
{
g.Key.movieID,
g.Key.movieName,
g.Key.scheduleId,
tiketSold = g.Sum(x => x.id != null ? 1 : 0)
};

Convert SQL query to linq lambda Entity Framework Core

I need select top 10 LINQ query with group by, sum(quantity) and where clause using Entity Framework Core.
Could somebody help me please to convert this SQL code to linq lambda?
Thank you very much.
SELECT TOP 10
OrderItems.ProductName,
OrderItems.ProductId,
SUM(OrderItems.Units) AS Quantity
FROM
Orders, OrderItems
WHERE
OrderItems.OrderId = Orders.Id
AND Orders.OrderDate >= '2019-12-18 16:38:27'
AND Orders.OrderDate <= '2020-12-18 16:38:27'
AND Orders.OrderStatusId = 2
GROUP BY
ProductName, OrderItems.ProductId
ORDER BY
Quantity DESC
I tried this EF query:
var query = (from sta in _context.Set<OrderItem>().AsQueryable()
join rec in _context.Set<Order>().AsQueryable() on sta.OrderId equals rec.Id
where rec.OrderStatusId == Convert.ToInt32(orderStatusId) && rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
group sta by new
{
sta.ProductName,
sta.ProductId
} into grp
select new OrderDto()
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Max(t => t.Units),
}).OrderBy(x => x.Quantity).Take(Convert.ToInt32(top)).ToList();
I believe your statement SELECT ... FROM Orders, OrderItems... WHERE OrderItems.OrderId = Orders.Id while looking like a CROSS JOIN, ends up being optimised into an INNER JOIN.
So, assuming you've got your model set up with navigation properties you might be better off using .Include(). Apart from that I think you are pretty much there:
var query = _context.Set<OrderItem>().Include(o => o.Order)
.Where(rec => rec.Order.OrderStatusId == Convert.ToInt32(orderStatusId))
.Where(rec => rec.Order.OrderDate >= Convert.ToDateTime(startDate) && rec.Order.OrderDate <= Convert.ToDateTime(endDate))
.GroupBy(g => new { g.ProductName, g.ProductId })
.Select(grp => new OrderDto
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Sum(t => t.Units)
})
.OrderBy(x => x.Quantity)
.Take(Convert.ToInt32(top));
This produces the following output:
SELECT TOP(#__p_3) [o].[ProductName], [o].[ProductId], SUM([o].[Units]) AS [Quantity]
FROM [OrderItems] AS [o]
INNER JOIN [Orders] AS [o0] ON [o].[OrderId] = [o0].[Id]
WHERE ([o0].[OrderStatusId] = #__ToInt32_0) AND (([o0].[OrderDate] >= #__ToDateTime_1) AND ([o0].[OrderDate] <= #__ToDateTime_2))
GROUP BY [o].[ProductName], [o].[ProductId]
ORDER BY SUM([o].[Units])
Suppose you can't add the navigation property to your OrderItem model, then your code seems pretty much there:
var query2 = (from sta in _context.Set<OrderItem>()
from rec in _context.Set<Order>()
where sta.OrderId == rec.Id && rec.OrderStatusId == Convert.ToInt32(orderStatusId)
&& rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
group sta by new
{
sta.ProductName,
sta.ProductId
} into grp
select new OrderDto()
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Max(t => t.Units),
}
)
.OrderBy(x => x.Quantity)
.Take(Convert.ToInt32(top));
This produces the following SQL:
SELECT TOP(#__p_3) [o].[ProductName], [o].[ProductId], MAX([o].[Units]) AS [Quantity]
FROM [OrderItems] AS [o]
CROSS JOIN [Orders] AS [o0]
WHERE ((([o].[OrderId] = [o0].[Id]) AND ([o0].[OrderStatusId] = #__ToInt32_0)) AND ([o0].[OrderDate] >= #__ToDateTime_1)) AND ([o0].[OrderDate] <= #__ToDateTime_2)
GROUP BY [o].[ProductName], [o].[ProductId]
ORDER BY MAX([o].[Units])
Here's my full test bench for reference
using Microsoft.EntityFrameworkCore
using Microsoft.EntityFrameworkCore.Query.SqlExpressions
using Microsoft.EntityFrameworkCore.Query
#region EF Core 3.1 .ToSql() helper method courtesy of https://stackoverflow.com/a/51583047/12339804
public static class IQueryableExtensions
{
public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
var relationalCommandCache = enumerator.Private("_relationalCommandCache");
var selectExpression = relationalCommandCache.Private<SelectExpression>("_selectExpression");
var factory = relationalCommandCache.Private<IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
var sqlGenerator = factory.Create();
var command = sqlGenerator.GetCommand(selectExpression);
string sql = command.CommandText;
return sql;
}
private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
}
#endregion
public class OrderItem
{
public int Id {get;set;}
public int OrderId {get;set;}
public int ProductName {get;set;}
public int ProductId {get;set;}
public int Units {get;set;}
public Order Order {get;set;} // added navigation property for .Include() to pick up on
}
public class Order {
public int Id {get;set;}
public int OrderStatusId {get;set;}
public DateTime OrderDate {get;set;}
}
public class OrderDto
{
public int ProductName { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
}
class Dbc : DbContext
{
public DbSet<Order> Orders {get;set;}
public DbSet<OrderItem> OrderItems {get;set;}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=.\SQLEXPRESS;Database=Test;Trusted_Connection=True");
base.OnConfiguring(optionsBuilder);
}
}
void Main()
{
var _context = new Dbc();
var orderStatusId = "2";
var top = "10";
var startDate = DateTime.Parse("2019-12-16 16:38:27");
var endDate = DateTime.Parse("2019-12-18 16:38:27");
var query = _context.Set<OrderItem>().Include(o => o.Order)
.Where(rec => rec.Order.OrderStatusId == Convert.ToInt32(orderStatusId))
.Where(rec => rec.Order.OrderDate >= Convert.ToDateTime(startDate) && rec.Order.OrderDate <= Convert.ToDateTime(endDate))
.GroupBy(g => new { g.ProductName, g.ProductId })
.Select(grp => new OrderDto
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Sum(t => t.Units)
})
.OrderBy(x => x.Quantity)
.Take(Convert.ToInt32(top));
query.ToSql().Dump();
//alternatively, trying to force a cross join syntax with extra WHERE condition. This way you don't need `public Order Order {get;set;}` navigation property on `OrderItem`
var query2 = (from sta in _context.Set<OrderItem>()
from rec in _context.Set<Order>()
where sta.OrderId == rec.Id && rec.OrderStatusId == Convert.ToInt32(orderStatusId)
&& rec.OrderDate >= Convert.ToDateTime(startDate) && rec.OrderDate <= Convert.ToDateTime(endDate)
group sta by new
{
sta.ProductName,
sta.ProductId
} into grp
select new OrderDto()
{
ProductName = grp.Key.ProductName,
ProductId = grp.Key.ProductId,
Quantity = grp.Max(t => t.Units),
}
)
.OrderBy(x => x.Quantity)
.Take(Convert.ToInt32(top));
query2.ToSql().Dump();
}

LINQ get a record with max date from multiple joins

I have three tables: Courses, CourseLocations, CourseSchedules
Each Course can be held in one or more Locations (1-to-many)
Each Location can host one or more Schedules (1-to-many)
I need to get all the courses, unique names, that have a Schedules.Date> Today and show also the MAX value of the date contained in the table CourseSchedules
My current linq code is:
var courses = (from c in db.Courses
join cl in db.CourseLocations on c.CourseID equals cl.CourseID
join cs in db.CourseSchedules on cl.CourseLocationID equals cs.CourseLocationID
where c.CourseStatusID == 1 && c.DeleteDate == null && ((c.CourseCategoryID == 1 && cs.EndDate >= courseEndDateFilter) || (c.CourseCategoryID == 3))
select new
{
c.CourseID,
CourseName = c.Name,
CourseEndDate = cs.EndDate
}).Distinct().OrderBy(o => o.CourseCategoryID).ThenBy(o => o.CourseName);
Where courseEndDateFilter is a variable used to define the date to filter.
The problem with the above query is that I get all the courses duplicated and not only the one with the MAX value of cs.EndDate
Is there a way (efficient) to do it?
#Ehsan is correct. You need a group by and then get the max value of EndDate. Given the following models:
public class Course
{
public int CourseID { get; set; }
public string Name { get; set; }
public int CourseStatusID { get; set; }
public int CourseCategoryID { get; set; }
public DateTime? DeleteDate { get; set; }
}
public class CourseLocation
{
public int CourseLocationID { get; set; }
public int CourseID { get; set; }
}
public class CourseSchedules
{
public int CourseLocationID { get; set; }
public DateTime EndDate { get; set; }
}
I created the following in memory objects:
var courses = new List<Course>
{
new Course { CourseID = 1, Name = "Test1", CourseCategoryID = 1, CourseStatusID = 1, DeleteDate = null },
new Course { CourseID = 2, Name = "Test2", CourseCategoryID = 1, CourseStatusID = 1, DeleteDate = null },
new Course { CourseID = 3, Name = "Test3", CourseCategoryID = 3, CourseStatusID = 1, DeleteDate = null }
};
var courseLocations = new List<CourseLocation>
{
new CourseLocation{ CourseID = 1, CourseLocationID = 1 },
new CourseLocation{ CourseID = 2, CourseLocationID = 1 },
new CourseLocation{ CourseID = 3, CourseLocationID = 1 },
new CourseLocation{ CourseID = 1, CourseLocationID = 2 },
new CourseLocation{ CourseID = 2, CourseLocationID = 2 },
new CourseLocation{ CourseID = 3, CourseLocationID = 2 }
};
var courseSchedules = new List<CourseSchedules>
{
new CourseSchedules { CourseLocationID = 1, EndDate = DateTime.Now.AddDays(10) },
new CourseSchedules { CourseLocationID = 1, EndDate = DateTime.Now.AddYears(1) }
};
Then the query would be the following to get Max EndDate:
var result = (from c in courses
join cl in courseLocations on c.CourseID equals cl.CourseID
join cs in courseSchedules on cl.CourseLocationID equals cs.CourseLocationID
where c.CourseStatusID == 1 && c.DeleteDate == null &&
(c.CourseCategoryID == 1 && cs.EndDate >= DateTime.Now || c.CourseCategoryID == 3)
select new
{
c.CourseID,
CourseName = c.Name,
CourseEndDate = cs.EndDate,
c.CourseCategoryID
})
.GroupBy(arg => new
{
arg.CourseID,
arg.CourseName,
arg.CourseCategoryID
})
.Select(grouping => new
{
grouping.Key.CourseID,
grouping.Key.CourseName,
CourseEndDate = grouping.Max(arg => arg.CourseEndDate),
grouping.Key.CourseCategoryID
})
.OrderBy(o => o.CourseCategoryID)
.ThenBy(o => o.CourseName);
I'm not sure this will work since it's not like I can actually compile it.
The problem I see right away is that you're filter by a few things at the base, some of which don't include the locations but you want the date from the locations anyway...
var courses = (from c in db.Courses
join cl in db.CourseLocations on c.CourseID equals cl.CourseID
join cs in db.CourseSchedules on cl.CourseLocationID equals cs.CourseLocationID
where c.CourseStatusID == 1 && c.DeleteDate == null && (c.CourseCategoryID == 3 ||
db.CourseLocations.Any(cl => cl.CourseID equals c.CourseID &&
db.CourseSchedules.Any(cs => cs.CourseLocationID equals cl.CourseLocationID &&
((c.CourseCategoryID == 1 && cs.EndDate >= courseEndDateFilter))
)
))
select new
{
c.CourseID,
CourseName = c.Name,
CourseEndDate = db.CourseSchedules.Where(cs => db.CourseLocations.Any(cl => cl.CourseID equals c.CourseID && cs.CourseLocationID equals cl.CourseLocationID)).Max(cs => cs.EndDate),
c.CourseCategoryID
});

Entity Framework Linq, Left Join and group with SUM and Count

I need a little help in converting SQL to Linq. It's pretty straight forward in MySQL...
Table: customers
ID Name
1 Bill
2 John
Table: purchases
ID CustomerID CompletedTransaction
1 1 False
2 2 True
3 1 True
4 1 True
SELECT c.ID
c.Name,
COUNT(p.ID) AS TotalPurchases,
SUM(CASE WHEN p.CompletedTransaction = TRUE THEN 1 ELSE 0 END) AS TotalCompleted
FROM customers c
LEFT JOIN purchases p ON c.ID = p.CustomerID
GROUP BY c.ID
Expected Result:
1, Bill, 3, 2
2, John, 1, 1
I've seen a few examples on how to implement a left join in Linq but I'm not sure how to include a SUM and Count into this. I've seen examples in Linq where the fields returned are selected from the group keys. Does this mean that if I have more fields in the customers table such as address and other contact details which I'd like to return, I'd have to include them in the join to then be able to select them? Hope this makes sense. Appreciate any help or links that might point me in the right direction.
Thanks
var answer = (from c in db.customers
join p in db.purchases
on c.ID = p.CustomerID into subs
from sub in subs.DefaultIfEmpty()
group sub by new { c.ID, c.Name } into gr
select new {
gr.Key.ID,
gr.Key.Name,
Total = gr.Count(x => x != null),
CountCompleted = gr.Count(x => x != null && x.CompletedTransaction)
}).ToList();
Here's the sample
class Program
{
static void Main(string[] args)
{
List<Customers> customers = new List<Customers>();
customers.Add(new Customers() { ID = 1, Name = "Bill" });
customers.Add(new Customers() { ID = 2, Name = "John" });
List<Purchases> purchases = new List<Purchases>();
purchases.Add(new Purchases() { ID = 1, CustomerID = 1, CompletedTransaction = false });
purchases.Add(new Purchases() { ID = 2, CustomerID = 2, CompletedTransaction = true });
purchases.Add(new Purchases() { ID = 3, CustomerID = 1, CompletedTransaction = true });
purchases.Add(new Purchases() { ID = 4, CustomerID = 1, CompletedTransaction = true });
IEnumerable<JoinResult> results = from c in customers
join p in purchases
on c.ID equals p.CustomerID
group new { c, p } by new {p.CustomerID, c.Name} into r
select new JoinResult
{
CustomerID = r.Key.CustomerID,
CustomerName = r.Key.Name,
TotalPurchases = r.Count(),
TotalCompleteTransaction = r.Where(s=> s.p.CompletedTransaction).Count()
};
foreach(JoinResult r in results)
{
Console.WriteLine($"CustomerID : {r.CustomerID} | Name : {r.CustomerName} | TotalPurchases : {r.TotalPurchases} | TotalCompleteTransaction : {r.TotalCompleteTransaction}");
}
Console.ReadKey();
}
}
class Customers
{
public int ID { get; set; }
public string Name { get; set; }
}
class Purchases
{
public int ID { get; set; }
public int CustomerID { get; set; }
public bool CompletedTransaction { get; set; }
}
class JoinResult
{
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public int TotalPurchases { get; set; }
public int TotalCompleteTransaction { get; set; }
}
Result

Lambda expression execution

When the DoTranslation method is run, the output is the same hash code for
all of the TranslatedObjects. Why is this happening as opposed to having a new List for each TranslatedObject?
public class TranslatedObject
{
public static Expression<Func<OriginalObject, TranslatedObject>> ObjectTranslator = o => new TranslatedObject
{
id = o.id
translatedList = new List<String>()
};
public int id { get; set; }
public List<String> translatedList { get; set; }
}
public class Translator
{
public void DoTranslation()
{
//3 objects returned with ids 1, 2, and 3 respectively
IQueryable<OriginalObject> originalObjects = dataContext.OriginalObjects.Where(o => o.id == 1 || o.id == 2 || o.id == 3);
var translatedObjects = originalObjects.Select(TranslatedObject.ObjectTranslator).ToArray();
foreach(TranslatedObject translated in translatedObjects)
{
Console.WriteLine(translated.translatedList.GetHashCode());
}
}
}
UPDATE: Changed service call to the following linq-to-sql call: dataContext.OriginalObjects.Where(o => o.id == 1 || o.id == 2 || o.id == 3).

Categories

Resources