Entity Framework Code First One-To-Many doing wrong queries? - entity-framework

So I have a model
public class Player
{
public int PlayerId { get; set; }
[Required]
public string Name2 { get; set; }
public string Cv { get; set; }
public int TeamId { get; set; }
public virtual Team Team { get; set; }
}
public class Team
{
public int TeamId { get; set; }
[Required]
public string Name { get; set; }
public string City { get; set; }
public DateTime Founded { get; set; }
public virtual IList<Player> Players { get; set; }
}
My Teams table in DB contains records, my Players table in DB does not contain any (empty).
When I run this query:
IQueryable<Player> query = playerRepository.All.Include(p => p.Team);
return View((query);
I get this query in DB (via profiler):
SELECT
[Project1].[TeamId] AS [TeamId],
[Project1].[Name] AS [Name],
[Project1].[City] AS [City],
[Project1].[Founded] AS [Founded],
[Project1].[C1] AS [C1],
[Project1].[PlayerId] AS [PlayerId],
[Project1].[Name2] AS [Name2],
[Project1].[Cv] AS [Cv],
[Project1].[TeamId1] AS [TeamId1]
FROM ( SELECT
[Limit1].[TeamId] AS [TeamId],
[Limit1].[Name] AS [Name],
[Limit1].[City] AS [City],
[Limit1].[Founded] AS [Founded],
[Extent2].[PlayerId] AS [PlayerId],
[Extent2].[Name2] AS [Name2],
[Extent2].[Cv] AS [Cv],
[Extent2].[TeamId] AS [TeamId1],
CASE WHEN ([Extent2].[PlayerId] IS NULL) THEN CAST(NULL AS int)
ELSE 1
END AS [C1]
FROM ( SELECT TOP (10) [c].[TeamId] AS [TeamId],
[c].[Name] AS [Name],
[c].[City] AS [City],
[c].[Founded] AS [Founded]
/* HERE */
FROM [dbo].[Teams] AS [c]
) AS [Limit1]
/* AND HERE */
LEFT OUTER JOIN [dbo].[Players] AS [Extent2]
ON [Limit1].[TeamId] = [Extent2].[TeamId]
) AS [Project1]
ORDER BY [Project1].[TeamId] ASC, [Project1].[C1] ASC
Which as a result I get one empty row shown on the screen. This is because this joins are done in wrong order...instead of joining Teams on Players, I get Players on Teams...which in turn means that even though I have NO players in the DB, I get an empty row in the grid.
Anyone have any ideas why???
Vladan

You must be doing something wrong or you are looking at wrong query. I used your entities and your linq query:
var data = context.Players.Include(p => p.Team).ToList();
and I get this SQL query:
SELECT
[Extent1].[PlayerId] AS [PlayerId],
[Extent1].[Name2] AS [Name2],
[Extent1].[Cv] AS [Cv],
[Extent1].[TeamId] AS [TeamId],
[Extent2].[TeamId] AS [TeamId1],
[Extent2].[Name] AS [Name],
[Extent2].[City] AS [City],
[Extent2].[Founded] AS [Founded]
FROM [dbo].[Players] AS [Extent1]
INNER JOIN [dbo].[Teams] AS [Extent2] ON [Extent1].[TeamId] = [Extent2].[TeamId]
But if I use this query:
var data = context.Teams.Include(t => t.Players).Take(10).ToList();
I will get exactly your SQL query. TOP (10) and reverse table querying will not appear without reason.

Related

how do I prevent left join for owned properties in entity framework core 3.1?

public class Employee
{
public string FullName { get; set; }
public string Code { get; set; }
public string Gender { get; set; }
public string MaritalStatus { get; set; }
public DateTime DateOfBirth { get; set; }
public string Status { get; set; }
// # Address
public Address Address { get; set; }
}
public class Address
{
public string Address1 { get; set; }
public string? Address2 { get; set; }
public string City { get; set; }
public string? Region { get; set; }
public string PostCode { get; set; }
public Country Country { get; set; }
private Address() {}
}
EntityConfiguration
public class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
{
public void Configure(EntityTypeBuilder<Employee> builder)
{
builder.OwnsOne(e => e.Address);
}
}
Generated SQL
SELECT t."Id", t."BsonId", t."Code", t."CompanyId", t."Created", t."DateOfBirth", t."FullName", t."Gender", t."LastModified", t."MaritalStatus", t."Status", t1."Id", t1."Address_Address1", t1."Address_Address2", t1."Address_City", t1."Address_PostCode", t1."Address_Region"
FROM (
SELECT e."Id", e."BsonId", e."Code", e."CompanyId", e."Created", e."DateOfBirth", e."FullName", e."Gender", e."LastModified", e."MaritalStatus", e."Status"
FROM "I180814-04-edm"."Employees" AS e
ORDER BY (SELECT 1)
LIMIT #__p_1 OFFSET #__p_0
) AS t
LEFT JOIN (
SELECT e0."Id", e0."Address_Address1", e0."Address_Address2", e0."Address_City", e0."Address_PostCode", e0."Address_Region", e1."Id" AS "Id0"
FROM "I180814-04-edm"."Employees" AS e0
INNER JOIN "I180814-04-edm"."Employees" AS e1 ON e0."Id" = e1."Id"
WHERE (e0."Address_Region" IS NOT NULL) OR ((e0."Address_PostCode" IS NOT NULL) OR ((e0."Address_City" IS NOT NULL) OR ((e0."Address_Address2" IS NOT NULL) OR (e0."Address_Address1" IS NOT NULL))))
) AS t0 ON t."Id" = t0."Id"
LEFT JOIN (
SELECT e2."Id", e2."Address_Address1", e2."Address_Address2", e2."Address_City", e2."Address_PostCode", e2."Address_Region", e3."Id" AS "Id0"
FROM "I180814-04-edm"."Employees" AS e2
INNER JOIN "I180814-04-edm"."Employees" AS e3 ON e2."Id" = e3."Id"
WHERE (e2."Address_Region" IS NOT NULL) OR ((e2."Address_PostCode" IS NOT NULL) OR ((e2."Address_City" IS NOT NULL) OR ((e2."Address_Address2" IS NOT NULL) OR (e2."Address_Address1" IS NOT NULL))))
) AS t1 ON t."Id" = t1."Id"
Did I miss something? I searched all over but I could not find anyway to modify the generated SQL for OwnedProperties in Ef core. Is there a way to setup the relationships so that ef core projects the properties as if it was a single model rather than detecting it as a relationship and generating a left join.
In fact, there are nothings wrongs with generated SQL statement (left join).
You can use right predicate to be sure address is not null like inner join without any performance impact:
var result = await dbcontext.Employee.Include(x => x.Include(y => y.Address)).Where(p => p.Address != null).FirstOrDefaultAsync();
This is a very common approach.
This was a known issue in Entity Framework Core 3.1. It has been fixed in Entity Framework 5.0. The bug ticket has some useful discussions about performance and workarounds, if you aren't able to upgrade. https://github.com/dotnet/efcore/issues/18299

Count Foreign Key table rows in EF Core

In SQL I am able to do something like this:
SELECT colA, colB, (SELECT COUNT(1) FROM ForeignTable WHERE colA = TableA.colA)
FROM TableA
Is something like this possible in EF Core?
Just to be clear with Models
public class TableA
{
public string colA { get; set; }
public string colB { get; set; }
}
public class ForeignTable
{
public string colA { get; set; }
public virtual TableA TableA { get; set; }
}
Thanks Ivan Stoev, the right answer is just to include a Navigation property in TableA to ForeignTable and ensuring that table is .Include() in the query.
The below is for reference, way more complicated...
var data = from q in _context.TableA.
GroupJoin(_context.ForeignTable, i => i.colA, j => j.FcolA,
(i,j) => new {TableA = i, ForeignTable = j}
select new TableA
{
colA = q.TableA.colA,
colB = q.TableA.colB,
count = q.ForeignTable.Count()
};

Entity Framework : extra call to database on Parent Table

My model is simple
public partial class Customer
{
public Customer()
{
this.Orders = new HashSet<Order>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
public partial class Order
{
public Order()
{
this.OrderDetails = new HashSet<OrderDetail>();
}
public int Id { get; set; }
public string OrderDescription { get; set; }
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
}
public partial class OrderDetail
{
public int Id { get; set; }
public int OrderId { get; set; }
public string ProductName { get; set; }
}
This code was generated using EF's .edmx file. Then I generated my database from this model. I am doing eager loading to fetch data.
public virtual IQueryable<T> GetAllEagerLoadSelective(string[] children)
{
foreach (var item in children)
{
DbSet.Include(item).Load();
}
return DbSet;
}
and my call looks like
string[] Navs = { "OrderDetails" };
var orders = VNUow.Order.GetAllEagerLoadSelective(Navs);
var temp = orders.ToList();
return temp;
LazyLoading is set to false .
There are two queries being run, The first one makes sense
SELECT
[Project1].[Id] AS [Id],
[Project1].[OrderDescription] AS [OrderDescription],
[Project1].[CustomerId] AS [CustomerId],
[Project1].[C1] AS [C1],
[Project1].[Id1] AS [Id1],
[Project1].[OrderId] AS [OrderId],
[Project1].[ProductName] AS [ProductName]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[OrderDescription] AS [OrderDescription],
[Extent1].[CustomerId] AS [CustomerId],
[Extent2].[Id] AS [Id1],
[Extent2].[OrderId] AS [OrderId],
[Extent2].[ProductName] AS [ProductName],
CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM [dbo].[Orders] AS [Extent1]
LEFT OUTER JOIN [dbo].[OrderDetails] AS [Extent2] ON [Extent1].[Id] = [Extent2].[OrderId]
) AS [Project1]
ORDER BY [Project1].[Id] ASC, [Project1].[C1] ASC
but why is there this second query? A select on the parent table...
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[OrderDescription] AS [OrderDescription],
[Extent1].[CustomerId] AS [CustomerId]
FROM [dbo].[Orders] AS [Extent1]
You are calling Load() to populate the internal sets, which is what creates the first query. Then you're returning the dbSet. Then you call ToList() on the DbSet which is effectively another query entirely. The only time querying a DbSet does not result in a database query is when you use the Find() function to retrieve an entity that has already been loaded. Everything else is a query to the db even if it is only returning entities that have already been loaded.
To fix this, change GetAllEagerLoadSelective like so:
public virtual IList<T> GetAllEagerLoadSelective(string[] children)
{
IQueryable<T> dbSet = DbSet;
foreach (var item in children)
{
dbSet = dbSet.Include(item);
}
return dbSet.ToList();
}

Get related entity id without loading it

I need to list all the orders with their corresponding customer ID using Entity Framework (Code First). This should be possible without querying the Customers table since the customer ID is a FK in the Orders table. However, EF generates selects against the Orders and the Customers tables as well.
This is the entities model and the code used to query Orders:
public class Order
{
public virtual Guid Id { get; set; }
public virtual string Description { get; set; }
public virtual Customer Customer { get; set; }
}
public class Customer
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
using (var context = new LazyLoadingEfContext())
{
foreach (var order in context.Orders)
{
Console.WriteLine("Order {0}, Customer {1}", order.Description, order.Customer.Id);
}
}
The SQL generated is the following:
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[Description] AS [Description],
[Extent1].[Customer_Id] AS [Customer_Id]
FROM [dbo].[Orders] AS [Extent1]
exec sp_executesql N'SELECT
[Extent2].[Id] AS [Id],
[Extent2].[Name] AS [Name]
FROM [dbo].[Orders] AS [Extent1]
INNER JOIN [dbo].[Customers] AS [Extent2] ON [Extent1].[Customer_Id] = [Extent2].[Id]
WHERE ([Extent1].[Customer_Id] IS NOT NULL) AND ([Extent1].[Id] =
#EntityKeyValue1)',N'#EntityKeyValue1 uniqueidentifier',
#EntityKeyValue1='FF947EF3-5A3F-4A26-BDB9-039C49F559A7'
(plus other identical queries with different values for the parameter #EntityKeyValue1)
Is there any way to configure EF in order to retrieve related entity IDs from the "parent" object instead of loading the related entity?
BTW, I've tested the same scenario using NHibernate and only one query is executed against the Orders table:
SELECT this_.Id as Id1_0_, this_.Description as Descript2_1_0_,
this_.Customer_id as Customer3_1_0_ FROM [Order] this_
Try this:
public class Order
{
public virtual Guid Id { get; set; }
public virtual string Description { get; set; }
public Guid CustomerID { get; set; }
[ForeignKey("CustomerID ")]
public virtual Customer Customer { get; set; }
}
After that you should be able to get just order.CustomerID within your foreach loop, without querying Customers table.

Code First conventions for foreign keys and table names

Please accept my apologies for the large amount of text, I tried not to miss the details that may affect on something
Entity Framework 4.3.1
My Model:
public class Podcast
{
public int Id { get; set; }
public string Title { get; set; }
public int PodcastStatus_Id { get; set; }
public int PodcastType_Id { get; set; }
public virtual PodcastStatus PodcastStatus { get; set; }
public virtual PodcastType PodcastType { get; set; }
public virtual ICollection<CategoryLink> CategoryLinks { get; set; }
}
public class PodcastStatus
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Podcast> Podcasts { get; set; }
}
public class PodcastType
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Podcast> Podcasts { get; set; }
}
Context:
public class EcDbContext : DbContext
{
public DbSet<PodcastType> PodcastTypes { get; set; }
public DbSet<PodcastStatus> PodcastStatuses { get; set; }
public DbSet<Podcast> Podcasts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Podcast>().HasRequired(p => p.PodcastStatus).WithMany(s => s.Podcasts).HasForeignKey(p => p.PodcastStatus_Id);
modelBuilder.Entity<Podcast>().HasRequired(p => p.PodcastType).WithMany(t => t.Podcasts).HasForeignKey(p => p.PodcastType_Id);
base.OnModelCreating(modelBuilder);
}
}
Query:
db.Podcasts.Where(p => p.PodcastStatus.SysStatus > 0 && p.Title.Contains(search))
.OrderBy(p => p.CreatedDate)
.Skip((page - 1) * PageSize).Take(PageSize)
Here I've deleted some columns from the Model descriptions so to not complicate things.
I get the following error message: Invalid object name 'dbo.PodcastStatus'. This table is named 'PodcastStatuses' in the database. We will see the following text if we look at the result SQL query:
{SELECT TOP (10)
[Project1].[Id] AS [Id],
[Project1].[Title] AS [Title],
[Project1].[CreatedDate] AS [CreatedDate],
[Project1].[PodcastStatus_Id] AS [PodcastStatus_Id],
[Project1].[PodcastType_Id] AS [PodcastType_Id],
[Project1].[PodcastStatus_Id1] AS [PodcastStatus_Id1],
[Project1].[PodcastType_Id1] AS [PodcastType_Id1]
FROM ( SELECT [Project1].[Id] AS [Id], [Project1].[CastNo] AS [CastNo], [Project1].[Title] AS [Title], [Project1].[OriginalText] AS [OriginalText], [Project1].[TranslateText] AS [TranslateText], [Project1].[CreatedDate] AS [CreatedDate], [Project1].[PodcastStatus_Id] AS [PodcastStatus_Id], [Project1].[PodcastType_Id] AS [PodcastType_Id], [Project1].[ImageFileName] AS [ImageFileName], [Project1].[PodcastStatus_Id1] AS [PodcastStatus_Id1], [Project1].[PodcastType_Id1] AS [PodcastType_Id1], row_number() OVER (ORDER BY [Project1].[CreatedDate] ASC) AS [row_number]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
[Extent1].[CreatedDate] AS [CreatedDate],
[Extent1].[PodcastStatus_Id] AS [PodcastStatus_Id],
[Extent1].[PodcastType_Id] AS [PodcastType_Id],
[Extent1].[PodcastStatus_Id1] AS [PodcastStatus_Id1],
[Extent1].[PodcastType_Id1] AS [PodcastType_Id1]
FROM [dbo].[Podcasts] AS [Extent1]
INNER JOIN [dbo].[PodcastStatus] AS [Extent2] ON [Extent1].[PodcastStatus_Id] = [Extent2].[Id]
WHERE ([Extent2].[SysStatus] > 0) AND ((N'' = #p__linq__0) OR ([Extent1].[Title] LIKE #p__linq__1 ESCAPE N'~'))
) AS [Project1]
) AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[CreatedDate] ASC}
PodcastStatus_Id1, PodcastStatus_Id1, [dbo].[PodcastStatus] (and not 'statusES'), [PodcastStatus_Id] = [Extent2].[Id]
Why? I don't understand what did I do wrong...
Can somebody help?
Thanks!
It looks like a problem when EF pluralizes names when generating the database - I ran across something like this a while ago but I forget which EF version.
You can force EF to name a table by using (although you will likely need to drop/recreate the database) :
[Table("PodcastStatus")]
public class PodcastStatus
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Podcast> Podcasts { get; set; }
}