How to update related entities using GraphDiff? - c#

I have the following model:
public class Customer
{
public int Id {get; set;}
public string Name {get; set;}
public int AddressId {get; set;}
public virtual Address Address {get; set;}
public virtual ICollection<CustomerCategory> Categories {get; set;}
}
public class CustomerCategory
{
public int Id {get; set;}
public int CustomerId {get; set;}
public int CategoryId {get; set;}
public virtual Category Category {get; set;}
}
public class Address
{
public int Id {get; set;}
public string Street{get; set;}
public virtual PostCode PostCode {get; set;}
}
From the above, and using GraphDiff, I want to update the customer aggregate as follows:
dbContext.UpdateGraph<Customer>(entity,
map => map.AssociatedEntity(x => x.Address)
.OwnedCollection(x => x.Categories, with => with.AssociatedEntity(x => x.Category)));
But the above is not updating anything!!
What is the correct way to use GraphDiff in this case?

GraphDiff basically distinguishes two kinds of relations: owned and associated.
Owned can be interpreted as "being a part of" meaning that anything that is owned will be inserted/updated/deleted with its owner.
The other kind of relation handled by GraphDiff is associated which means that only relations to, but not the associated entities themselves are changed by GraphDiff when updating a graph.
When you use the AssociatedEntity method, the State of the child entity is not part of the aggregate, in other words, the changes that you did over the child entity will not be saved, just it will update the parent navegation property.
Use the OwnedEntity method if you want to save tha changes over the child entity, so, I suggest you try this:
dbContext.UpdateGraph<Customer>(entity, map => map.OwnedEntity(x => x.Address)
.OwnedCollection(x => x.Categories, with => with.OwnedEntity(x => x.Category)));
dbContext.SaveChanges();

Related

Abstract Class with 1 to 1 relationship Entity Framework

Using entity framework I've been trying to create this relationship. Basically I have 1 object which has a Result. The Result object is abstract, as it has to be one of the 3 classes that inherit from Result, i.e. Approved, Rejected, or Modified:
I'm trying to create the table structure using Entity Framework. Originally I was going for a TPCT (Table Per Concrete Type) structure, so there would be no Result table, but I wanted to keep the link back in the Action table if I wanted to reference the Result, so now I'm attempting just TPT structure. I find TPCT is cleaner, but ultimately if TPT is the only way to achieve what I want, I'm fine with it.
I've tried variations of the following for my model structure:
public class Action
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
public int Result_Id {get; set;}
[ForeignKey("Result_Id")]
public virtual Result Result {get; set;}
public string Description {get; set;}
}
public abstract class Result
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[Required]
public int Action_Id {get; set;}
[ForeignKey("Action_Id")]
public virtual Action Action {get; set;}
public string Comment {get; set;}
public class Approved : Result
{
public string Thing {get; set;}
}
public class Rejected : Result
{
public string Stuff {get; set;}
}
public class Modified : Result
{
public string Whatever {get; set;}
}
}
And then I've tried the following 2 strategies in my context file to either implement TPT:
modelBuilder.Entity<Approved>().ToTable("Approved");
modelBuilder.Entity<Rejected>().ToTable("Rejected");
modelBuilder.Entity<Modified>().ToTable("Modified");
Or for TCPT:
modelBuilder.Entity<Approved>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Approved");
});
modelBuilder.Entity<Rejected>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Rejected");
});
modelBuilder.Entity<Modified>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Modified");
});
Everytime I try to add the new migration, whatever I try, I'm faced with this error:
Unable to determine the principal end of an association between the types 'Result' and 'Action'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
The one time I was able to have it work was if I removed this reference from in the Action class:
public int Result_Id {get; set;}
[ForeignKey("Result_Id")]
public virtual Result Result {get; set;}
But I would really like to keep that reference there so then when I go into my DB to grab that Action object, I can immediately tell if there is a Result associated to it, without having to go through all 3 Result tables to see if there is a reference to that Action (which is why I think I need to have TPT...)
Any help to get this working would be greatly appreciated!
With a lot of research and trial and error, I discovered what I needed to get the result I wanted. It's TPCT DB structure, and the Action object is able to keep the reference to Result. Here are the model classes:
public class Action
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
public virtual Result Result {get; set;} //just virtual here, as Action is the dependent and Result is the principal-- i.e. this Result isn't required
public string Description {get; set;}
}
public abstract class Result
{
//got rid of the Result_Id, since it's 1:1 the Action_Id can be the Key
[Required, Key] //added "Key"
public int Action_Id {get; set;}
[ForeignKey("Action_Id")]
public Action Action {get; set;} //removed this virtual, as Action is Required for Result, that makes Result the principal
public string Comment {get; set;}
public class Approved : Result
{
public string Thing {get; set;}
}
public class Rejected : Result
{
public string Stuff {get; set;}
}
public class Modified : Result
{
public string Whatever {get; set;}
}
}
And here is the fluent API code from the context:
//this gave me TPCT like I wanted
modelBuilder.Entity<Approved>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Approved");
});
modelBuilder.Entity<Rejected>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Rejected");
});
modelBuilder.Entity<Modified>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Modified");
});
//this defined the principal-dependent relationship I was missing
modelBuilder.Entity<Action>()
.HasOptional(a => a.Result)
.WithRequired(a => a.Action)
.Map(x => x.MapKey("Action_Id"));
And then it worked! Hopefully this example can assist someone else.

