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

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

Related

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

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

Keep list of foreign keys in many-to-many Entity Framework relationship

I have a many-to-many relationship in my code-first Entity Framework model. Imagine we have two tables, "Company" and "Article", that have such relationship in between. My simplified code model looks like the following:
public class Article
{
public int Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
Using fluent mapping, I create many-to-many relationship:
modelBuilder.Entity<Company>().HasMany(c => c.Articles).WithMany(a => a.Companies);
This works fine, EF creates intermediate table. But, according to my logic, it would be very nice to have a collection of foreign keys together with each entity. It means I would like to add the following property to corresponding model classes:
public virtual ICollection<int> ArticlesId { get; set; } // to Company
public virtual ICollection<int> CompaniesId { get; set; } // to Article
I know that one workaround solution is to create intermediate table's model in EF and manually select appropriate IDs in every call, but maybe EF mapping can provide more convenient way to do such operation? Thank you in advance for any tips.
It seems that unfortunately there is no way to map IDs in the way I want. However, here are three workarounds of how to implement retrieval of required entity keys.
First solution, suggested by Ben Reich.
Implement get-only property, that will return only IDs of linked entities.
public class Company
{
public virtual ICollection<Article> Articles { get; set; }
public IEnumerable<int> ArticlesIds
{
get { return Articles.Select(a => a.Id); }
}
}
It seems to be convient for using, however, it has a disadvantage - whole entity will be read from database in order to receive the only ID. Here is log of such call from SQL Profiler:
exec sp_executesql N'SELECT
[Extent2].[Id] AS [Id],
[Extent2].[Header] AS [Header],
[Extent2].[Description] AS [Description],
[Extent2].[Text] AS [Text],
[Extent2].[CreationDate] AS [CreationDate],
[Extent2].[AccountId] AS [AccountId],
[Extent2].[ImageSetId] AS [ImageSetId]
FROM [dbo].[CompanyArticles] AS [Extent1]
INNER JOIN [dbo].[Articles] AS [Extent2] ON [Extent1].[Article_Id] = [Extent2].[Id]
WHERE [Extent1].[Company_Id] = #EntityKeyValue1',N'#EntityKeyValue1 int',#EntityKeyValue1=1
Second solution.
Using the same model, read IDs separately after reading of the entity.
var ids = db.Set<Article>().Where(a => a.Companies.Select(c => c.Id).Contains(f.Id)).ToList();
This approach works quite the same as the previous one, whole entity set will be fetched.
exec sp_executesql N'SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Header] AS [Header],
[Extent1].[Description] AS [Description],
[Extent1].[Text] AS [Text],
[Extent1].[CreationDate] AS [CreationDate],
[Extent1].[AccountId] AS [AccountId],
[Extent1].[ImageSetId] AS [ImageSetId]
FROM [dbo].[Articles] AS [Extent1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[ArticleCompanies] AS [Extent2]
WHERE ([Extent1].[Id] = [Extent2].[Article_Id]) AND ([Extent2].[Company_Id] = #p__linq__0)
)',N'#p__linq__0 int',#p__linq__0=1
Third solution. The most appropriate, from my point of view.
Create entity class for your intermediate table.
public class ArticleCompany
{
public int CompanyId { get; set; }
public int ArticleId { get; set; }
public virtual Company Company { get; set; }
public virtual Article Article { get; set; }
}
Map both entities with this entity as 1-to-m relationship. Don't forget to map the new entity itself.
modelBuilder.Entity<Article>().HasMany(a => a.ArticlesCompanies).WithRequired(ac => ac.Article).HasForeignKey(ac => ac.ArticleId);
modelBuilder.Entity<Company>().HasMany(c => c.ArticlesCompanies).WithRequired(ac => ac.Company).HasForeignKey(ac => ac.CompanyId);
modelBuilder.Entity<ArticleCompany>().ToTable("ArticlesCompanies");
modelBuilder.Entity<ArticleCompany>().HasKey(ac => new { ac.ArticleId, ac.CompanyId });
Then, after fetching the entity, use intermediate table in order to fetch related IDs:
var ids = db.Set<ArticleCompany>().Where(ca => ca.CompanyId == companyEntity.Id).Select(ca => ca.ArticleId);
Corresponding SQL log (only IDs are fetched from database):
exec sp_executesql N'SELECT
[Extent1].[ArticleId] AS [ArticleId]
FROM [dbo].[ArticlesCompanies] AS [Extent1]
WHERE [Extent1].[CompanyId] = #p__linq__0',N'#p__linq__0 int',#p__linq__0=1
Would some simple getters suffice?
public IEnumerable<int> ArticlesId { get { return this.Articles.Select(a => a.Id); } }
public IEnumerable<int> CompaniesId { get { return this.Companies.Select(c => c.Id); } }
This would cover many use cases. Of course since this isn't a virtual association property, you lose some of the flexibility of using these properties in actual Linq-to-Entities queries.

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