Im having two models:
public class Customer
{
public int Id { get; set; }
public int Number { get; set; }
public int ParentNumber { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string Language { get; set; }
}
and
public class Batch
{
public int Id { get; set; }
public int Number { get; set; }
public string FileName { get; set; }
public string ArticleNumber { get; set; }
public string ArticleDescription { get; set; }
public int Weight { get; set; }
public DateTime ProductionDate { get; set; }
public DateTime DeliveryDate { get; set; }
public DateTime BestBeforeDate { get; set; }
public DateTime? ApprovedDateTime { get; set; }
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
One batch can have a customer attached to it. But since we're importing the data from another system we decided not to take over their id's.
Right now the foreign key says try to find a customer by the property Customer.Id
I'm trying to achieve to get the foreign key point to Customer.Number from Batch.Customer(Id)
How would i succeed in this?
I've tried by defining the Customer.Number to be a Key with the Key attribute.. but this made the primary key go from Id to Number which is not what i wanted...
What are you asking was impossible in EF prior to EF Core. Fortunately in EF Core it can be done by using Alternate Keys feature. But please note that in order to be able to use it, your Cusomer.Number field should be unique.
The solution requires Fluent API configuration.
Start by defining Customer.Number as alternate key:
modelBuilder.Entity<Customer>()
.HasAlternateKey(e => e.Number);
Then set up the relationship as follows:
modelBuilder.Entity<Batch>()
.HasOne(e => e.Customer)
.WithMany()
.HasForeignKey(e => e.CustomerId)
.HasPrincipalKey(e => e.Number);
The last two lines will do what you are seeking for.
As a side note, it would be better to name the property (and column) CustomerNumber in order to avoid confusion of what the value is in it.
Related
My Entities:
public partial class Student: IBrand
{
public Student()
{
Grades = new HashSet<Grade>();
}
[Key]
public int StudentId { get; set; }
[ForeignKey("Users")]
public string UserId { get; set; }
public ApplicationUser User { get; set; }
[ForeignKey("Parent")]
public int? ParentId { get; set; }
public Parent Parent { get; set; }
public int? SectionId { get; set; }
public string FatherName { get; set; }
public int classNumber { get; set; }
public Section Section { get; set; }
public string brevetResult { get; set; }
public DateTime? dateLeftAec { get; set; }
public string additionalInfo { get; set; }
public bool bacc { get; set; }
public string baccResult { get; set; }
public string baccSection { get; set; }
public int BrandId { get; set; }
[JsonIgnore]
public Brand Brand { get; set; }
public virtual ICollection<StudentRegistration> StudentReg { get; set; }
public virtual ICollection<Grade> Grades { get; set; }
public virtual ICollection<Absence> Absences { get; set; }
public virtual ICollection<StudentStudyYear> StudentStudyYears { get; set; }
}
public class Grade: IBrand
{
public int gradeId { get; set; }
public int grade { get; set; }
public int StudentId { get; set; }
public int SubjectId { get; set; }
public int TeacherId { get; set; }
public int TermId { get; set; }
public int SectionId { get; set; }
public bool IsApproved { get; set; }
public string ResultToEdit { get; set; }
public bool IsEditedByAdmin { get; set; }
public virtual Student Student { get; set; }
public virtual Subject Subject { get; set; }
public virtual Teacher Teacher { get; set; }
public virtual Term Term { get; set; }
public virtual Section Section { get; set; }
public int BrandId { get; set; }
[JsonIgnore]
public Brand Brand { get; set; }
public virtual ICollection<GradeStudyYear> GradeStudyYears { get; set; }
}
I'm trying to get a student with his grades, but I encountered a problem with circular references. I tried to add this to the startup file:
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
That seemed to solve the circular references problem, but then I started getting 5GB worth of data... so that code apparently just suppressed the error without solving the circular data problem.
I tried to put the attribute [JSONIgnore] in my grade.cs file, but I need to get a student from grade so it will not be useful.
How can I solve this circular references problem?
Serializing entities leads to a number of issues and exposes more information about your domain than is needed. When returning data to a view or an API consumer you can instead define a view model or DTO to contain just the details that consumer will need in whatever structure best serves that need. This avoids reference issues which EF navigation properties can cause, reduces the amount of domain knowledge and data you expose to clients, and minimizes the payload size to just what is needed. Data can be flattened, so if you are displaying a list of one entity, you don't need a ViewModel/DTO per related entity, your view model can merely contain relevant details of any related entity that applies to that consumer.
Once you have defined your view model / DTO you can use .Select() to populate it, or set up mapping with Automapper and populate it using .ProjectTo<TViewModel>().
I am using the Sakila Sample Database from MySql on a MySql server. The Diagram looks as follows.
The important tables are the store, inventory and film tables. The is a many-to-many relationship between the tables and the linker table is the inventory table.
I scaffolded this Database in a new dotnetcore project using EFCore 2.
I am trying to get a list of stores and their list of films.
The Entities are defined as follows:
Store
public class Store
{
public Store()
{
Customer = new HashSet<Customer>();
Inventory = new HashSet<Inventory>();
Staff = new HashSet<Staff>();
}
public byte StoreId { get; set; }
public byte ManagerStaffId { get; set; }
public short AddressId { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Address Address { get; set; }
public Staff ManagerStaff { get; set; }
public ICollection<Customer> Customer { get; set; }
public ICollection<Inventory> Inventory { get; set; }
public ICollection<Staff> Staff { get; set; }
}
Inventory
public partial class Inventory
{
public Inventory()
{
Rental = new HashSet<Rental>();
}
public int InventoryId { get; set; }
public short FilmId { get; set; }
public byte StoreId { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Film Film { get; set; }
public Store Store { get; set; }
public ICollection<Rental> Rental { get; set; }
}
Film
public partial class Film
{
public Film()
{
FilmActor = new HashSet<FilmActor>();
FilmCategory = new HashSet<FilmCategory>();
Inventory = new HashSet<Inventory>();
}
public short FilmId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public short? ReleaseYear { get; set; }
public byte LanguageId { get; set; }
public byte? OriginalLanguageId { get; set; }
public byte RentalDuration { get; set; }
public decimal RentalRate { get; set; }
public short? Length { get; set; }
public decimal ReplacementCost { get; set; }
public string Rating { get; set; }
public string SpecialFeatures { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Language Language { get; set;
public Language OriginalLanguage { get; set; }
public ICollection<FilmActor> FilmActor { get; set; }
public ICollection<FilmCategory> FilmCategory { get; set; }
public ICollection<Inventory> Inventory { get; set; }
}
My context looks as follows:
modelBuilder.Entity<Inventory>(entity =>
{
entity.ToTable("inventory", "sakila");
entity.HasIndex(e => e.FilmId)
.HasName("idx_fk_film_id");
entity.HasIndex(e => new { e.StoreId, e.FilmId })
.HasName("idx_store_id_film_id");
And lastly the repo looks as follows:
public IEnumerable<Store> GetStores()
{
return _context.Store.
Include(a => a.Inventory).
ToList();
}
Problem:
When I call this method from a Controller to get the list of stores I don´t get any json response on Postman. Yet if I debug into the list that is returned from the Controller I find the list of stores.
The problem is that the list contains:
store->inventory->film->store->inventory->film->store... Etc. Creating a circular dependency that fills up the allowed Process memory of the request.
Possible Solutions:
I think it has to do with the fact that on the Context both the Foreign Keys are defined as HasIndex instead of HasKey
entity.HasIndex(e => new { e.StoreId, e.FilmId })
.HasName("idx_store_id_film_id");
When I define it as HasKey then I get an Error:
'The relationship from 'Rental.Inventory' to 'Inventory.Rental' with
foreign key properties {'InventoryId' : int} cannot target the primary
key {'StoreId' : byte, 'FilmId' : short} because it is not compatible.
Configure a principal key or a set of compatible foreign key
properties for this relationship.'
To answer #hamzas comment, I did find a solution to this problem. I used EFCore to build the entities and the DBContext through scaffolding (DB First). As a best practice you should be using Models (Dtos) to represent the Data for the client. EFCore is very helpful in giving us the flexibility to access this M to N relationship however we want. This gives us the flexibility to represent this Data to the client however we want.
Whatever your use case might be. You have to convert the M to N relationship into an 1 to N model.
Use Case #1: You want to show all the movies for a specific store.
Solution
Step #1: You create a StoreDto (Model)
public class StoreDto
{
int StoreId { get; set; }
ICollection<FilmDto> Films { get; set; }
= new List<FilmDto> ();
}
Step #2: Create a FilmDto
public class FilmDto
{
int FilmId { get; set; }
int StoreId { get; set; }
string FilmName { get; set; }
}
Step #3: You provide a Mapping with auto mapper
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<Store, StoreDto>();
CreateMap<Film, FilmDto>();
}
}
Step #4: Query the data correctly, Unfortunately I don´t have this example anymore to test this code, so here is where you´ll have to experiment a bit
public Store GetFilmsForStore(byte StoreId)
{
return _context.Store.
Include(a => a.Inventory).
ThenInclude(i => i.Film)
ToList();
}
On the "Include" part you want to only get the Inventory entries where StoreId == Inverntory.StoreId and then Include the Films Object from the resulting list.
I hope you get the jist of it. You want to break up your m to n relationships and make them seem like 1 to m for your clients.
I am writing a PhoneGap/Web/JS mobile application that uses the WebAPI and Entity Framework in the backend.
I have a class called Thing which references the User table 4 times (ChangedByUserId, CreatedByUserId etc. The User table is really large (30 user-related fields)
I want to pass as little data over each call as possible, but I need the User's Name for each of these UserID foreign keys. (this is the only information from the user record I need).
When I use the object graph in EF it returns the FULL user record for each foreign key, so a single Thing object becomes massively bloated. I don't want to make multiple calls to get the Thing POCO object and then the User's name by UserID.
What I really want to do is a sort of flattened DTO object which is just the Thing class below but with a string for each user name, e.g. CreatedByUserName, ChangedByUserName etc. Then I would return this DTO as my hydrated POCO object and the data would be small.
So my question is: How do I do this using Entity Framework? (limit related record's return data?)
public partial class Thing
{
public int ThingId { get; set; }
public int FromUserId { get; set; }
public int ToUserId { get; set; }
public string ThingText { get; set; }
public int StatusId { get; set; }
public int ChangedByUserId { get; set; }
public int CreatedByUserId { get; set; }
public virtual User FromUser { get; set; }
public virtual User ToUser { get; set; }
public virtual User CreatedByUser { get; set; }
public virtual User ChangedByUser { get; set; }
}
As you said, you need to flatten Thing
public class FlatThing
{
public int ThingId { get; set; }
public int FromUserId { get; set; }
public int ToUserId { get; set; }
public string ThingText { get; set; }
public int StatusId { get; set; }
public int ChangedByUserId { get; set; }
public int CreatedByUserId { get; set; }
public string FromUserName { get; set; }
public string ToUserName{ get; set; }
}
// assume you have your things
var flatThings = new List<FlatThings>;
foreach (Thing t in things)
flatThings.Add(new FlatThing{ ThingId = t.ThingId, FromUserId = t.FromUserId,
FromUserName = t.FromUser.Name .....});
return flatThings;
Let's use a simple example:
public class Employee
{
public int EmployeeID { get; set; }
public ICollection<Pay> Pays { get; set; }
}
public class Pay
{
public Employee Employee { get; set; }
public int Year { get; set; }
public double Amount { get; set; }
}
Is there any way to use the fluent API to create a Pays table with a primary key on Employee_EmployeeID, Year (using EF4.1 column conventions)?
I don't want to use data annotations but I tried this anyway:
public class Pay
{
[Key, Column(Order = 0)]
public Employee Employee { get; set; }
[Key, Column(Order = 1)]
public int Year { get; set; }
public double Amount { get; set; }
}
All that got me was a primary key on Year and a foreign key on Employee_EmployeeID though.
This will be only possible if you also add FK property to your Pay:
public class Pay
{
public int EmployeeId { get; set; }
public Employee Employee { get; set; }
public int Year { get; set; }
public double Amount { get; set; }
}
Now you can map it with fluent-api:
modelBuilder.Entity<Pay>()
.HasKey(p => new
{
p.EmployeeId,
p.Year
});
You need employee's FK as a property if you want to make it part of PK.
I want to created Bi-Directional One-One relationship between two entities using EF Code First. I have trouble with the following code. What do you think I should do?
public class User
{
public string ID { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public int ProfileID { get; set; }
public Profile Profile { get; set; }
}
public class Profile
{
public int UserID { get; set; }
public User User { get; set; }
public int ProfileID { get; set; }
public string ProfileName { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastUpdateDate { get; set; }
}
I want to have both Navigation property and Foreign Key in both the entities.
This gives me error. What can do I in Fluent Mapping API to make this work?
Use this:
public class User
{
public string ID { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public Profile Profile { get; set; }
}
public class Profile
{
[Key, ForeignKey("User")]
public int ProfileID { get; set; }
public string ProfileName { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastUpdateDate { get; set; }
public User User { get; set; }
}
That is the only valid way to build one-to-one relation in EF - PK of the dependent entity must be also FK to principal entity. There is nothing like bidirectional one-to-one relation in EF because it cannot work in EF.
The way how people sometimes overcome this are two one-to-many relations where principal doesn't have navigation collection for dependent entities + manually defined unique keys in the database. That require manual mapping:
public class User
{
public string ID { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
// one side MUST be nullable otherwise you have bidirectional constraint where each
// entity demands other side to be inserted first = not possible
public int? ProfileId { get; set; }
public Profile Profile { get; set; }
}
public class Profile
{
public int ProfileID { get; set; }
public string ProfileName { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastUpdateDate { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
And in mapping you will define:
modelBuilder.Entity<User>
.HasOptional(u => u.Profile)
.WithMany()
.HasForeignKey(u => u.ProfileId);
modelBuilder.Entity<Profile>
.HasRequired(u => u.User)
.WithMany()
.HasForeignKey(u => u.UserId);
Now you must define Unique keys in the database - if you are using code first use custom database initializer. Be aware that still bidirectional one-to-one is wrong concept because both sides demand unique FK where NULL is still included in unique values so once you insert User before Profile there mustn't be any other User without Profile. That probably leads to serializable transaction.