Force inner join with many-to-many relationship entity framework - c#

I have a many-to-many relationship setup in my database like so:
User
-------
Id (PK, Identity)
First
Last
...various other fields
Skill
-------
Id (PK, Identity)
Description
UserSkill
-----------
UserId (PK, FK on User.Id)
SkillId (PK, FK On Skill.Id)
When I run this LINQ query on the DbContext:
from u in Users
from s in u.Skills
where s.Id == 5
select new
{
u.Id,
s.Description
})
The SQL generated contains all inner joins which is what I want:
SELECT
[Extent1].[UserId] AS [UserId],
[Extent2].[Description] AS [Description]
FROM [dbo].[UserSkill] AS [Extent1]
INNER JOIN [dbo].[Skill] AS [Extent2] ON [Extent1].[SkillId] = [Extent2].[Id]
WHERE 5 = [Extent2].[Id]
However, when I add a simple extra where clause:
from u in Users
from s in u.Skills
where s.Id == 5
&& u.Last == "test"
select new
{
u.Id,
s.Description
})
The SQL generated now uses a sub-query:
[Extent1].[Id] AS [Id],
[Filter1].[Description] AS [Description]
FROM [dbo].[User] AS [Extent1]
INNER JOIN (SELECT [Extent2].[UserId] AS [UserId], [Extent3].[Description] AS [Description]
FROM [dbo].[UserSkill] AS [Extent2]
INNER JOIN [dbo].[Skill] AS [Extent3] ON [Extent3].[Id] = [Extent2].[SkillId]
WHERE 5 = [Extent3].[Id] ) AS [Filter1] ON [Extent1].[Id] = [Filter1].[UserId]
WHERE 'test' = [Extent1].[Last]
Maybe I am missing something, but I would think EF would just add another join back to the User table for this query and be able to do a where on User.Last instead of doing a sub-query. Is there any way to force this kind of behavior? Am I doing something wrong?
Thanks.
UPDATE
Cosmin, I am wanting the query to come out like this:
SELECT u.Id, s.Description
FROM [User] u INNER JOIN
[UserSkill] us ON u.Id = us.UserId INNER JOIN
[Skill] s ON us.SkillId = s.Id
WHERE s.Id = 2 AND u.Last = 'test'

Looks like this is an optimization that EF does not currently do. Personally, I'd stick with the sub query it generates unless performance becomes a problem.
But if you are willing to lose the direct navigation properties for User and Skill, you can model the intermediate table to get the query you are looking for.
public class User
{
public int Id { get; set; }
public string First { get; set; }
public string Last { get; set; }
public virtual ICollection<UserSkill> UserSkills { get; set; }
}
public class UserSkill
{
public int Id { get; set; }
[Required]
public User User { get; set; }
[Required]
public Skill Skill { get; set; }
}
public class Skill
{
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<UserSkill> UserSkills { get; set; }
}
Then the following query will produce a join instead of subquery
from x in db.UserSkills
where x.Skill.Id == 5 && x.User.Last == "test"
select new {x.User.Id, x.Skill.Description};

#ryanulit, your issue is valid and it is an issue for all Linq to Entities. Please check the posted MS Forum's URL
MS Forum's URL

Related

Casting a Select to get a property from .Last() in a collection

