Entity framework produces left join when condition on foreign key - c#

I have 2 models:
public class User
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Email { get; set; }
[Required]
[MaxLength(100)]
public string Password { get; set; }
}
and
public class Questionnaire
{
public int Id { get; set; }
[Required]
[MaxLength(500)]
public string Title { get; set; }
public User User { get; set; }
}
I would like to use this query to retrieve all questionnaires of certain user:
List<Questionnaire> questionnaires = this._dbContext.Questionnaires.Where(a => a.User.Id == 1).ToList();
It works, but entity framework produces this sql query:
SELECT `q`.`Id`, `q`.`Title`, `q`.`UserId`
FROM `Questionnaires` AS `q`
LEFT JOIN `Users` AS `u` ON `q`.`UserId` = `u`.`Id`
WHERE `u`.`Id` = 1;
In my opinion, left join is unnecessary. Please is there any workaround to avoid this left join? Thank you in advance.

You will need to expose UserId property on Questionnaire manually:
public class Questionnaire
{
public int Id { get; set; }
[Required]
[MaxLength(500)]
public string Title { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
And use it in query instead of a.User.Id:
var questionnaires = this._dbContext.Questionnaires
.Where(a => a.UserId == 1) // use UserId instead of User.Id
.ToList();

For more information:
If you choose not to explicitly include a foreign key property in the dependant end of the relationship, EF Core will create a shadow property using the pattern Id. If you look at the Questionnaire database table, UserId column exists and it has created by EF core as a shadow foreign key.
When you refer User inside where clause _dbContext.Questionnaires.Where(a => a.User.Id == 1), EF Core translate linq query into TSQL left join.
You can also use shadow property do define foreign key:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Questionnaire>()
.Property<int>("UserId");
builder.Entity<Questionnaire>()
.HasOne(e => e.User)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
}
Now left join will be replaced with the inner join:
SELECT [q].[Id], [q].[Title], [q].[UserId]
FROM [Questionnaires] AS [q]
INNER JOIN [Users] AS [c] ON [q].[UserId] = [c].[Id]
WHERE [c].[Id] = 1
To avoid unnecessary join as #Guru Stron said you need to expose UserId property on Questionnaire class.

Related

LINQ To Entity - Inner Join issue

I have two related tables like below :
Users :
public partial class Users
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Users()
{
}
public int ID { get; set; }
public int UserType_ID { get; set; }
public string Email { get; set; }
public virtual UserTypes UserTypes { get; set; }
}
UserTypes :
public partial class UserTypes
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public UserTypes()
{
this.Users = new HashSet<Users>();
}
public int ID { get; set; }
public string Name { get; set; }
public string Title { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Users> Users { get; set; }
}
For access Name of UserType i wrote this linq to entity :
string[] UserTypes = new string[1];
using (Crypto_Entities entities = new Crypto_Entities())
{
int User_ID_Integer = int.Parse(User_ID.Trim());
var user = (from User in entities.Users
//join UserType in entities.UserTypes on User.UserType_ID equals UserType.ID
where User.ID == User_ID_Integer
select User).FirstOrDefault();
if (user != null)
{
UserTypes[0] = user.UserTypes.Name;
}
}
My question is why user.Name does not work for my purpose and what is the benefit of join in linq to entity?
If i remove join as i did in my query i still can see Name field of UserType with user.UserTypes.Name.
You do not need join if you have defined correctly navigation properties. And if you just need Name, do not retrieve full entity.
string[] UserTypes = new string[1];
using (Crypto_Entities entities = new Crypto_Entities())
{
int User_ID_Integer = int.Parse(User_ID.Trim());
var query =
from User in entities.Users
where User.ID == User_ID_Integer
select User.UserTypes.Name;
var name = query.FirstOrDefault();
if (name != null)
{
UserTypes[0] = name;
}
}
If you use navigation property in query, EF automatically generates all needed joins. But if you just select whole entity without defining Include - EF will not load related data. It makes sense, because otherwise you may load almost whole database if there are a lot of relations.
Since you have set up the relations in your entities you don't need to manually write join to load related data:
var user = entities.Users
.Include(u => u.UserTypes)
.Where(u => u.ID == User_ID_Integer)
.FirstOrDefault();
As for your join being useless - EF Core translates the code into actual SQL (which you can check) and since you are not selecting any data from the joined table - it is as useless as it would be in SQL query where you have selected fields only from one table of join result.

Can I use Keyless Entity Types to query CHANGETABLE in Entity Framework Core?

