Count Foreign Key table rows in EF Core - entity-framework

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

Related

How to insert data in a joined table with Entity Framework Core with one save?

Assuming I have the below table/class structure for Entity Framework Core. Is it possible to insert new records with a join table in one call to SaveChanges?
I know it can be done with 2 saves (insert TableB > save > insert TableAB > save) and there's a similar question doing that.
public class TableA
{
public int Id { get; set; }
public ICollection<TableAB> ABs { get; set; } = null!;
}
public class TableAB
{
public int TableAId { get; set; }
public TableA TableA { get; set; } = null!;
public int TableBId { get; set; }
public TableB TableB { get; set; } = null!;
}
public class TableB
{
public int Id { get; set; }
}
It's quite easy to insert multiple related objects with only one save - for example:
var b1 = new TableB();
var b2 = new TableB();
context.TableAs.Add(new TableA
{
ABs = new List<TableAB>
{
new TableAB
{
TableB = b1,
},
new TableAB
{
TableB = b2,
},
},
});
context.TableAs.Add(new TableA
{
ABs = new List<TableAB>
{
new TableAB
{
TableB = b1,
},
},
});
context.TableAs.Add(new TableA
{
ABs = new List<TableAB>
{
new TableAB
{
TableB = b2,
},
},
});
context.SaveChanges();
The only slightly complicated part is keeping references to the related entities (TableB) when you need to reference them from multiple places.
This assumes that your Id properties are all IDENTITY columns in the database. If not, you'll need to manually assign them.

Can i have a property in my class that not exist in bd table using Entity Framework?

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.

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.

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

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.