I have a complex object with many children where I'm trying to only select a few properties from it and its children for displaying in a grid. The way the query was structured before with blindly tossing in every .Include() that was needed generated a SQL statement that is 1095 lines long.
I have no issue with getting single properties from a child object, however the one is the name of the last activity that was performed. Doing a .Last().Name on the collection tosses an Exception that it can't be converted to SQL. I'll make a basic example to help visualize (all FKs are actually set in my code, that's not the problem) :
public class Foo
{
public int Id { get; set; }
// just a dummy class everyone knows for illustration
public Address Address { get; set; }
public ICollection<Activity> Activities { get; set; }
}
public class Activity
{
public string Name { get; set; }
}
public class FooModel
{
public int Id { get; set; }
public string StreetName { get; set; }
public string LastActivity { get; set; }
}
This is a basic example of the query I'm setting up:
public IEnumerable<FooModel> GetHomePageItems(IEnumerable<int> fooIds)
{
return await context.Foos
.Where(f => fooIds.Contains(f.id))
.Select(f => new FooModel
{
Id = f.Id,
StreetName = f.Address.Street,
// here is the problem as it can't
// convert this to SQL
LastActivity = f.Activities.Last().Name
})
.ToListAsync();
}
Is this something that cane be done, or do I have to pull everything in without the LastActivity, and then query for the activities with a GroupBy and get them that way?
You can try changing your query to:
var query = context.Foos
.Where(f => fooIds.Contains(f.Id))
.Select(f => new FooModel
{
Id = f.Id,
StreetName = f.Address.Street,
LastActivity = f.Activities.OrderByDescending(x => x.Id).FirstOrDefault().Name
}).ToListAsync();
That linq generates the following sql for Entity Framework version 6.1.3:
SELECT
[Filter1].[Id1] AS [Id],
[Filter1].[Street] AS [Street],
[Limit1].[Name] AS [Name]
FROM (SELECT [Extent1].[Id] AS [Id1], [Extent2].[Street] AS [Street]
FROM [dbo].[Foos] AS [Extent1]
LEFT OUTER JOIN [dbo].[Addresses] AS [Extent2] ON [Extent1].[Address_Id] = [Extent2].[Id]
WHERE [Extent1].[Id] IN (1, 2, 3, 4) ) AS [Filter1]
OUTER APPLY (SELECT TOP (1) [Project1].[Name] AS [Name]
FROM ( SELECT
[Extent3].[Id] AS [Id],
[Extent3].[Name] AS [Name]
FROM [dbo].[Activities] AS [Extent3]
WHERE [Filter1].[Id1] = [Extent3].[Foo_Id]
) AS [Project1]
ORDER BY [Project1].[Id] DESC ) AS [Limit1]
Which might be enough for your project. On large amounts of data, though, you might have to switch to something faster, even probably to manual query using .Sql() method.
Entity Framework does not recognize .LastOrDefault() and .Last() methods, instead you should use .FirstOrDefault() or .First() methods coupled with OrderBy() or OrderByDescending().

Linq query with many to many relations using entity framework

I have two tables: tbA and tbB .Between them I have a relationship n to n, so a table tbAB was generated in the database. I am using an Entity Framework Database First, then when I mapped these tables , it does not generate a specific entity for tbAB. Thus , I'm not seeing how I can create a query relating the two tables if I can't call directly thetbAB.
What I want to do in SQL would be as follows :
SELECT *
FROM tbA
INNER JOIN tbAB
ON tbAB.idA = tbA.idA
INNER JOIN tbB
ON tbB.idB = tbAB.idB
That's what I'm trying to do with Linq:
var table = (from l in db.tbA
join k in db.tbB on l.? equals k.?
where ?.IDCONCESSAO == objectTbB.IDCONCESSAO
select l).ToList();
The question is how can I do this in a Linq expression ?
Thanks in advance.
Following the model proposed by #Michal, you could do this:
var query= from a in db.TableAs
from b in a.TableBs
where b.Id==10
select new{A_Id=a.Id,a.Name, B_Id=b.Id,b.Price,...};
In the select you can choose the properties you need from both entities(I also select a Name from TableA and a Price from TableBto help you understand better this example).From each direction of the relationship, you don’t ever interact with the junction table, you just follow a relationship from each direction as if it were a one-to-many. The query that I show above will be translated in a sql query where the joins between the tables will be made this way:
{SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Join1].[Id] AS [Id1],
[Join1].[Price] AS [Price]
FROM [dbo].[TableAs] AS [Extent1]
INNER JOIN (SELECT [Extent2].[TableA_Id] AS [TableA_Id], [Extent3].[Id] AS [Id], [Extent3].[Price] AS [Price]
FROM [dbo].[TableBTableAs] AS [Extent2]
INNER JOIN [dbo].[TableBs] AS [Extent3] ON [Extent3].[Id] = [Extent2].[TableB_Id] ) AS [Join1] ON [Extent1].[Id] = [Join1].[TableA_Id]
WHERE 10 = [Join1].[Id]}
public void Test()
{
var db = new DbContext();
// This will automatically do you inner join for you.
db.TableAs.Include(a => a.TableBs);
}
Context:
public class DbContext
{
public IDbSet<TableA> TableAs { get; set; }
public IDbSet<TableB> TableBs { get; set; }
}
Models:
public class TableA
{
public int Id { get; set; }
public virtual List<TableB> TableBs { get; set; }
}
public class TableB
{
public int Id { get; set; }
public virtual List<TableA> TableAs { get; set; }
}
var table = from a in db.tbA
join ab in db.tbAB on a.idA equals ab.idA
join b in db.tbB on ab.idB equals b.idB
where a.Anything = 10
select a;
var results = table.ToList();

