Need help creating complex ef linq select - c#

Hello i am having some problems creating an complex Linq with multible joins and left joins.
I have 4 tables as listed in the end, what this is used for is to see how i need to send an email about a new reply to a topic. So i fetch all Posts from the topic joined by the user. The same user can ofcause have more then 1 post in each topic.
After that i join with UserEmailSetting, this is to see if the user have oped out reciving email notifications. Lastly i need to know if an email have been send to the user notifying about the new reply (I don't want to spam my users if there are made a lot of replys), so if an reply notification have been sendt since the last visit to the site I don't want to send another mail. Here is my try that works, but i would like to optimate it!
The problem is that there can be many results on the UserEmailSetting, so i get alot os results back when i infact only get 1 or 2 back.
here is my attept
var select = (from p in ForumPostRepository.Get()
join u in UserRepository.Get() on p.UserId equals u.Id
join ues in UsersEmailSettingRepository.Get() on u.Id equals ues.UserId
join els in
(from _el in EmailLogRepository.Get()
where _el.Type == "ReplyToTopic" &&
_el.Values == topicId
orderby _el.Id descending
select _el) on u.Id equals els.UserId
into emailLogs
from el in emailLogs.DefaultIfEmpty()
where p.TopicId == forumTopic.Id &&
ues.ReplyToTopic //&& // We only want people who need notifications
//!u.Online // We only want people who are not online
orderby p.Id descending, el.Id descending
select new
{
User = u,
EmailLog = el
});
var result = select.DistinctBy(x => x.User.Id).ToList();
Here are the database classes
public class ForumPost
{
public int? TopicId { get; set; }
public int UserId { get; set; }
...
}
public class User
{
public int Id { get; set; }
public bool Online { get; set; }
public DateTime LastLogin { get; set; }
...
}
public class UsersEmailSetting
{
public int UserId { get; set; }
public bool ReplyToTopic { get; set; }
}
public class EmailLog
{
public int Id { get; set; }
public int UserId { get; set; }
public string Type { get; set; }
public string Values { get; set; }
public DateTime Created { get; set; }
}
Updata: a bit more structured layout of what i want the linq to do, I hope It helps
Get all posts from ForumPostRepository where topicId is 13
Join with UserRepository on ForumPostRepository.UserId = UserRepository.Id
Now I only want unike users
Join with UsersEmailSettingRepository on UserRepository.Id =
UsersEmailSettingRepository.UserId
Left join with EmailLogRepository on UserRepository.Id =
EmailLogRepository.UserId AND EmailLogRepository.Type =
“ReplyToTopic” AND EmailLogRepository.Values = “topicId”
-> Now there can be anywhere from 0 to * results on this request, I only want the latest!

No guarantees that this will be performant.
var users = UserRepository.Get();
var userEmailSettings = UsersEmailSettingRepository.Get();
var emailLogs = EmailLogRepository.Get();
var posts = ForumPostRepository.Get();
var foo =
from user in users
where posts
.Any(post => post.UserId == u.Id && post.TopicId == topicId)
where userEmailSettings
.Any(ues => u.Id equals ues.UserId && ues.ReplyToTopic == true)
where false ==
(
from el in emailLogs
where el.Type == "ReplyToTopic"
&& el.Values == topicId
&& el.UserId == user.Id
&& el.Created > user.LastLogin
select el
).Any()
select user;
Signed
A Linq Ninja
edit: just saw that you don't have navigation properties.

Related

LINQ uses extra inner join

Here are my simplified models:
public class Resource
{
public int id { get; set; }
public string Name { get; set; }
}
public class Upgrade
{
public int Id { get; set; }
public virtual Resource Res { get; set; }
public int Lvl { get; set; }
public int Amount { get; set; }
}
Basically, I need to group by Resource and get [Name] from "Resource" and sum([Amount]) from "Upgrade".
Here is the LINQ:
from u in _db.Upgrades
join r in _db.Resources on u.Res equals r
where u.Lvl > levelFrom
&& u.Lvl <= levelTo
group u by new { r.id, r.Name } into grp
select new UpgradeCost()
{
resName = grp.Key.Name,
resAmount = grp.Sum(k => k.Amount),
};
And here is the SQL I get (Sqlite):
SELECT "r0"."Name" AS "resName", COALESCE(SUM("u"."Amount"), 0) AS "resAmount"
FROM "Upgrades" AS "u"
LEFT JOIN "Resources" AS "r" ON "u"."Resid" = "r"."id"
INNER JOIN "Resources" AS "r0" ON "r"."id" = "r0"."id"
WHERE ("u"."Lvl" > #__levelFrom_0) AND ("u"."Lvl" <= #__levelTo_1)
GROUP BY "r0"."id", "r0"."Name"
LINQ uses extra INNER JOIN to group by.
I want it to be made like this:
SELECT "r"."Name" AS "resName", COALESCE(SUM("u"."Amount"), 0) AS "resAmount"
FROM "Upgrades" AS "u"
LEFT JOIN "Resources" AS "r" ON "u"."Resid" = "r"."id"
WHERE ("u"."Lvl" > #__levelFrom_0) AND ("u"."Lvl" <= #__levelTo_1)
GROUP BY "r"."id", "r"."Name"
Additional join generated when used u.Res navigation property. Actually you don't need explicit joins here.
from u in _db.Upgrades
where u.Lvl > levelFrom
&& u.Lvl <= levelTo
group u by new { u.Res.id, u.Res.Name } into grp
select new UpgradeCost()
{
resName = grp.Key.Name,
resAmount = grp.Sum(k => k.Amount),
};

LINQ to a model including List

I would like to get your advice on Linq.
I have 3 tables users ( listing all the users), another one userEtablishments (one user can have many places), and the table Establishment (to list the place)
i have a models which is:
public class UserVM {
public Int32 id { get; set; }
public string name { get; set; }
public List<Etablishment> Etablishments { get; set; }
}
and the Etablishment:
public class EtablishmentVM {
public Int32 id { get; set; }
public string name { get; set; }
}
I query first the users, then establishments of the user, but its very slow:
UserVM obj = new UserVM();
using (context db = new context(ConnectionString))
{
userlist = (from u in db.users
join p in db.establishments on u.id equals p.id
join pe in db.userEtablishments on p.id equals pe.id
select new
{
u,
}).FirstOrDefault();
if (entity != null)
{
obj.id = entity.u.id);
obj.name = entity.u.name);
// Then get the establishment list then update the establishmentVM
List<EstablishmentVM> userplaces = new List<EstablishmentVM>();
var userplacesList = (from u in db.users join p in db.establishments on
u.id equals p.id join pe in db.userEtablishments on p.id equals pe.id
select new { u, pe }).ToList();
if (userplacesList != null && userplacesList.Count > 0)
{
foreach (var item in userplacesList)
{ }.....
is there a better way to do to get it execute faster ?
Any advices would helps, the purpose is to get betterperformances.
Thanks for your helps

Use a filter in an outer join in linq

I have the following entities:
public class Company
{
public string CompanyName { get; set; }
public int ID { get; set; }
}
public class CompanyCurrency
{
public int Id { get; set; }
public int CompanyId { get; set; }
public decimal Rate { get; set; }
public int CurrencyId { get; set; }
}
public class Currency
{
public int ID { get; set; }
public string Name { get; set; }
}
I need to get the list of currencies for a country. If a country does not have an entry for a currency I need a line for that missing entry too.
The statement I have right now is:
var currencies =
from c in Currencies
join cc in CompanyCurrency
on c.ID equals cc.CurrencyId
into jointable
from resultiten in jointable.DefaultIfEmpty()
select new {c.Name ,
HasEntry = resultiten == null ? 0:1,
rate = resultiten != null ? resultiten.Rate:0 ,
} ;
This is not filtered by a countryID . I tried to add a filter by
from c in Currencies
join cc in CompanyCurrency
on c.ID equals cc.CurrencyId
into jointable
from resultiten in jointable.DefaultIfEmpty()
where resultiten.CompanyId == 1 || resultiten == null
select new {c.Name ,
HasEntry = resultiten == null ? 0:1,
rate = resultiten != null ? resultiten.Rate:0
But that does not have a result for a currency that has en entry for a company other then companyID 1.
The cooresponding SQL query would be
select *
from [dbo].[Currency] c
left outer join [dbo].[CompanyCurrency] cc
on c.id = cc.Currencyid
and cc.[Companyid] = 1
You need to either apply the filter before the join:
join cc in CompanyCurrency.Where(e => e.CompanyId == 1)
or as part of the join
on new { CurrencyId = c.ID, CompanyId = 1 } equals new { cc.CurrencyId, cc.CompanyId }
For inner joins it doesn't really matter, but for outer join it's important (the same btw applies to SQL queries).

Linq Query with join on subquery

Basically I'm trying to write a query where it joins on select top 1 from a second table so something like:
SELECT Sum(pinfo.quantity + p.itemcount),
i.owner
FROM invoice i
JOIN purchase_info pinfo
ON pinfo.invoice = i.invid
JOIN (SELECT DISTINCT sku,
productlineid,
itemcount
FROM products WHERE productlineid in (13, 14)) p
ON p.sku = pinfo.item
WHERE i.owner = 22623
GROUP BY i.owner
Here's my pathetic attempt in linq that has somewhat invalid syntax, any ideas would be much appreciated.
(from i in _invoiceRepository.Table
join pi in _purchaseInfoRepository.Table on i.InvoiceId equals pi.InvoiceId
join p in (from p2 in _productRepository.Table where p2.Sku == pi.Item select new { p2.Sku, p2.ItemCount }).Take(1)
on pi.Item equals p.Sku
where i.MemberId == memberId &&
(p.ProductLineId == (int)ProductLines.InkCartridges ||
p.ProductLineId == (int)ProductLines.TonerCartridges)
select pi.Quantity * p.ItemCount)
.DefaultIfEmpty(0)
.Sum();
Here is my first stab at this.
From the sql, it looks like you want to find how many Ink and Toner Cartridges a particular customer has ordered from you ever.
This should give you the same results as the sql (this is depending on the order of the Products table since we are taking the top 1 without some sort of ordering being done:
var count = from i in _invoiceRepository.Table
where i.OwnerId == memberId
select new
{
OwnerId = i.OwnerId,
TotalProductCount = i.Purchases.Sum(pro => pro.Products
.Where(p => p.ProductLineId == (int)ProductLines.InkCartridges ||
p.ProductLineId == (int)ProductLines.TonerCartridges)
.Take(1)
.Sum(p => p.ItemCount * pro.Quantity))
};
Since I did not know the the classes of the three objects (Invoice, PurchaseInfo, and Product) I made a guess at what they are:
Invoice Class: I assume it has a list/collection of PurchaseInfos
public class Invoice
{
public int Id { get; set; }
public int OwnerId { get; set; }
public List<PurchaseInfo> Purchases { get; set; }
}
PurchaseInfos: An invoice has multiple PurchaseInfos, each one links to (ideally) one product but since the SKU is not unique I assome that this has a list/collection of Products in it.
public class PurchaseInfo
{
public int Id { get; set; }
public int Quantity { get; set; }
public int InvoiceId { get; set; }
public Invoice Invoice { get; set; }
public int Item {get;set;}
public List<Product> Products { get; set; }
}
Product Class: I assome that there is an Id field (not shown) or a composite primary key somewhere
public class Product
{
public int Sku { get; set; }
public int ProductLineId { get; set; }
public int ItemCount { get; set; }
public List<PurchaseInfo> PurchaseInfos { get; set; }
}
Hopefully you can take this a get what you need. If this is way off, please update question with the class definitions (you can remove unneeded properities if you want) so a better answer can be produced.

Entity Framework LINQ Get all items part of another collection

Get all the NWatchRelation records from the DBContext that overlap those in the relationsCollection.
The same Id, RelatedNodeId, and RelationType (enum: int) should be what's considered a match.
public class NWatchRelation : INWatchRelation
{
public int Id { get; set; }
public int NodeId { get; set; }
public NWatchNode Node { get; set; }
public int RelatedNodeId { get; set; }
public NWatchNode RelatedNode { get; set; }
public NWatch.NWatchRelationType RelationType { get; set; }
}
INWatchRelation[] relationsCollection = GetRelations();
You can do a LINQ join between these 2 collections.
var result = from a in db.NWatchRelations.AsEnumerable()
join b in relationsCollection on a.RelatedNodeId equals b.RelatedNodeId
&& a.Id equals b.Id
&& a.RelationType equals b.RelationType
select a;
The only way you can do that fully in LINQ to Entities is to manually compose UNION ALL query by using Queryable.Concat like this:
IQueryable<NWatchRelation> query = null;
foreach (var relation in relationsCollection)
{
var m = relation;
var subQuery = db.NWatchRelations
.Where(r => r.Id == m.Id
&& r.RelatedNodeId == m.RelatedNodeId
&& r.RelationType == m.RelationType);
query = query == null ? subQuery : query.Concat(subQuery);
}
But please note that it's a limited approach and will not work if the relationsCollection is big.
You could create a kind of unique key using the three values:
//To create a unique key (an string, which is a primitive type) combining the three values
var keys=relationsCollection.Select(e=>e.Id+"-"+e.RelatedNodeId+"-"+ ((int)e.RelationType)).Distinct();
var query=db.NWatchRelations.Where(r=>keys.Any(k=>k == (SqlFunctions.StringConvert((double)r.Id)+"-"+
SqlFunctions.StringConvert((double)r.RelatedNodeId )+"-"+
SqlFunctions.StringConvert((double)((int)r.RelationType)) ));
If your NWatchRelations table doesn't have many rows or relationsCollection is a small collection, please, use one of the alternatives that were proposed earlier at your convinience.
Also you can have the directly linked like this
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public NWatchRelation()
{
this.INWatchRelation = new HashSet<INWatchRelation>();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<INWatchRelation> INWatchRelation { get; set; }
But the entiry relation must be liked like this in order to work properly
Then you could select/list it like this
db.NWatchRelation.INWatchRelation.ToList();

Categories