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.
Related
I'm upgrading my ASP NET Core web application from .NET Core 2.1 to .NET Core 5.0.
I noticed a little difference in how my SQL queries are being generated from LINQ statements when I use Include() and Skip()/Take() together.
I have two tables: Shops and Companies - Every Shop is assigned to a single Company.
Those are the two table definitions:
public class Shop
{
public int ShopID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public int CompanyID { get; set; }
public Company Company { get; set; }
}
public class Company
{
public int CompanyID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string VATCode { get; set; }
}
... and this is the LINQ expression I am referring to:
shopIQ = _context.Shops.Include(x => x.Company)
.OrderBy(x => x.Code)
.Skip(0).Take(20);
With EFCore 2.1 this was translated in this SQL Server query:
SELECT [x].[ShopID], [x].[Code], [x].[Name], [x].[CompanyID], [x.Company].[Code] AS [Code0], [x.Company].[Name] AS [Name0], [x.Company].[VATCode] AS [VATCode]
FROM [Shops] AS [x]
INNER JOIN [Companies] AS [x.Company] ON [x].[CompanyID] = [x.Company].[CompanyID]
ORDER BY [x].[Code]
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY
... but with EFCore 5.0 this is translated to:
SELECT [t].[ShopID], [t].[Code], [t].[Name], [t].[CompanyID], [c].[CompanyID], [c].[Code], [c].[Name], [c].[VATCode]
FROM (
SELECT [s].[ShopID], [s].[Code], [s].[Name], [s].[CompanyID]
FROM [Shops] AS [s]
ORDER BY [s].[Code]
OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY
) AS [t]
INNER JOIN [Companies] AS [c] ON [t].[CompanyID] = [c].[CompanyID]
ORDER BY [t].[Code]
which is kinda weird to me.
Anyone knows why is this happening?
Thanks.
I have a table, in my DB, like this:
Table_A
Col1 nvarchar(100)
Col2 nvarchar(100)
Col3 nvarchar(100)
Now in my entity i have:
public class Table_A
{
public string Col1 {get; set;}
public string Col2 {get; set;}
public string Col3 {get; set;}
public string Col4 {get; set;}
}
Using EF, in some cases i need to execute a Stored Procedure that return Col1, Col2, Col3 and Col4(Col4 is a column retrive in a Left outer join with other table); the problem is that when i don't use that SP i get an error on Col4.
I am using EF Core 2.2.3 and MS SQL Server 2017.
Can i have a property in my class that not exist in bd table using Entity Framework?
Yes, you can use the NotMapped attribute
Excerpt:
public class Contact
{
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string FullName => $"{FirstName} {LastName}";
public string Email { get; set; }
}
or you can use the Fluent API Ignore method
Excerpt:
public class SampleContext : DbContext
{
public DbSet<Contact> Contacts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().Ignore(c => c.FullName);
}
}
public class Contact
{
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
public string Email { get; set; }
}
Using EF, in some cases i need to execute a Stored Procedure that return Col1, Col2, Col3 and Col4(Col4 is a column retrive in a Left outer join with other table); the problem is that when i don't use that SP i get an error on Col4. I am using EF Core 2.2.3 and MS SQL Server 2017. Thanks.
But this begs the question, is your actual question: can I have an optional property? to which the answer is no. The property either; always has to be mapped or never mapped.
I am using EF 6.0 and creating my database views using code first. I have a small sample that works fine. I'm trying to migrate to another set of views. For some reason I am getting the wrong column name on the query for one of the columns. It seems to be bring in the column from the other view I'm joining to.
I've tried to change the [Key] columns on the tables but it just doesn't seem to change.
The view vABC consists of columns: name varchar(30) and desc varchar(100)
The view vXYZ consists of columns: name varchar(30) and amount float
They will join to each other on name.
This is a snippet of the query I am running:
var name = dataRecord.Cells[0].Value.ToString();
var query = from b in _context.XYZ
.Where(b => b.name == name)
select b;
This is returning:
SELECT
1 AS [C1],
[Extent1].[name] AS [name],
[Extent1].[amount] AS [amount],
[Extent1].[ABC_name] AS [ABC_name]
FROM [dbo].[vXYZ] AS [Extent1]
WHERE [Extent1].[name] = #p__linq__0
I will attached the classes and dbcontext below.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Mynamespace
{
[Table("vABC")]
public class ABC
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public ABC()
{
XYZs= new HashSet<XYZ>();
}
[Key]
[StringLength(150)]
public string name { get; set; }
[StringLength(150)]
public string Desc { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<XYZ> XYZs{ get; set; }
}
}
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Mynamespace
{
[Table("vXYZ")]
public class Price
{
[Key]
[Required]
[StringLength(200)]
public string name{ get; set; }
public decimal? amount{ get; set; }
public virtual ABC ABC{ get; set; }
}
}
namespace Mynamespace
{
using System.Data.Entity;
public partial class myContext : DbContext
{
public myContext () : base("name=myContext ")
{
Database.SetInitializer<myContext >(null);
}
public virtual DbSet<ABC> ABCs { get; set; }
public virtual DbSet<XYZ> XYZs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<XYZ>()
.Property(e => e.amount)
.HasPrecision(19, 4);
}
}
}
I am expecting query to be
SELECT [Extent1].[name] AS [name],
[Extent1].[amount] AS [amount]
FROM [dbo].[vXYZ] AS [Extent1]
WHERE [Extent1].[name] = #p__linq__0
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();
}
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.