I have three tables
EVENT - PERSON - COMPANY
I need to have a relation many-to-many using those tables. An event can have one or more "clients",which can be either person or company. Normally, using no ORM, using sql , it would be something like :
EVENT
----
id
name
CLIENTEVENT
-----------
id
clientid
clienttype -- person or company
PERSON
-----------
id
name
lastname
...
COMPANY
-------
id
name
How does this approach can be replicated using entity framework? I am pretty new using EF so I would appreciate all help you can give me.I am using repository pattern, following this approach http://www.codeproject.com/Articles/838097/CRUD-Operations-Using-the-Generic-Repository-Pat.
I advice you instead of clientid and clienttype make another two columns: personID and companyID. Models will look this way:
public class Event
{
public int ID { get; set; }
public string name { get; set; }
public virtual ICollection<CLIENTEVENT> links { get; set; }
}
public class Company
{
public int ID { get; set; }
public string name { get; set; }
public virtual ICollection<CLIENTEVENT> links { get; set; }
}
public class PERSON
{
public int ID { get; set; }
public string name { get; set; }
public string lastname { get; set; }
public virtual ICollection<CLIENTEVENT> links { get; set; }
}
public class CLIENTEVENT
{
public int ID { get; set; }
public virtual PERSON person { get; set; }
public int? personID { get; set; }
public virtual Company company { get; set; }
public int? companyID { get; set; }
public virtual Event event1 { get; set; }
public int? event1ID { get; set; }
}
With EF Code First, you don't have to deal with joining tables like ClientEvent. You can simply write:
public class Event
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Company> Companies { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
Try this code, you'll see EF creating two linking tables (EventPerson and EventCompany). Now, the client type is the collection you read (someEvent.Persons or someEvent.Companies), you can also easily get the events linked to a specific Person or a Company.
About many-to-many with EF.
Related
I got a model like below which is having some related tables associated with this like Addresses, MetaInfo, etc :
public class Contact
{
[Key]
public int Id { get; set; }
[MaxLength(15)]
[Column(Order = 1)]
public string Title { get; set; }
[Required, MaxLength(100)]
public string FirstName { get; set; }
[MaxLength(100)]
public string LastName { get; set; }
......some more string and int properties
public virtual ICollection<ContactAddress> Addresses { get; set; }
public virtual ContactMetaInfo MetaInfo { get; set; }
}
public class ContactAddress
{
[Key]
public int Id { get; set; }
[ForeignKey(nameof(Contact))]
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
[MaxLength(150)]
public string Line1 { get; set; }
[MaxLength(150)]
public string Line2 { get; set; }
......some more string and int properties and some foreign keys
[ForeignKey(nameof(Continent))]
public int? ContinentId { get; set; }
public virtual Continent Continent { get; set; }
[ForeignKey(nameof(Country))]
public int? CountryId { get; set; }
public virtual Country Country { get; set; }
[ForeignKey(nameof(County))]
public int? CountyId { get; set; }
public virtual County County { get; set; }
}
Now I am dealing with a requirement. I have to create a historical version of these entities.
I am thinking of making new Class as Entities like below :
HistoryContact
HistoryContactAddress
HistoryContactMetaInfo
And copy all properties from Contact to HistoryContact and subsequent tables. There are almost 7 Entities are there to copy so...
In future, if a new property added to any of these entities, I have to handle them manually in 2 entities [Master and History entities separately].
Is any better way to handle these scenarios?
Currently I'm using EF Core 5, but it's possible to use EF Core 6.
I have two model classes Category and Recipe and their relationship in one to many. I want to Edit the Recipe and also change the category that the recipe belongs to.Thanks in advance.
public class CookContext : DbContext
{
public CookContext(): base("cookContext")
{
}
public DbSet<Recipe> Recipes { get; set; }
public DbSet<Category> Categories { get; set; }
}
public class Category
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Recipe> Recipes { get; set; }
}
public class Recipe
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string Ingridients { get; set; }
public string Image { get; set; }
public Category category { get; set; }
}
[HttpPost]
[ValidateInput(false)]
public ActionResult EditRecipe(Recipe recipe, int? categoryName)
{
var category = context.Categories.Where(c => c.Id ==
(int)categoryName).FirstOrDefault();
context.Entry(recipe).State = EntityState.Modified;
recipe.category = category;
context.SaveChanges();
}
The error messages i get are:
1.
[DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types.
2.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
Try by adding the field CategoryId to your Recipe class, also, the category property should begin with a capital "C".
The collection property should be marked virtual if you want the data to be lazy loaded (only loaded when needed) otherwise, you may load all recipes for a given category every time you make a query:
public class Recipe
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string Ingridients { get; set; }
public string Image { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
If it doesn't work try by setting up the ForeignKey attribute:
public class Recipe
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Description { get; set; }
[Required]
public string Ingredients { get; set; }
public string Image { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
I have declared two classes - Person and Vehicle as shown below
public class Person
{
public Person()
{
this.Vehicles = new HashSet<Vehicle>();
}
[Key]
public int PersonID { get; set; }
[Required, MaxLength(50)]
public string FirstName { get; set; }
[Required, MaxLength(50)]
public string MiddleName { get; set; }
[Required, MaxLength(50)]
public string LastName { get; set; }
[Required, MaxLength(10)]
public string MobileNo1 { get; set; }
[MaxLength(10)]
public string MobileNo2 { get; set; }
[MaxLength(50)]
public string Email1 { get; set; }
[MaxLength(50)]
public string Email2 { get; set; }
public virtual ICollection<Vehicle> Vehicles { get; set; }
}
public class Vehicle
{
[Key]
public int VehicleID { get; set; }
[MaxLength(20)]
public string VehicleNumber { get; set; }
[ForeignKey("VehicleOwner")]
public int? VehicleOwnerID { get; set; }
[ForeignKey("VehicleOwnerID")]
public virtual Person VehicleOwner { get; set; }
[ForeignKey("VehicleDriver")]
public int? VehicleDriverID { get; set; }
[ForeignKey("VehicleDriverID")]
public virtual Person VehicleDriver { get; set; }
[ForeignKey("Person")]
public int? PersonID { get; set; }
[ForeignKey("PersonID")]
public virtual Person Person { get; set; }
}
This generates two foreign keys on Vehicles table as
.ForeignKey("dbo.Person", t => t.PersonID)
.ForeignKey("dbo.Person", t => t.Person_PersonID)
whereas what i expect is only
.ForeignKey("dbo.Person", t => t.PersonID)
Initially i thought it might be because i missed out declaring the entities as virtual but that was not the case. I am not able to detect the problem with this code.
Like Vehicles, i have another class - Documents with somewhat the same structure and relationship with Person. But for Documents the foreign key is generated as expected.
You've got 3 classes pointing at Person, so configure as:
public class Vehicle
{
[Key]
public int VehicleID { get; set; }
[MaxLength(20)]
public string VehicleNumber { get; set; }
public int? VehicleOwnerID { get; set; }
[ForeignKey("VehicleOwnerID")]
public virtual Person VehicleOwner { get; set; }
public int? VehicleDriverID { get; set; }
[ForeignKey("VehicleDriverID")]
public virtual Person VehicleDriver { get; set; }
public int? PersonID { get; set; }
[ForeignKey("PersonID")]
public virtual Person Person { get; set; }
}
While that is incorrect syntax, the 2nd foreign key comes from the collection of vehicles on the person and EF not being able to resolve which FK it belongs to.
So in your Person class on your collection you need to point to the corresponding nav in the vehicle:
[InverseProperty("Person")]
public virtual ICollection<Vehicle> Vehicles { get; set; }
http://www.entityframeworktutorial.net/code-first/inverseproperty-dataannotations-attribute-in-code-first.aspx
As Steve has said, this is because you have references to Person from your vehicle class. So, Entity Framework creates 3 keys one for Person, VehicleOwner, and VehicleDriver. Yet, it creates another key because of the collection of vehicles from person. You must use the inverse property attribute here to tell Entity Framework that you mean to build a relationship between Vehicle and Person on the actual Vehicle.Person property. You can do this by
[InverseProperty("Person")]
public virtual ICollection<Vehicle> Vehicles { get; set; }
Also, you being redundant with:
[ForeignKey("Person")]
public int? PersonID { get; set; }
[ForeignKey("PersonID")]
public virtual Person Person { get; set; }
Remove the ForeignKey from the int? PersonID in your Vehicle class.
I have class in my MVC project and I used Entity Framework 6. Every person has a Master (master_Id) and it references to the same table (primary key in Person table). My way does not work... what's the solution?
public class Person
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
[ForeignKey("Person")]
public int Master_Id { get; set; }
public virtual Person Master { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
public class Person
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int? MasterId { get; set; }
[ForeignKey("MasterId")]
public virtual Person Master { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
I have a two classes. A person class and a Title class.
public class Person
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int TitleTypeId { get; set; }
}
public class TitleType
{
public int Id { get; set; }
public string Name { get; set; }
}
The title class contains things like "Mr., Mrs., MS.,etc.."
What is the proper way to define these classes so that I can use Linq to recall the desired title "name" from the TitleType table?
i.e. person.Title.name
public class Person
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public TitleType Title { get; set; }
}
EF will create additional column to handle Foreign Key for you automatically.