Code First two objects having other object - c#

using entity framework code first I have this :
public class Person {
public int PersonId {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public virtual List<Note> Notes {get;set;}
}
public class Product {
public int ProductId {get;set;}
public string Name {get;set;}
public virtual List<Note> Notes {get;set;}
}
public class Note {
public int NoteId {get;set;}
public string Body {get;set;}
public virtual User Author {get;set;}
// what should be here ?
}
How can I know if a note is from a Person or a Product ?
Should I need different classes (PersonNote, ProductNote) for that ?
Can I use a Interface approach ? like INoteable ?
I'm not sure what's the best strategy for the DB to be modeled or the classes.
Any suggestion is appreciated.
Thanks,
EDIT (based on answers)
the suggested answer means that for every entity that has notes, the table will have a new column which i don't like much. Is there any way to use a discriminator ? I don't like the idea of modifying the table everytime a new entity can have notes and potentially end up with like 10 FK (9 always null) if I have 10 entities that supports notes.
I ideally want to have something like an Interface (or whatever) so I can have in code, Product : INoteable and by that it means that a note can be created using this id.
Maybe with a discriminator column. Is that even possible ?
Sorry for not being clear the first time.
In real life scenario I have like : Products, Persons, Purchase Orders, Sales, Payments, Payment Orders, and a few more entities that I need to implement notes.
EDIT 2 :
What about this Database structure :
TABLE: Note
int PK NoteId
int FK NoteDataId
string Body
TABLE: Person
int PK PersonId
int FK NoteDataId
TABLE: Product
int PK ProductId
int FK NoteDataId
TABLE: NoteData
int PK NoteDataId
with this data structure, all entities that want to implement Notes I just add a navigation property NoteDataId and when creating notes, I just give the NoteDataId value. I think EF will take care of the creation of NoteDataId row if it doesn't exists.
EDIT 3:
Example data:
Person:
PersonId 1
Name Bart
NoteDataId 1
PersonId 2
Name Alex
NoteDataId 2
Product:
ProductId 1
Name "Dulce de Leche"
NoteDataId 3
NoteData:
NoteDataId 1
NoteDataId 2
NoteDataId 3
Note:
NoteId 1
NoteDataId 1
Body "first note"
NoteId 2
NoteDataId 1
Body "second note of Bart"
NoteId 3
NoteDataId 2
Body "first note of Alex"
NoteId 4
NoteDataId 3
Body "Note about Dulce de Leche"
how to get the notes of some person ?
SELECT * FROM Note
JOIN NoteData USING (NoteDataId)
JOIN Person USING (NoteDataId)
WHERE PersonId = 1
backwards ?
SELECT * FROM Note
JOIN NoteData USING (NoteDataId)
LEFT JOIN Person USING (NoteDataId) 'can be null, only one type exists'
LEFT JOIN Product using (NoteDataId) 'can be null, only one type exists'
WHERE NoteId = 2

Create nullable foreign keys:
public class Note {
public int NoteId {get;set;}
public string Body {get;set;}
public virtual User Author {get;set;}
public int? PersonId { get; set; }
public virtual Person Person { get; set; }
public int? ProductId { get; set; }
public virtual Product Product { get; set; }
}
You have to map the properties as optional.
Configuration for Note:
Property(n => n.PersonId).IsOptional();
Property(n => n.ProductId).IsOptional();
Configuration for Person and Product:
Property(p => p.Note).IsOptional();
Update:
You could also use the table-per-hierarchy solution.
Leave Note the same (might as well make it abstract) and create derived types. For example PersonNote:
public clas PersonNote : Note
{
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
Where Person gets a navigation property:
public virtual PersonNote Note { get; set; }
And ProductNote:
public clas ProductNote : Note
{
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
Where Product gets a navigation property:
public virtual ProductNote Note { get; set; }
Entity Framework will create one Note table containing all the properties of the derived types and a discriminator column. But this will result in a lot of classes in your code, but that's not a bad thing in my opinion.
Update 2
You could also let go of the navigation property from Note to a Product or Person if you don't need it. This will keep your code a lot simpler. You can leave Note as it is, and add navigation and foreign key properties to your other entities:
public class Person
{
// properties..
public int NoteId { get; set; }
public virtual Note Note { get; set; }
}

Edited: Thinking about it allover. This doesn't solve the first issue, we only added another table. Why don't you add a note type column that map to an enum.
According to your last edit, this is how I would do the code
public class Person {
public int PersonId {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int NoteData NoteDataId{get;set;}
[ForeignKey("NoteDataId")]
public virtual NoteData NoteData{get;set;}
}
public class Product {
public int ProductId {get;set;}
public string Name {get;set;}
public int NoteData NoteDataId{get;set;}
[ForeignKey("NoteDataId")]
public virtual NoteData NoteData{get;set;}
}
public class NoteData {
public int NoteDataID {get;set;}
public virtual List<Note> Notes {get;set;}
}
public class Note {
public int NoteId {get;set;}
public string Body {get;set;}
public int NoteData NoteDataId{get;set;}
[ForeignKey("NoteDataId")]
public virtual NoteData NoteData{get;set;}
}

I'm not sure how it would work for EF, but in our model we use a generic "Parent" property that returns an object. This only works if you only have one object that owns the note and not many objects referencing the note.
public virtual object Parent { get; set; }
However, I don't think EF will automatically populate that for you...

Related

Store foreign key depending on the enum

Few days ago I switch (due to my student project) to Entity Framework and I have to develop Entity whos foreign key will depend of ENUM value , I spend last two day trying to figure it out but unfortunately I was not able to figure it out , so I hope someone here will be able to help me with it :)
Seller.cs
Public int Id {get;set}
public string FullName {get;set}
public string Country {get;set}
public int CentralizationId {get;set}
[ForeignKey("CentralizationId"}
public Centralization Centralization {get;set;}
Buyer.cs
Public int Id {get;set}
public string FullName {get;set}
public string Country {get;set}
public CurrencyType CurrencyType {get;set;}
public int CentralizationId {get;set}
[ForeignKey("CentralizationId"}
public Centralization Centralization {get;set;}
Centralization.cs
public int Id { get; set; }
public int AuthorId { get; set; }
[ForeignKey("UserId")]
public Author Author { get; set; }
public Type Type { get; set; }
[ForeignKey("TypeId")]
public int TypeId { get; set; }
public enum Type
{
Selling = 1,
Buying = 2,
}
So basically what I need is that if Type = 1 on typeId to be Seller.Id where later via getAllIncluding I will be able to get his date (somehow typeId should depend on Type)
I tried using Getters and Setters but didn`t help at all
How I think at the end should looks like for example :
Type = 1 (Selling)
[ForeignKey("TypeId")]
public Seller typeId {get;set;}
Hope someone here will be able to help me :)
Have a nice day !
You can use an Enum for a FK/PK. EF will typically treat this as an int in the database.
For instance if in a database I want a "Type" table with an TypeId to enforce referential integrity but I don't necessarily want a "Type" entity, I just want an enumeration to represent that type:
public enum Type
{
None = 0,
Buying = 1,
Selling = 2
}
public class Something
{
public int Id { get; set; }
// ...
[Column("TypeId")]
public Type Type { get; set; }
}
I would recommend a name that is more descriptive than just "Type" to avoid naming collisions with system types. I.e. "OrderType", "TransactionType" etc.
The [ForeignKey] attribute is used to nominate the FK fields for relationships between entities. In this case we aren't using an entity, but an enumeration so we just use [Column] to tell EF what the column should be named.
The above works for DB-First implementations. If you want to use Code-First where the EF definitions will be responsible for creating the schema, you will need to manually add migrations to create the "Type" table and set up the FK between it and your other table. Otherwise EF will merely leave that TypeId column as a regular column on the "Something" table.
Alternatively you can use Enums as a FK/PK which can allow you to define additional properties to go along with the relationship, and let Code First manage the table relationships as well:
public enum TypeIds
{
None = 0,
Buying = 1,
Selling = 2
}
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public TypeIds TypeId { get; set; }
public string Description { get; set; }
}
public class Something
{
[Key]
public int Id { get; set; }
// ...
[ForeignKey("Type")]
public TypeIds TypeId { get; set; }
public virtual Type Type { get; set; }
}
This allows you to use the Enumeration as both a PK of an entity containing relevant details about the enumeration, and as the FK within the related entities.
Enumerations as PK/FK are useful in cases where you might have business logic dependent on those states. The important thing to consider with enumerations is that the Application rather than the database should be in control of the ID assignment, hence the use of [DatabaseGenerated(DatabaseGeneratedOption.None)] on the PK of our Type entity/table. This tells EF that the database should not use an Identity but rely on the consumer (our code) to set these IDs. We want to ensure that it is as clear as possible that our Enum is the source of truth for these keys.

Entity Framework .Net Core 3.1 - Code First vs Scaffolding

What I'm trying to do is implementing the model in Code First for this basic entities:
public class CompanyType{
public int Id {get;set;}
public string CompanyType {get;set;}
}
public class Company{
public int Id {get;set;}
public string Company {get;set;}
public string idCompanyType {get;set;} //Related 1-1 to CompanyType
}
public class Employee{
public int Id {get;set;}
public string Company {get;set;}
public int idCompany {get;set;} // --> Here I have to relate idCompany with CompanyId ( 1 company , N Employee
}
Questions are:
What is the correct way to implement the relations in Code First ?
Since the database of the application I have to realize will be very big, Can be a good approach designing the database in SqlServer and then proceed to scaffold the tables ?
Thanks to support
public class CompanyType{
public int Id {get;set;}
public string CompanyType {get;set;}
}
public class Company{
public Company()
{
Employees = new HashSet<Employee>();
}
public int Id {get;set;}
public string Company {get;set;}
public int CompanyTypeID
public virtual CompanyType CompanyType {get;set;}
public virtual ICollection<Employee> Employees { get; set; }
}
public class Employee {
public int Id {get;set;}
public int CompanyID {get;set;}
public virtual Company Company {get;set;}
}
public class SomeContext : DbContext {
public SomeContext() : base("SomeContext")
{
}
public DbSet<CompanyType> CompanyTypeSet { get; set; }
public DbSet<Employee> EmployeeSet { get; set; }
public DbSet<Company> CompanySet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
This is basically how to set up your relations in EF code first
Summary
Create Data Model
Create Database Context
Setup EF
Code first Migration
you can find notes on step 3 and 4 here
Get Started with Entity Framework 6 Code First
for the second part of your question you can refer to these links to weigh your options
what is advantage of CodeFirst over Database First
EF Code-First Approach Vs Database-First Approach
3 reasons to use code first design
As far as my point of view , should be dependant on a person, how he/she is comfortable. If you are good at SQL side, design db first then scaffold. If you are good at c# side, use code first approach
1) No comment as I never use it.
2) Database-First approach is the most effective to do. Save alot of your time. Yes, design tables in SQL Server, then run Scaffold-DbContext.

Multiple foreign key relationships for one record in asp.net mvc, code-first

I have a very simple question. I am new to ASP.NET MVC and very much confused about relationships while following code-first technique.
I have two model classes. I want to describe it as one person can have many courses.
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId { get; set; }
}
public class Course
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CourseId { get; set; }
// public virtual ICollection<Course> Courses{ get; set; }
/* I removed above line from model because it was not creating any Course Field in Db table of Person and added a third table */
}
In order to make a relationship I created another model class that contains Id of persons and repeating Id's of the course
public class ModelJoin
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ModelJoinId { get; set; }
[ForeignKey("Person")]
public int PersonId { get; set; }
public virtual Person Person{ get; set; }
//One to many relationships
public virtual ICollection<Course> Courses{ get; set; }
}
So model join will have only two properties. I want to ask how we achieve this in a best way.
Value of courses will always be null so we can not add any course in it. Where in the code we will assign it a object?
There are a lot of questions on stackoverflow but no one describes it from scratch.
Is there any tutorial for add update delete tables with foreign keys.
Any help would be appreciated.
I think you need many to many relationship as one student can be enrolled for many courses and one course can be taken by many students.
Look at this:
http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
The course collection should be in the Person class, try this :
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CourseId { get; set; }
}
So for example if you want to add a person with a course.
var person = new Person();
var courses = new List<Course>();
courses.Add(new Course());
person.Courses = courses;
dbContext.Persons.Add(person);
dbContext.SaveChanges();

Entity Framework TPH Factory Method

I currently have a MVC5 site with a TPH relationship with classes as follows:
public abstract class product{
public int productID {get;set;}
}
public class toy : product {
public virtual List<ChildComment> Comments {get;set;}
public virtual List<AdultComment> Comments {get;set;}
}
public class tool : product {
public virtual List<AdultComment> Comments {get;set;}
}
public class ChildComment {
public CommentID {get;set;}
public string commentText {get;set;}
public virtual product Product {get;set;}
}
public class AdultComment {
public CommentID {get;set;}
public string commentText {get;set;}
public virtual product Product {get;set;}
}
My Issue is when:
1) I am creating a new adult comment in the adult comment controller
2) I use db.Products.find(id) to add a product to the product virtual property of the comment
3) I go to the view of the Product I just added the comment to and see that there are 0 comments (lets say I tried to add a comment to a toy, but remember I didn't cast it as a toy when I added it to the virtual property)
4) When I go to the database, there are 3 key columns in the adultcomment table: one for product, one for toys, and one for tools. The correct id was placed in the product column and the others are null
Do I have to cast a product as either a toy or tool before adding it to the adultcomment's virtual property?
Why are there extra columns in the adultcomment table, is it possible to consolidate to one single id column (since after all, i have one products table in my tph), and should I do so if it is possible?
Add the foreignKey attribute to new Comment class
public class Comment
{
[Key]
public int CommentID { get; set; }
public string commentText { get; set; }
public int productID { get; set; }
[ForeignKey("productID")]
public virtual Product Product { get; set; }
}
so AdultComment now looks like this
public class AdultComment : Comment
{
}
you will have to add a unique Identifier when creating a new product despite database auto generating id
using (var context = new YOUR-CONTEXT())
{
var toy = new Toy
{
productID = 1, //Unique identifier
AdultComments = new List<AdultComment>()
{
new AdultComment { commentText = "Some comment" }
}
};
context.Products.Add(toys);
context.SaveChanges();
}

Many-to-Many + additonal column EF design, it is adding an additional column to my table

I want the following table structure:
Person
-person_id
Company
-company_id
Company_Person
-person_id
-company_id
-other_column
Location
-id
Currently my EF is resulting in a 'company_id' column in the Person table also as a FK.
My models look like:
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId { get; set; }
public int LocationId {get;set;}
public virtual Location Location {get; set; }
public virtual ICollection<CompanyPerson> CompanyPersons {get; set;}
}
public class Company
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CompanyId { get; set; }
public virtual ICollection<CompanyPerson> CompanyPersons {get; set;}
}
[Table("Company_Person")]
public class CompanyPerson
{
[Key, Column(Order = 0)]
public int PersonId { get; set; }
[Key, Column(Order = 1)]
public int CompanyId { get; set; }
public bool IsActive { get; set; }
public virtual Person Person { get; set; }
public virtual Company Company { get; set; }
}
public class Location
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id {get;set;}
public virtual ICollection<Person> Persons {get;set;}
}
I followed the same pattern as in here: Create code first, many to many, with additional fields in association table
How can I get that extra CompanyId column from being generated in the Person table?
Update
Ok I figured it out, and it turns out it was another association that I didn't post (my bad once again).
In my Company model I had this which I commented out and it generated the correct table. I still want this association so can someone tell me what is why this is happening?
public class Company
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CompanyId { get; set; }
public virtual ICollection<CompanyPerson> CompanyPersons {get; set;}
public virtual ICollection<History> Histories {get; set; }
}
public class History
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("Company")]
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
So when I commented out the everything in the History model except for the Id property, and the Company.History property it generated the table structure I was expecting.
I think that EF is treating your CompanyPerson property as a complex type, because essentially, it can't actually create a M2M relationship with what you've given it. Whether that's actually the problem or not, either way you'll need to fix your CompanyPerson properties to be:
public virtual ICollection<CompanyPerson> CompanyPersons { get; set; }
UPDATE
The oddest part is that your History class would perfectly explain the issue if it only actually was defined as:
public class History : Person
That's why I asked you about any subclasses of Person because EF's default behavior with inheritance is to use TPH (table per hierarchy). In other words, it will simply add all properties of all subclasses to the base class' table, instead of creating a table for each subclass. Plainly and simply, the only source of this column you aren't expecting is going to be one of either:
Company, or some subclass of Company has direct relationship to Person (not through CompanyPerson) and it's configured to be a one-to-one.
Person or some subclass of Person has a relationship to Company.
Ok I found the problem, and surprise surprise the real bug was with me!
In my Company table had this:
public virtual ICollection<Person> Histories { get; set; }
if you didn't catch that, the type should be History and not Person!
public virtual ICollection<History> Histories { get; set; }
Thanks for all that helped with this!

Categories

Resources