I'm using SQL Server Change Tracking and I'm trying to adapt this article from Microsoft Docs to an Entity Framework application: Work with Change Tracking.
I want to run this SQL query using Entity Framework:
SELECT
P.*, CT.*
FROM
dbo.Product AS P
RIGHT OUTER JOIN
CHANGETABLE(CHANGES dbo.Product, #last_synchronization_version) AS CT
ON
P.ProductID = CT.ProductID
This is what I've got so far:
public class Product
{
public int ProductID { get; set; }
// omitted dozens of other properties
}
public class ProductChange
{
public int ProductID { get; set; }
public Product? Product { get; set; }
public long SYS_CHANGE_VERSION { get; set; }
public long? SYS_CHANGE_CREATION_VERSION { get; set; }
public char SYS_CHANGE_OPERATION { get; set; }
public byte[]? SYS_CHANGE_COLUMNS { get; set; }
public byte[]? SYS_CHANGE_CONTEXT { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductChange>()
.HasNoKey()
.ToView(null);
base.OnModelCreating(modelBuilder);
}
long lastSynchronizationVersion = ...; // obtained as described in "Work with Change Tracking"
const string sql = #"
SELECT
P.*, CT.*
FROM
dbo.Product AS P
RIGHT OUTER JOIN
CHANGETABLE(CHANGES dbo.Product, {0}) AS CT
ON
P.ProductID = CT.ProductID";
var changes = await dbContext.Set<ProductChange>.FromSqlRaw(sql, lastSynchronizationVersion);
It does not work, because EF does not understand how P.* maps to public Product? Product { get; set; }. When I remove the Product property and remove P.* from the query, things work as expected. However, I need all of the properties, not just the ID.
Copying all of Product's properties into ProductChange and making them all nullable works, but I really don't want to resort to doing that.
In practice I will be using Change Tracking not just for products, but for dozens of entity types, which all have many properties. Having to specify each property in two places just to make Entity Framework play nice with Change Tracking is not a good idea.
Is there a way to get Keyless Entity Types to do what I want? Or do I have to use ADO.NET's ExecuteReader and manually map the result?
It turns out you can use relationships with navigation properties on keyless entity types, just like you can with entity types.
Configure the relationship in OnModelCreating:
modelBuilder.Entity<ProductChange>()
.HasNoKey()
.HasOne(x => x.Entity).WithMany().HasForeignKey(x => x.ProductID) // I added this line.
.ToView(null);
Now you can use Include instead of manually joining tables:
const string sql = "SELECT * FROM CHANGETABLE(CHANGES dbo.Product, {0}) AS CT";
var changes = await dbContext
.Set<ProductChange>
.FromSqlRaw(sql, lastSynchronizationVersion)
.Include(x => x.Entity)
.DefaultIfEmpty() // https://stackoverflow.com/a/63006304/1185136
.ToArrayAsync();
// it works!
Additionally (this is optional), I created base types Change and Change<TEntity> that can be inherited from easily:
public abstract class Change
{
public long Version { get; set; }
public long? CreationVersion { get; set; }
public char Operation { get; set; }
public byte[]? Columns { get; set; }
public byte[]? Context { get; set; }
}
public abstract class Change<TEntity> : Change
where TEntity : class
{
public TEntity? Entity { get; set; }
}
public ProductChange : Change<Product>
{
public int ProductID { get; set; }
}
public OrderChange : Change<Order>
{
public int OrderID { get; set; }
}
// etc...
You'll have to configure the relationship for each derived type in OnModelCreating.
modelBuilder.Entity<ProductChange>()
.HasOne(x => x.Entity).WithMany().HasForeignKey(x => x.ProductID);
modelBuilder.Entity<OrderChange>()
.HasOne(x => x.Entity).WithMany().HasForeignKey(x => x.OrderID);
// etc...
You won't have to repeat HasNoKey() and ToView(null) for every enitity though, add this loop instead:
foreach (var changeType in modelBuilder.Model.FindLeastDerivedEntityTypes(typeof(Change)))
{
var builder = modelBuilder.Entity(changeType.ClrType).HasNoKey().ToView(null);
builder.Property(nameof(Change.Version)).HasColumnName("SYS_CHANGE_VERSION");
builder.Property(nameof(Change.CreationVersion)).HasColumnName("SYS_CHANGE_CREATION_VERSION");
builder.Property(nameof(Change.Operation)).HasColumnName("SYS_CHANGE_OPERATION");
builder.Property(nameof(Change.Columns)).HasColumnName("SYS_CHANGE_COLUMNS");
builder.Property(nameof(Change.Context)).HasColumnName("SYS_CHANGE_CONTEXT");
}
If you want to, you can move the ID property to Change<TEntity>. By doing this, you can remove the ProductChange, OrderChange etc. classes. But, you'll have to specify the column name so Entity Framework Core understands the ID property from Change<Product> maps to ProductID, and the ID property from Change<Order> maps to OrderID, etc. I opted not to do this because this approach won't work if you have composite keys.

EF 2.1 incorrectly translating Group By Query

I'm trying to get a query working on EF 2.1 against an existing database. I'm getting an error which suggests that I haven't configured my models properly.
My models:
public class Job
{
public int JobId { get; set; }
public JobStatus JobStatus { get; set; }
}
public class JobStatus
{
[Key]
public string JobStatusId { get; set; }
public string Colour { get; set; }
public ICollection<Job> Jobs { get; set; }
}
My Query:
var jobs = _context.Jobs
.GroupBy(p => p.JobStatus.Colour)
.Select(g => new { colour = g.Key, count = g.Count() });
The error is "Invalid column name 'JobStatusId'. EF is translating into the following query:
SELECT [p.JobStatus].[Colour] AS [colour], COUNT(*) AS [count]
FROM [Jobs] AS [p]
LEFT JOIN [JobStatus] AS [p.JobStatus] ON [p].[JobStatusId] = [p.JobStatus].[JobStatusId]
GROUP BY [p.JobStatus].[Colour]
Which isn't right. p.JobStatusId doesn't exist, it should be p.JobStatus.JobStatusId. What am I doing wrong?
UPDATE
I've added this to my Job model;
public string JobStatusFK {get; set;}
And tried the following:
modelBuilder.Entity<Job>().HasOne(x=>x.JobStatus).HasForeignKey(p => p.AuthorFK);
However Intellisense doesn't allow this:
'ReferenceNavigationBuilder<Job, JobStatus>' does not contain a definition for 'HasForeignKey' and no accessible extension method 'HasForeignKey' accepting a first argument of type 'ReferenceNavigationBuilder<Job, JobStatus>' could be found
That's because the relatinship of your Job : JobStatus is Many-to-One.
The EF thought there's a foreign key that references JobStatus , i.e. , a JobStatusId column within the Jobset as FK .
You have to make sure that JobStatusId is FK in Job class.
You can use the below declaration in the Job class or use the HasForeignKey in DBContext class using fluent API.
public string JobStatusId { get; set; }

Creating LINQ statement against an EF Context with no relationships

I cannot wrap my head around how to write a linq query against my EF context to get what I want.
1) What I have
Database with no foreign keys assigned, and a reverse engineered code first entity framework project. I tried manually adding virtual classes so EF might create implied foreign keys in the DBcontext, but I get errors on my .Include statements still.
Without the include the only thing I can think of is to use left joins, but I haven't gotten it down yet. In the end there will be 21 tables I have to get data from, but the following table outline encapsulates the majority of issues i'm facing.
Sample data structure:
Table Human: HumanId, LastFoodEatenId, FavoriteFoodId, CurrentlyDesiredFoodId
Table Food: FoodId, FoodName, FoodStuff
Table Toys: HumanOwnerId, ToyId, ToyName
Table Pets: HumanOwnerId, PetId, PetName, PetType
Table PetSurgery: PetId, SurgeryId, SurgeryPerformed
2) What I want
Given a HumanID, I want a compsite class or something like it from a single query.
Public Class QueryResult
{
public Human human {get;set;}
public Food LastEatenFood {get;set;}
public Food FavoriteFood {get;set;}
public Food CurrentlyDesiredFood {get;set;}
public IEnumerable<Toy> Toys {get;set;}
public IEnumerable<Pet> Pets {get;set;} //Includes surgeries if any
}
Is it even possible to write a single query to get this kind of information in a single db hit? I'd be fine is someone simply confirmed it is't possible. Then I can at least request we add relationships to our database.
Thanks in advance,
You can use linq to query multiple, non-related tables.
I'm going to assume a LOT about your entities, but here we go...
int humanId = 1234;
using (var context = new MyContext())
{
var human = (from h in context.Humans
join lf in context.Foods on h.LastFoodEatenId equals lf.foodId into lfg
from lf in lfg.DefaultIfEmpty() // left join
join ff in context.Foods on h.FavoriteFoodId equals lf.foodId into ffg
from ff in ffg.DefaultIfEmpty() // left join
join cf in context.Foods on h.CurrentlyDesiredFoodId equals lf.foodId into cfg
from cf in cfg.DefaultIfEmpty() // left join
join p in context.Pets on h.humanId equals p.humanId into pg // group
join t in context.Toys on h.humanId equals t.humanId into tg // group
where h.humanId = humanId
select new QueryResult { human = h, LastEatenFood = lf, FavoriteFood = ff, CurrentlyDesiredFood = cf, Toys = tg, Pets = pg }
).SingleOrDefault();
}
Note: I'm doing this from memory without a syntax checker, so ymmv. Adding surgeries should be possible as well, but may require a subquery.
I tried manually adding virtual classes
I assume you mean virtual collections. You can define relationships in a "code-first" model if they are not in the database. The only condition is that foreign keys must refer to properties that EF knows as primary keys. So you should be able to do LINQ queries using navigation properties in stead of these verbose joins by a model like this (reduced to the essentials):
class Human
{
public int HumanId { get; set; }
public int LastFoodEatenId { get; set; }
public virtual Food LastEatenFood { get; set; }
public int FavoriteFoodId { get; set; }
public virtual Food FavoriteFood { get; set; }
public int CurrentlyDesiredFoodId { get; set; }
public virtual Food CurrentlyDesiredFood { get; set; }
public virtual ICollection<Toy> Toys { get; set; }
public virtual ICollection<Pet> Pets { get; set; }
}
class Food
{
public int FoodId { get; set; }
}
class Pet
{
public int PetId { get; set; }
public int HumanOwnerId { get; set; }
}
class Toy
{
public int ToyId { get; set; }
public int HumanOwnerId { get; set; }
}
And a mapping:
class HumanMapping : EntityTypeConfiguration<Human>
{
public HumanMapping()
{
HasOptional(h => h.LastEatenFood).WithMany()
.HasForeignKey(h => h.LastFoodEatenId);
HasOptional(h => h.FavoriteFood).WithMany()
.HasForeignKey(h => h.FavoriteFoodId);
HasOptional(h => h.CurrentlyDesiredFood).WithMany()
.HasForeignKey(h => h.CurrentlyDesiredFoodId);
HasMany(h => h.Toys).WithOptional().HasForeignKey(t => t.HumanOwnerId);
HasMany(h => h.Pets).WithOptional().HasForeignKey(t => t.HumanOwnerId);
}
}
EF will infer the primary keys by name conventions.
Now you will be able to execute a LINQ statement like:
context.Humans.Where(h => h.HumanId == id)
.Include(h => h.LastEatenFood)
.Include(h => h.FavoriteFood)
.Include(h => h.CurrentlyDesiredFood)
.Include(h => h.Toys)
.Include(h => h.Pets)
From your description I understand that PetSurgery should be a junction class between Pet and another class (Surgery?). Anyway, I think you will manage creating the correct mappings, seeing this example.