How to get records from table that depends on another tables in entity framework core?

I want to get some records from Database that depends on three tables.
Three tables are:
1.Company(Id,Name)
2. Car(Id,CompanyId,Name)
3. Showroom(Id,CarId,Name)
Now a one company contains many cars and many cars may exist in many showrooms.
I want to get records from showroom table where company '2' cars exist along with cars. Is it possible to do it in entity framework core?
I think your entities will be like :
Company
public class Company
{
public int Id {get; set;}
public string Name {get; set;}
public ICollection<Car> Cars {get; set;}
}
Car:
public class Car
{
public int Id{get; set;}
public string Name {get; set;}
public int CompanyId{get; set;}
public Company Company {get; set;}
}
ShowRoom:
public class ShowRoom
{
public int Id{get; set;}
public string Name {get; set;}
public int CarId{get; set;}
public Car Car{get; set;}
}
In your method:
var context = new SomeContext();
var showRooms= context.ShowRooms
.Include(x=> x.Car)
.ThenInclude(x=> x.Company)
.Where(x=> x.Car.Company.Id== 2)
.ToList();

How to return this result using linq in entity framework( 2 foreign key mapping table)

I have a table that contains 2 foreign key that reference separately to 2 different table.
I would like to return the result of all person that has course of "Science".
How to retrieve the record back using LINQ?
This is what i gotten so far:
return
_ctx.Person
.Include(u => u.Course
.Where(ug=>ug.CourseName== "Science"));
This is not working as it shows the error.
The Include path expression must refer to a navigation property
defined on the type
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<Person> Persons { get; set; }
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<Course> Courses { get; set; }
}
This is the mapping table. Only contains 2 foreign key from 2 different table.
I could not use this table inside the solution.As the code first won't generate this table as it doesn't contain it's own PK.
//This is not shown in the EntityFramework when generating Code First.
public class PersonCouseMap
{
public int PersonID {get; set;}
public int CourseID {get; set;}
}
Update : this works after I switched the entity.
return _ctx.Course
.Include(u=>u.Person)
.Where(ug=>ug.CourseName == "Sciene");
Anyone can explain why it won't work the another way round.
I need to display a List of Person who have course of "Science",
not Course Science that has a list of user.
The original query does not work because you've pushed the Where predicate inside the Include expression, which is not supported as indicated by the exception message.
The Include method is EF specific extension method used to eager load related data. It has nothing to do with the query filtering.
To apply the desired filter person that has course of "Science" you need Any based predicate since the Person.Courses is a collection:
return _ctx.Person
.Where(p => p.Courses.Any(c => c.CourseName == "Science"));
To include the related data in the result, combine it with Include call(s):
return _ctx.Person
.Include(p => p.Courses)
.Where(p => p.Courses.Any(c => c.CourseName == "Science"));
It looks like there is no relations between these two entites, you can establish a relationship by making the following changes to your code:
Here I am assuming that you want to establish Many-to-Many relationship between these two tables by having a third entity PersonCourseMap
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<CoursePersons> Courses { get; set; }
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<PersonCourse> Courses { get; set; }
}
public class PersonCourseMap
{
public int PersonID {get; set;}
public int CourseID {get; set;}
public virtual ICollection<Person> Persons { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
After making above changes you can simply navigate through properties.
Include Foreign Key Mapping
public class Course
{
public int CourseID {get; set;}
public string CourseName {get; set;}
public virtual ICollection<Person> Person {get; set}
}
public class Person
{
public int PersonID {get; set;}
public virtual ICollection<Course> Course {get; set;}
}
using System.ComponentModel.DataAnnotation.Schema;
public class PersonCouseMap
{
[ForeignKey("Person")]
public int PersonID {get; set;}
[ForeignKey("Course")]
public int CourseID {get; set;}
public virtual ICollection<Person> Person {get; set;}
public virtual ICollection<Course> Course {get; set;}
}

LINQ/Lambda - Getting parent object property while using Include to obtain children

I am using this "model" from a previous question. Here I have a Vehicle,VehicleType, and Price entity.
public class Vehicle
{
public int Id {get; set;}
public string Name {get; set}
public int VehicleTypeId {get; set;}
public virtual VehicleType VehicleType {get; set;}
}
public class VehicleType
{
public int Id {get; set;}
public string VehicleTypeName {get; set}
public ICollection<Vehicle> Vehicles {get; set;}
public ICollection<Price> Prices {get; set;}
}
public class Price
{
public int Id {get; set;}
public int Price {get; set;}
public int VehicleTypeId {get; set;}
public virtual VehicleType VehicleType {get; set;}
}
The VehicleType entity serves as a relationship to both the Vehicle entity and the Price entity. I am trying to use LINQ to get at the Price for a given Vehicle based upon it's VehicleType...
// Query #1 to get `Vehicle` name
var vehicle = dbContext.Vehicles.SingeOrDefault(v => v.Id = 1234);
string vehicleName = vehicle.Name;
// Query #2 to get lowest `Price` for `Vehicle`
var myVehiclePrice = dbContext.Vehicles.Include("VehicleType.Prices")
.SingleOrDefault(v => v.Id == 1234)
.VehicleType.Prices
.OrderBy(p => p.PriceAmount).FirstOrDefault()
I have two queries to get the information I want. Is there a way to combine these two queries together to make one trip to the database? I tried reusing the vehicle variable obtained from the first query, but it represents a single entity and cannot make use of the Include() extension which only works off of an object query.
I think you are misinterpreting the Include. It's used for eagerly loading the entity content. It's not needed at all for queries that use selective projection of entity properties/aggregates.
You can gather the vehicle name and the lowest price with a single database trip simply with
var info = dbContext.Vehicles
.Where(v => v.Id == 1234)
.Select(v => new
{
VehicleName = v.Name,
LowestPrice = v.VehicleType.Prices.Min(p => (int?)p.Price)
})
.SingleOrDefault();

Should foreign Id properties be mapped from Model to Dto?

If I have the following model:
public class Customer
{
public int Id {get; set;}
public int CustomerTypeId {get; set;}
public virtual CustomerType {get; set;}
}
Should the Dto exclude foreign Id's to look like this:
public class CustomerDto
{
public int Id {get; set;}
public virtual CustomerType {get; set;}
}
And when using Graphdiff to update the object graph, will EF know that CustomerType maps to CustomerTypeId?
Yes, you need to use it but you can avoid virtual member declaration. If you use AutoMapper, then the mapping will be done automatically. So, your Dto will look like this:
public class CustomerDto
{
public int Id {get; set;}
public int CustomerTypeId {get; set;}
}
And the mapping:
Mapper.CreateMap<Customer, CustomerDto>();
Mapper.CreateMap<CustomerDto, Customer>();

Categories