EF does not generate expected SQL

I'm using EF 6.1.2-beta1 code first in my program and have following model in my project(my database generated by EF itself, too):
public class Document
{
public int Id { get; set; }
public AppUser Creator { get; set; }
public AppUser Modifier { get; set; }
}
public class AppUser
{
public int Id { get; set; }
public string UserId { get; set; }
public Party Party { get; set; }
}
public class Party
{
public int Id { get; set; }
public string Name { get; set; }
}
I want to write a linq to entities query to find Creator.Name and Modifier.Name of first Document(for simplicity I supposed that I want to find the first Document). So I wrote following code:
var result = context.Documents.Select(d =>
new{
d.Id,
CreatorName = d.Creator.Party.Name,
ModifierName = d.Modifier.Party.Name,
}).FirstOrDefault();
EF generate following SQL for above query:
SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent3].[Name] AS [Name]
FROM [dbo].[Documents] AS [Extent1]
LEFT OUTER JOIN [dbo].[AppUser] AS [Extent2]
ON [Extent1].[Creator_Id] = [Extent2].[AppUserId]
LEFT OUTER JOIN [dbo].[Parties] AS [Extent3]
ON [Extent2].[Party_Id] = [Extent3].[Id]
But, as you see, above SQL has only one join to Party table and so it get only Creator.Name.
[Updated]
You can get my test project source code from here
Dos anyone know where is the problem?
As soon as downgrading to EF 6.1.1 solves this problem (generated SQL and results are correct) - I think this is bug in EF 6.1.2 beta.
For example this command
var result = context.Documents.Select(d =>
new
{
d.Id,
Creator = d.Creator,
CreatorParty = d.Creator.Party,
CreatorPartyName = d.Creator.Party.Name,
Modifier = d.Modifier,
ModifierParty = d.Modifier.Party,
ModifierPartyName = d.Modifier.Party.Name
}).FirstOrDefault();
Generates SQL
SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent2].[Id] AS [Id1],
[Extent2].[UserId] AS [UserId],
[Extent2].[Party_Id] AS [Party_Id],
[Extent3].[Id] AS [Id2],
[Extent3].[Name] AS [Name],
[Extent4].[Id] AS [Id3],
[Extent4].[UserId] AS [UserId1],
[Extent4].[Party_Id] AS [Party_Id1]
FROM [dbo].[Documents] AS [Extent1]
LEFT OUTER JOIN [dbo].[AppUsers] AS [Extent2] ON [Extent1].[Creator_Id] = [Extent2].[Id]
LEFT OUTER JOIN [dbo].[Parties] AS [Extent3] ON [Extent2].[Party_Id] = [Extent3].[Id]
LEFT OUTER JOIN [dbo].[AppUsers] AS [Extent4] ON [Extent1].[Modifier_Id] = [Extent4].[Id]
There is no second Parties join from Extent4 (second AppUsers)
[Update]
Thanks to Masoud (in comments) - this bug in EF had been fixed on Oct 20.
Try multiple joins instead.
var q = from d in context.Documents
join uCreator in context.AppUsers on d.Creator.Id equals uCreator.Id
join uModifier in context.AppUsers on d.Modifier.Id equals uModifier.Id
select new {
Id = d.Id,
Creator = uCreator.Party.Name,
Modifier = uModifier.Party.Name
};
That query does not match the anonymous type you are giving, so I think that is just executed when you are referring to the d.Creator.Party.Name and you stop logging there? Or is there multiple queries executed?
Try following maybe?
var doc = (from d in context.Documents
.Include("Creator.Party").Include("Modifier.Party")
select new
{
Id = d.Id,
CreatorName = d.Creator.Party.Name,
ModifierName = d.Modifier.Party.Name
}).FirstOrDefault();

