I am facing a problem with EF7 inverse property. There are two entities that are connected like this.
public class Employee
{
public int Id { get; set; }
}
public class Review
{
public int Id { get; set; }
[Required]
public virtual Employee Employee { get; set; }
[Required]
public Employee Manager { get; set; }
}
I want to access a list of the reviews when I start to query my employees, so I tried to do this:
public class Employee
{
public Employee()
{
Reviews = new List<Review>();
}
public int Id { get; set; }
[InverseProperty("Employee")]
public virtual ICollection<Review> Reviews { get; set; }
}
With this, the query is not well made and return this error:
Invalid column name 'EmployeeId1'.
This is the part of the query where is the error:
SELECT [ua].[Id], [r].[EmployeeId], [r].[EmployeeId1], [r1].[EmployeeId], [r1].[EmployeeId1]
FROM [UserAssessment] AS [ua]
LEFT JOIN [Review] AS [r] ON [ua].[ReviewId] = [r].[Id]
LEFT JOIN [Review] AS [r1] ON [ua].[ReviewId] = [r1].[Id]
Anyone know what can I do?
UPDATE
This statement is generating the query:
return this.DbSet
.Include(ua => ua.Employee).ThenInclude(t => t.Role)
.Include(ua => ua.Review).ThenInclude(rt => rt.ReviewType)
.Include(ua => ua.Review).ThenInclude(rt => rt.Manager).ThenInclude(r => r.Role)
I have to access with those same includes because lazy loading is not available on EF7 yet.
You need the InverseProperty on both the Employee and Review
public class Review
{
public int Id { get; set; }
[Required]
[InverseProperty("Reviews")]
public Employee Employee { get; set; }
[Required]
public Employee Manager { get; set; }
}
public class Employee
{
public int Id { get; set; }
[InverseProperty("Employee")]
public ICollection<Review> Reviews { get; set; }
}
Should work. I have a similar setup where it creates the navigation without creating any new fields. If this doesn't work let me know and I'll spin up a test project.
Also note, that EF7 currently ignores virtual and this does not have meaning like it did in EF6.
Related
I have two tables that I need to join and filter. Orders and Customers. I have generated these classes using EF Code First from DB.
Generated classes for the tables -
Orders
[Table("Orders")]
public partial class Orders
{
[Key]
[StringLength(17)]
public string OrderID { get; set; }
public int ShipToCustomerID { get; set; }
//Navigation Property
public Customer Customer { get; set; }
}
Customers
[Table("Customer")]
public partial class Customer
{
public int CustomerID { get; set; }
public string AccountNumber { get; set; }
//Navigation prop
public int ShipToCustomerID { get; set; } (not a part of the table, just attempting to get the navigation work)
public Orders Order { get; set; }
}
Method 1:
LINQ Joins
using (var context = new OrderDetailsGeneral1())
{
var data = (from p in context.Orders
join q in context.Customers
on p.ShipToCustomerID equals q.CustomerID
where p.OrderID == "7150615"
select new
{
OrderID = p.OrderID,
CustomerID = q.AccountNumber
}
);
var orders = data.ToList();
return Json(orders);
}
This works well and I get the following output -
[
{
"OrderID": "7150615",
"CustomerID": "23320347 "
}
]
Method 2:
I read that it's better to use navigation properties than using joins and that's why I was trying to do so, as per that I added the navigation properties to the classes above.
I tried a bunch of ways to link them together. One of them is the way mentioned here and I came across a bunch of errors.
It would try to map Customers.CustomerID to Orders.OrderID instead of Orders.ShipToCustomerID.
What's the best way to achieve this? I am having a hard time figuring out linking this foreign key (Customers.CustomerID) to a non primary/alternate key (Orders.ShipToCustomerID)
you have to fix your classes
[Table("Orders")]
public partial class Order
{
[Key]
[StringLength(17)]
public string OrderID { get; set; }
public int ShipToCustomerID { get; set; }
[ForeignKey(nameof(ShipToCustomerID))]
[InverseProperty("Orders")]
public virtual Customer Customer { get; set; }
}
[Table("Customer")]
public partial class Customer
{
[Key]
public int CustomerID { get; set; }
public string AccountNumber { get; set; }
[InverseProperty(nameof(Order.Customer))]
public virtual ICollection<Order> Orders { get; set; }
}
I'm having trouble adding multiple entities with multiple children at once in foreach loop.
Its obvious ef cannot track ids in foreach loop. But maybe there are other solutions that you can guide me.
The error when I tried to add multiple entities with a child is:
The instance of entity type cannot be tracked because of another instance
with the same key value for {'Id'} is already being tracked. When
attaching existing entities, ensure that only one entity instance with
a given key value is attached.
For example:
public class Order
{
public Order()
{
OrderDetails = new HashSet<OrderDetail>();
}
[Key]
public int Id { get; set; }
[StringLength(50)]
public string Code { get; set; }
public int? CompanyId { get; set; }
public int? PartnerId { get; set; }
public decimal TotalNetPrice { get; set; }
public decimal TotalPrice { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
[ForeignKey("PartnerId")]
public virtual Partner Partner { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
[Key]
public int Id { get; set; }
public int OrderId { get; set; }
[StringLength(50)]
public string Code { get; set; }
public int LineNumber { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
[ForeignKey("OrderId")]
public virtual Order Order { get; set; }
[ForeignKey("ProductId")]
public virtual Product Product { get; set; }
}
Here is my code in the method:
foreach (var order in orderList)
{
// consider we create/cast object to Order class.
_orderService.Add(order);
// in here we don't have id, because we didn't call savechanges.
foreach(var orderDetail in order.orderDetailList)
{
// consider we create/cast object to OrderDetail class.
orderDetail.orderId = order.Id;
// in here, order id is always 0 as expected.
_order.OrderDetails.Add(orderDetail);
}
}
try
{
await uow.SaveChangesAsync();
}
catch(Exception exception)
{
var msg = exception.Message;
}
I tried to use [DatabaseGenerated(DatabaseGeneratedOption.Identity)] attribute for the identity columns.
According to documentation (https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations):
Depending on the database provider being used, values may be generated
client side by EF or in the database. If the value is generated by the
database, then EF may assign a temporary value when you add the entity
to the context. This temporary value will then be replaced by the
database generated value during SaveChanges().
So it should give at least temp id to track it. But it didn't work with my case.
I also tried the same approach on model creating a part in context. But again the same result. No success.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.Property(x => x.Id)
.ValueGeneratedOnAdd();
}
It seems like the best solution is to make a transaction and save order before details and get real id and then add details. But it has performance issues as you know.
I'm wondering if there is any other best practice for that issue?
Thank you.
Try this:
foreach (var order in orderList)
{
_orderService.Add(order);
foreach(var orderDetail in order.orderDetailList)
{
// Add reference Of Order to OrderDetails, not an id
orderDetail.Order = order;
_order.OrderDetails.Add(orderDetail);
}
}
In this case EF will know how to connect Order and OrderDetail on SaveChangesAsync
I have these tables i have made in c# using code first approach.
Employee class:
public int id { get; set; }
public string name { get; set; }
Department class:
public int id { get; set; }
public string deptName { get; set; }
public IQueryable<Employee> { get; set; }
This generates a DepartmentID in my Employee table in my sql database. I cannot however access this field in c# as DepartmentID is not a field in the employee class/model.
My question is how do i access this variable. I wish to do some various joins etc but am struggling with this.
You can certainly expose the foreign key, but it is not necessarily needed. The beauty of EF is you don't need joins.
First I would clean up your classes:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
// Exposed FK. By convention, EF know this is a FK.
// EF will add one if you omit it.
public int DepartmentID { get; set; }
// Navigation properties are how you access the related (joined) data
public virtual Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
Now you can query your data easily:
var employeeWithDepartment = context.Employee
.Include(e => e.Department)
.FirstOrDefault(e => e.ID = 123);
var employeeName = employeeWithDepartment.Name;
var departmentName = employeeWithDepartment.Department.Name;
... etc.
var departmentWithListOfEmployees = context.Departments
.Include(d => d.Employees)
.Where(d => d.Name == "Accounting")
.ToList();
... build table or something
foreach (var employee in departmentWithListOfEmployees.Employees)
{
<tr><td>#employee.ID</td><td>#employee.Name</td>
}
... close table
I have two simple classes:
class Student
{
public int StudentId { get; set; }
public int IndeksNo { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public virtual Group Group { get; set; }
}
And
class Group
{
public int GroupId { get; set; }
public string Name { get; set; }
public virtual List <Student > Students { get; set; }
}
Database is created correctly (using Code First). I have added couple items to both tables, but all the time Students list and Group property in Student are null. I have no idea why. I have searched solution for about an hour and i came up with something like this:
modelBuilder.Entity<Student>()
.HasRequired(st => st.Group)
.WithMany(gr => gr.Students)
.WillCascadeOnDelete(false);
But it doesn't help. I have no idea what may went wrong or why Group.Students and Student.Group are always null. List of groups and list of students are selected from db successfully - i mean all params except those connections.
In order to use the Lazy Loading feature of EntityFramework. Your navigation property must be virtual. In your Student class in the the case. The problem is with your Group class. The navigational property is a virtual List<Student> it must be a virtual ICollection<Student>
You can simply change your Group to
class Group
{
public int GroupId { get; set; }
public string Name { get; set; }
public virtual ICollection<Student > Students { get; set; }
}
I am getting the following exception:
I have gone through many posts here, here and here. But no post suggests proper solution to the problem. I want to know how can this situation be tackled practically.
My Models and Contexts are as follows:
public class Context : DbContext
{
public Context() : base("DefaultConnection")
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<Staff> Staffs { get; set; }
}
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Required]
public virtual Course Course { get; set; }
[Required]
public virtual Staff Staff { get; set; }
}
public class Staff
{
public int StaffId { get; set; }
public string Name { get; set; }
public string Contact { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
[Required]
public virtual Staff Staff { get; set; }
}
I am getting this exception on the line :
context.Students.Add(student);
of the following code:
public void AddStudent()
{
Student student = new Student();
student.FirstName = "Bruce";
student.LastName = "Wayne";
student.Course = new Course();
student.Course.CourseName = "CSE";
student.Course.Staff = new Staff();
student.Course.Staff.Name = "Albert";
student.Course.Staff.Contact = "1234567890";
context.Students.Add(student);
context.Courses.Add(student.Course);
context.SaveChanges();
Console.WriteLine("Student , Course, Staff Added");
}
I had asked this question some time back. This should help you out.
EF Code First giving problems in foreign keys
Reference reading here
http://weblogs.asp.net/manavi/archive/2011/05/01/associations-in-ef-4-1-code-first-part-5-one-to-one-foreign-key-associations.aspx
The main part to look for in the article is "What's a Multiple Cascade Path Anyway?"
To solve the problem practically you need to identify which path do you want the cascade delete to be turned on. For e.g. If the a staff gets deleted does the course also get deleted or does it remain ?
Disabling cascading deletes for that entity should solve your issue. If you want a cascading delete for this set of entities, do it manually. It can't be done automatically because when there's a cycle there's no way to determine when to stop.