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
Related
I have two tables, table A (loan_id, amount) and table B (id, loan_id). Now I want to select rows from table A which loan_id is not available in table B. For example
Table A has following rows:
loan_id amount
------- ------
1 200
2 400
Table B has following rows:
id loan_id
-- -------
1 2
In the above scenario, I want to join this table based on loan_id and display only those rows which is not available in table B. I want output should be like following
output:
loan_id amount
------- ------
1 200
how can I achieve this using Entity framework. So far, what I know that I need to perform left join and select those rows which B.id == null, however, I am not finding how to do this using c#, linq.
EDIT:
here I also added my Entity class:
[Table("loans")] ( in my given scenario this is table A)
public class Loan
{
[Column("loan_id")]
public int Id { get; set; }
[Column("funding_amount")]
public decimal FundingAmount { get; set; }
}
[Table("loan_approves")] (in my given scenario this is table B)
public class LoanApprove
{
[Column("id")]
public int Id { get; set; }
[Column("loan_id")]
public int LoanId { get; set; }
}
Your query should looks like this:
var result = context.Loan
.Where(l => !context.LoanApprove.Any(a => a.LoanId == l.Id))
.ToList();
Or with NOT IN
var result = context.Loan
.Where(l => !context.LoanApprove.Select(a => a.LoanId).Contains(l.Id))
.ToList();
Since you haven't provided any details of what your entity classes look like, this is just a guess:
Assuming you have:
class TableA
{
public int LoanId { get; set; }
public decimal Amount { get; set; }
public List<TableB> TableBs { get; set; }
}
class TableB
{
public int Id { get; set; }
public int LoanId { get; set; }
public TableA Loan { get; set; }
}
Then you simply need to use:
var result = context.TableAs.Where(a => !a.TableBs.Any()).ToList();
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.
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; }
public class User
{
public int ID { get; set; }
public string EmailAddress { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
}
class TestDbContext : DbContext
{
public TestDbContext()
: base("DefaultConnectionString")
{
}
public DbSet<User> Users { get; set; }
public DbSet<Address> Addresses { get; set; }
}
Above are the model definitions and DbContext difinitions. I want to add a new address for the user, so i wrote my code as bellow:
var context = new TestDbContext();
var user = context.Users.FirstOrDefault(item => item.ID == 1);
user.Addresses.Add(new Address()
{
City = "City",
Street = "Street",
Postcode = "Postcode",
});
context.SaveChanges();
My doubt is why there are 3 SQL queries are executed in this code?
It's generated in FirstOrDefault
SELECT TOP (1)
[Extent1].[ID] AS [ID],
[Extent1].[EmailAddress] AS [EmailAddress]
FROM [dbo].[Users] AS [Extent1]
WHERE 1 = [Extent1].[ID]
It's generated in user.Addresses.Add
exec sp_executesql N'SELECT
[Extent1].[ID] AS [ID],
[Extent1].[City] AS [City],
[Extent1].[Street] AS [Street],
[Extent1].[Postcode] AS [Postcode],
[Extent1].[User_ID] AS [User_ID]
FROM [dbo].[Addresses] AS [Extent1]
WHERE ([Extent1].[User_ID] IS NOT NULL)
AND ([Extent1].[User_ID] = #EntityKeyValue1)',N'#EntityKeyValue1 int',#EntityKeyValue1=1
It's generated in SaveChanges
exec sp_executesql N'INSERT [dbo].[Addresses]([City], [Street], [Postcode], [User_ID])
VALUES (#0, #1, #2, #3)
SELECT [ID]
FROM [dbo].[Addresses]
WHERE ##ROWCOUNT > 0 AND [ID] = scope_identity()',N'#0 nvarchar(max) ,#1 nvarchar(max) ,#2 nvarchar(max) ,#3 int',#0=N'City',#1=N'Street',#2=N'Postcode',#3=1
How can I avoid the second SQL?
The Addresses nav property is lazy loading when you access the property (i.e. user.Addresses), which is why you're getting the second SQL command.
Try disabling lazy loading and see if that works (don't forget to initialize the Addresses property in a constructor for User e.g.:
public User()
{
Addresses = new HashSet<Address>();
}
You can even prevent the first two queries!
You already know the user's ID value, so all you have to do is set the foreign key value in Address. Of course, Address should have this property:
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
public int UserID { get; set; } // Set this property
public User User { get; set; }
}
The pair User and UserID is called a foreign key association, which is the preferred way to deal with associations in EF (precisely because it can reduce the number of queries).
Have you tried changing the class definition slightly:
public class Address
{
public int ID { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Postcode { get; set; }
public virtual User User { get; set;}
}
so that now you can write:
var context = new TestDbContext();
var user = context.Users.FirstOrDefault(item => item.ID == 1);
context.Addresses.Add(new Address()
{
City = "City",
Street = "Street",
Postcode = "Postcode",
User = user
});
context.SaveChanges();
As already pointed out the problem here is your Addresses property is a navigation property so when you access it EF is generating a SELECT statement to load the collection in. To avoid this from happening you have 2 options:
Eager load the addresses when you load the User so you take the hit when you first load the user e.g. Users.Include(x => x.Addresses)
Disable lazy loading on that particular property by making the Addresses property non-virtual
I would add a UserId foreign key to the Address class, then I'd do this:
var context = new TestDbContext();
context.Addresses.Add(new Address()
{
UserId = 1,
City = "City",
Street = "Street",
Postcode = "Postcode",
});
context.SaveChanges();
No need to retrieve the user or the user's existing addresses
Foreign keys make Entity Framework is easier to use:
Why does Entity Framework Reinsert Existing Objects into My Database?
Making Do with Absent Foreign Keys
And relationship fix-up will synchronise the navigation property:
http://msdn.microsoft.com/en-gb/data/jj713564.aspx
Models:
public class User
{
public int Id {get; set;}
public int SomeProperty {get; set;}
public virtual Group Group { get; set; }
}
public class Group {
{
public int Id {get; set;}
// other properties
}
Running this linq query:
myContext.Users.Where(u => u.SomeProperty = 4);
yields this sql query:
select
extent1.Id as Id
extent1.SomeProperty as SomeProperty
extent1.Group_Id as Group_Id
from
dbo.Users as extent1
It's weird that it decided not to camel case the association column like it did with the other properties. Is there some reason for this?
In any case, I added mapping code to try and fix it:
var entity = modelBuilder.Entity<User>();
entity.HasRequired( a => a.Group )
.WithRequiredDependent()
.Map( a => a.MapKey( "GroupId" ) );
Unfortunately, querying with linq produces this query:
select
extent1.Id as Id
extent1.SomeProperty as SomeProperty
extent1.GroupId as GroupId
extent1.Group_Id as Group_Id
from
dbo.Users as extent1
It looks a bit better, but obviously doesn't work still because my table has the column GroupId and not Group_Id. Does anyone know what's going on here or how to fix it?
Since the mapping User-Group is n - 1 the mapping should be:
var entity = modelBuilder.Entity<User>();
entity.HasRequired(a => a.Group) // user has one Group
.WithMany() // Group has many Users
.Map( a => a.MapKey("GroupId"));
EF created the Group_Id column itself for the 1-n association it inferred from your class model, while GroupId was added because of the 1:1 association you mapped yourself.
Or you can write it as follow
public class User
{
public int Id {get; set;}
public int SomeProperty {get; set;}
[ForeignKey("Group")]
public int GroupId {get;set;}
public virtual Group Group { get; set; }
}
Group_Id cloumn is created automatically when not define foreignKey property.