convert Sql with inner select to Linq

How to convert this Sql query to LinQ
SELECT *
FROM
users
JOIN products
ON users.id = products.user_id
WHERE users.id IN (
SELECT id
FROM users
WHERE users.status = 'Online'
)
Well, your sql is too complicated (the subquery is useless), so simplify it, and you'll get an easy linq...
from u in users
join p in products on u.id equals p.user_id
where u.status == "Online"
select new{u, p};
your sql could just be
select *
from users u
join products p on u.id = p.user_id
where u.status = 'Online'
You don't need an inner SELECT at all
SELECT *
FROM
users
JOIN products
ON users.id = products.user_id
WHERE
users.status = 'Online'
Now, converting this to a LINQ query is a piece of cake.
Note: You would need an IN-clause with a nested SELECT if you were querying another table that, when joined, would produce more output rows, because it has an 1 : n relation to one of the tables already included in the query.
I assume you have these entities:
class User {
public int Id { get; set; }
public string Status { get; set; }
public IList<Product> Products { get; set; }
}
class Product {
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
which there is a one-to-many relationship from user to product. So we have:
var result =
from user in users
join product in products on user.Id equals product.UserId
where user.Status == "online"
select new { user, product };

Entity Framework is executing too many queries

I need to find distinct Campaigns of a particular user. A User has CodeRights, CodeRights contain Codes and Codes contain Campaign. Here is the CodeRight class
public class SmartCodeRight
{
[Key, Column(Order = 1)]
public long UserId { get; set; }
public virtual User User { get; set; }
[Key, Column(Order = 2)]
public long CodeId { get; set; }
public virtual SmartCode Code { get; set; }
public CodeRight CodeRight { get; set; }
}
I would write the following SQL for this:
SELECT *
FROM campaigns
WHERE campaignid IN (SELECT DISTINCT campaignid
FROM smartcodes t1
INNER JOIN smartcoderights t2 ON t1.codeId = t2.codeId
WHERE t2.userid = #userId)
Using EF I am writing this code:
var v = user.CodeRights.Select(r => r.Code.Campaign).Distinct().ToList();
Now on profiling I am seeing that EF is executing 2 SQL queries for every CodeRight present.
I have also calculated the time of entire execution and EF takes ~400 ms while using ADO.Net it's only ~8.
Now my question is that if EF is really this slow or I'm doing something wrong?
Edits
Following two blocks are being executed for every CodeRight
exec sp_executesql N'SELECT
[Extent1].[CodeId] AS [CodeId],
[Extent1].[CodeTitle] AS [CodeTitle],
[Extent1].[CodeContent] AS [CodeContent],
[Extent1].[CreatedOn] AS [CreatedOn],
[Extent1].[IsActive] AS [IsActive],
[Extent1].[Deleted] AS [Deleted],
[Extent1].[OwnerId] AS [OwnerId],
[Extent1].[Tags] AS [Tags],
[Extent1].[CampaignId] AS [CampaignId]
FROM [dbo].[SmartCodes] AS [Extent1]
WHERE [Extent1].[CodeId] = #EntityKeyValue1',N'#EntityKeyValue1 bigint',#EntityKeyValue1=24
go
and
exec sp_executesql N'SELECT
[Extent1].[CampaignId] AS [CampaignId],
[Extent1].[Name] AS [Name],
[Extent1].[Description] AS [Description],
[Extent1].[AdminId] AS [AdminId]
FROM [dbo].[Campaigns] AS [Extent1]
WHERE [Extent1].[CampaignId] = #EntityKeyValue1',N'#EntityKeyValue1 int',#EntityKeyValue1=12
go
You should spend time looking at Fetch Plans for Entity Framework. In order for EF to perform the join, you'll have to use the Include keyword.
It'll have to be part of your initial query when you get your user object:
var user = context.Users
.Include("CodeRights.Code")
.Include("CodeRights.Campaign")
.FirstOrD‌​efault(u => u.Id == id);

Categories

Resources