Linq query with many to many relations using entity framework - c#

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();

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().

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();

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

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

EF Reduce Outer Joins for Nested Types

I am having trouble with my configurations for one of my projects and the SQL EF is generating seems to be showing some unexpected (to me, at least) nesting. This question will be a little long-winded but bear with me please.
So, say I have the following set up:
public class Order
{
public int Id { get; set; }
public decimal Amount { get; set; }
public Address ShippingAddress { get; set; }
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
with the entities configured as such:
public class OrderConfiguration : EntityTypeConfiguration<Order>
{
public OrderConfiguration()
{
ToTable("Orders");
HasRequired(order => order.ShippingAddress)
.WithRequiredDependent()
.Map(mapping => mapping.MapKey("ShippingAddressId"));
}
}
public class PersonConfiguration : EntityTypeConfiguration<Person>
{
public PersonConfiguration()
{
ToTable("Persons");
HasRequired(person => person.Address)
.WithRequiredDependent()
.Map(mapping => mapping.MapKey("AddressId"));
}
}
My goal is to get the orders with the addresses included, so I run a simple query such as:
var orders = context.Orders
.Include(order => order.ShippingAddress)
.Where(order => order.ShippingAddress.State == "FL")
.ToList();
As I monitor SQL Profiler, I am expecting to see generated SQL which selects from the Orders table and doing an inner join on the Address table
SELECT * FROM [dbo].[Orders] AS [Extent1]
INNER JOIN [dbo].[Addresses] AS [Extent2] ON [Extent1].[AddressId] = [Extentd2].[Id]
WHERE [Extent2].[State] == 'FL'
instead, I am finding this generated:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Amount] AS [Amount],
[Join5].[Id1] AS [Id1],
[Join5].[StreetAddress] AS [StreetAddress],
[Join5].[City] AS [City],
[Join5].[State] AS [State],
[Join5].[ZipCode] AS [ZipCode],
[Join8].[Id2] AS [Id2],
[Join11].[Id3] AS [Id3]
FROM [dbo].[Orders] AS [Extent1]
INNER JOIN (SELECT [Extent2].[State] AS [State], [Extent3].[Id] AS [Id4]
FROM [dbo].[Addresses] AS [Extent2]
LEFT OUTER JOIN [dbo].[Orders] AS [Extent3] ON [Extent2].[Id] = [Extent3].[ShippingAddressId]
LEFT OUTER JOIN [dbo].[Persons] AS [Extent4] ON [Extent2].[Id] = [Extent4].[AddressId] ) AS [Join2] ON [Extent1].[Id] = [Join2].[Id4]
LEFT OUTER JOIN (SELECT [Extent5].[Id] AS [Id1], [Extent5].[StreetAddress] AS [StreetAddress], [Extent5].[City] AS [City], [Extent5].[State] AS [State], [Extent5].[ZipCode] AS [ZipCode], [Extent6].[Id] AS [Id5]
FROM [dbo].[Addresses] AS [Extent5]
LEFT OUTER JOIN [dbo].[Orders] AS [Extent6] ON [Extent5].[Id] = [Extent6].[ShippingAddressId]
LEFT OUTER JOIN [dbo].[Persons] AS [Extent7] ON [Extent5].[Id] = [Extent7].[AddressId] ) AS [Join5] ON [Extent1].[Id] = [Join5].[Id5]
LEFT OUTER JOIN (SELECT [Extent9].[Id] AS [Id2]
FROM [dbo].[Addresses] AS [Extent8]
LEFT OUTER JOIN [dbo].[Orders] AS [Extent9] ON [Extent8].[Id] = [Extent9].[ShippingAddressId]
LEFT OUTER JOIN [dbo].[Persons] AS [Extent10] ON [Extent8].[Id] = [Extent10].[AddressId] ) AS [Join8] ON [Extent1].[Id] = [Join8].[Id2]
LEFT OUTER JOIN (SELECT [Extent12].[Id] AS [Id6], [Extent13].[Id] AS [Id3]
FROM [dbo].[Addresses] AS [Extent11]
LEFT OUTER JOIN [dbo].[Orders] AS [Extent12] ON [Extent11].[Id] = [Extent12].[ShippingAddressId]
LEFT OUTER JOIN [dbo].[Persons] AS [Extent13] ON [Extent11].[Id] = [Extent13].[AddressId] ) AS [Join11] ON [Extent1].[Id] = [Join11].[Id6]
WHERE N'FL' = [Join2].[State]
Obviously, this is a lot more work than needs to be done. Is there an easier way to do this or do I have the entities configured incorrectly? I do not understand why it is pulling back all of these extra joins, or how I can limit them just to the tables I need right then. Any insight would be tremendously appreciated!
Thanks,
Derek
As you have noticed .Include() can sometimes generate horrendous SQL!
I have found the easiest solution in this situation is to explicitly load the required related entities directly after the initial load. The downside to this is the additional database calls - it will call once for each order.
var orders = (
from order in context.Orders
where order.ShippingAddress.State == "FL"
select order)
.ToList();
var t1 = (
from order in orders
from address in order.ShippingAddress
select address)
.ToList();

Entity Framework generates a second left join in a one-to-one (optional/required) relationship

I have the following models in EF Code First:
public class A
{
public int Id { get; set; }
public virtual B { get; set; }
}
public class B
{
public int Id { get; set; }
public virtual A { get; set; }
}
I have defined the relationship as follows:
modelBuilder.Entity<A>().HasKey(entity => entity.Id);
modelBuilder.Entity<B>().HasKey(entity => entity.Id);
modelBuilder.Entity<A>()
.HasOptional(entity => entity.B)
.WithRequired(entity => entity.A);
When I write the following query:
var a = db.AItems.Include("B");
The query that is produced is as follows:
SELECT
[Extent1].[Id] AS [Id],
[Extent3].[Id] AS [Id1]
FROM [dbo].[As] AS [Extent1]
LEFT OUTER JOIN [dbo].[Bs] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Id]
LEFT OUTER JOIN [dbo].[Bs] AS [Extent3] ON [Extent2].[Id] = [Extent3].[Id]
Why does Entity Framework have an additonal (useless) left join for this type of relationship?
First welcome to the exciting world(!) of entity framework which is full of surprises. I have faced with this situation before; when you have one to one relationships, even if you do not explicitly include the related entity, entity framework creates a join statement.
In your case, first join results from the include statement and the other left outer join is added by default. You can check this by removing include statement and observing the sql output which will include the left outer join statement.

Categories

Resources