Entity Framework 4.1 Mapping Relationships by setting Foreign Keys

How does one tell the bloody Entity Framework to map relationship to the columns one wants!
I have 1 table:
public class ShedPart
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int GroupId { get; set; }
public int ParentGroupId { get; set; }
public string GroupName { get; set; }
[ForeignKey("GroupId")]
[InverseProperty("ParentGroupId")]
public ICollection<Part> ParentParts { get; set; }
}
Each Part can have multiple ParentParts...
The SQL generated is this :
SELECT
`Project1`.`Id`,
`Project1`.`Name`,
`Project1`.`GroupId`,
`Project1`.`ParentGroupId`,
`Project1`.`GroupName`,
`Project1`.`C1`,
`Project1`.`Id1`,
`Project1`.`Name1`,
`Project1`.`GroupId1`,
`Project1`.`ParentGroupId1`,
`Project1`.`GroupName1`
FROM (SELECT
`Extent1`.`Id`,
`Extent1`.`Name`,
`Extent1`.`GroupId`,
`Extent1`.`ParentGroupId`,
`Extent1`.`GroupName`,
`Extent2`.`Id` AS `Id1`,
`Extent2`.`Name` AS `Name1`,
`Extent2`.`GroupId` AS `GroupId1`,
`Extent2`.`ParentGroupId` AS `ParentGroupId1`,
`Extent2`.`GroupName` AS `GroupName1`
CASE WHEN (`Extent2`.`Id` IS NULL) THEN (NULL) ELSE (1) END AS `C1`
FROM `Parts` AS `Extent1` LEFT OUTER JOIN `Parts` AS `Extent2` ON `Extent1`.`Id` = `Extent2`.`GroupId`) AS `Project1`
ORDER BY
`Id` ASC,
`C1` ASC}
As you can see that is wrong as it is joining the tables on Id => GroupId, when I am trying to join by ParentGroupId => GroupId.
So I try this:
modelBuilder.Entity<Part>()
.HasMany(s => s.ParentParts)
.WithMany()
.Map(m =>
{
m.ToTable("parts");
m.MapLeftKey("GroupId");
m.MapRightKey("ParentGroupId");
});
Does the same thing..... Seems Entity Framework will only map to the Key Column! How do get it to relate the columns I want?
Have you tried to extract
[Key]
public int GroupId { get; set; }
public int ParentGroupId { get; set; }
to a Group table which has a self join ?
this way you could have a navigation property for Part -> Group.
group would contain a collection of parts and its parent group.
GroupId would then be a primary key and you could self reference for the ParentGroupId

Categories

Resources