Entity Framework Core Navigation Properties Error - c#

I'm trying to make a simple app to try Entity Framework Core, but i a have problem with setting up relations between entities. My entities:
public class Card
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Adress { get; set; }
public DateTime DoB { get; set; }
public DateTime DoS { get; set; }
public User Portal { get; set; }
public List<Reservation> Res { get; set; }
}
public class Doctor
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public TimeSpan Start_Working { get; set; }
public TimeSpan End_Working { get; set; }
public List<Reservation> Reservations { get; set; }
public int SpecID { get; set; }
public Spec Spec { get; set; }
}
public class Reservation
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime DoR { get; set; }
public string Info { get; set; }
public int CardID { get; set; }
public Card Card_Nav_R { get; set; }
public int DoctorID { get; set; }
public Doctor Doctor { get; set; }
}
public class Spec
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public List<Doctor> Doctors { get; set; }
}
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public int CardID { get; set; }
public Card Card { get; set; }
}
And a configuration class where i tried to set up relations:
class ApplicationContext:DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Card> Cards { get; set; }
public DbSet<Reservation> Reservations { get; set; }
public DbSet<Doctor> Doctors { get; set; }
public DbSet<Spec> Specs { get; set; }
public ApplicationContext()
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder ModelBuilder)
{
ModelBuilder.Entity<User>().HasKey(u => u.Id);
ModelBuilder.Entity<Card>().HasKey(c => c.Id);
ModelBuilder.Entity<Doctor>().HasKey(d => d.Id);
ModelBuilder.Entity<Spec>().HasKey(s => s.Id);
ModelBuilder.Entity<Reservation>().HasKey(r => r.Id);
ModelBuilder.Entity<User>().Property(u => u.Email).IsRequired();
ModelBuilder.Entity<User>().Property(u => u.Password).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Name).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Surname).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.DoB).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Adress).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Name).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Surname).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Spec).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Email).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Start_Working).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.End_Working).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Info).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Card_Nav_R).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Doctor).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.DoR).IsRequired();
ModelBuilder.Entity<Spec>().Property(s => s.Name).IsRequired();
ModelBuilder.Entity<Doctor>().HasOne<Spec>(d=>d.Spec).WithMany(s => s.Doctors).HasForeignKey(d => d.SpecID);
ModelBuilder.Entity<User>().HasOne<Card>(u => u.Card).WithOne(c => c.Portal).HasForeignKey<User>(u => u.CardID);
ModelBuilder.Entity<Reservation>().HasOne<Card>(r => r.Card_Nav_R).WithMany(c => c.Res).HasForeignKey(r => r.CardID);
ModelBuilder.Entity<Reservation>().HasOne<Doctor>(r => r.Doctor).WithMany(d => d.Reservations).HasForeignKey(r => r.DoctorID);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Simple_Try;Trusted_Connection=True;");
}
}
So, when i tried to add migration or add something to database i saw this error:
System.InvalidOperationException: 'The property or navigation 'Spec' cannot be added to the entity type 'Doctor' because a property or navigation with the same name already exists on entity type 'Doctor'.'
I really don't know how to fix this, i tried to use annotations instead of Fluent API, but had the same result.

The cause of the exception is the following line:
ModelBuilder.Entity<Doctor>().Property(d => d.Spec).IsRequired();
because Doctor.Spec is a navigation property
public class Doctor
{
// ...
public Spec Spec { get; set; }
}
and navigation properties cannot be configured via Property fluent API.
So simply remove that line. Whether reference navigation property is required or optional is controlled via relationship configuration. In this case
ModelBuilder.Entity<Doctor>()
.HasOne(d => d.Spec)
.WithMany(s => s.Doctors)
.HasForeignKey(d => d.SpecID)
.IsRequired(); // <--
although the IsRequired is automatically derived from the FK property type - since SpecID is non nullable, then the relationship is required.
For more info, see Required and Optional Properties and Required and Optional Relationships documentation topics.

Related

Entity Framework Core: multiple relationships to one table of base type

Let's assume that Administrator, Purchaser and Supplier have User base type and remaining models look following:
public class Vendor
{
public int VendorId { get; set; }
public List<Supplier> Suppliers { get; set; }
}
public class Task
{
public int TaskId { get; set; }
public Administrator Admin { get; set; }
public List<Purchaser> Purchasers { get; set; }
public Vendor Vendor { get; set; }
}
Now I would like to create a UserTask table that contains IDs of all users of the Task: an Admin, Purchasers and Suppliers of the Vendor in column User and their Tasks IDs in column Task.
How could I configure such setup in Fluent API?
Edit:
I created additional entity UserTask that consists of IDs and navigation properties:
public class UserTask
{
public int UserId { get; set; }
public User User { get; set; }
public int TaskId { get; set; }
public Task Task { get; set; }
//some other needed properties
}
And tried to configure models like this:
modelBuilder.Entity<UserTask>(ut =>
{
ut.HasKey(x => new { x.UserId, x.TaskId });
ut.HasOne(u => u.User).WithMany()
.HasForeignKey(u => u.UserId)
.OnDelete(DeleteBehavior.Cascade);
ut.HasOne(t => t.Task).WithMany()
.HasForeignKey(t => t.TaskId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<Task>(t =>
{
t.HasMany(p => p.Purchasers).WithMany(p => p.Tasks);
t.HasOne(a => a.Administrator).WithMany(); //adding a => a.Task expression in parameter throws error that the relationship is already defined
t.HasMany(s => s.Vendors.Suppliers).WithMany(s => s.Tasks); //throws error
});
And it fails because HasMany(s => s.Vendors.Suppliers) i not a valid member access expression. Is there any way to overcome this issue?
Considering the relationships in these tables, add a property so that Fluent API can reference the relationship. About the specific modelbuilder.
modelBuilder.Entity<Supplier>()
.HasOne(x => x.vendor)
.WithMany(y => y.Suppliers);
modelBuilder.Entity<Administrator>()
.HasOne(a => a.tasks)
.WithOne(t => t.Admin)
.HasForeignKey<Administrator>(f=>f.AdministratorId);
modelBuilder.Entity<Vendor>()
.HasOne(a => a.tasks)
.WithOne(t => t.Vendor)
.HasForeignKey<Vendor>(f=>f.VendorId);
The model need to be redesigned as this.
public class User
{
public int id { get; set; }
public string Property { get; set; }
}
public class Vendor
{
public int VendorId { get; set; }
public List<Supplier> Suppliers { get; set; }
public Tasks tasks { get; set; }
}
public class Tasks
{
[Key]
public int TaskId { get; set; }
public Administrator Admin { get; set; }
public List<Purchaser> Purchasers { get; set; }
public Vendor Vendor { get; set; }
}
public class Supplier:User
{
public int SupplierId { get; set; }
public string SupplierProperty { get; set; }
public Vendor vendor { get; set; }
}
public class Administrator:User
{
public int AdministratorId { get; set; }
public string adminProperty { get; set; }
public Tasks tasks { get; set; }
}
public class Purchaser:User
{
public int PurchaserId { get; set; }
public string purProperty { get; set; }
public Tasks tasks { get; set; }
}

EF Core Relationship mapping

In project I can have one User that can have many UserActivites. In my models I've set up their relationship as follows:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
//relationship mapping example
// delete these attributes and you'll cause a self referenceing loop error
[JsonIgnore]
[IgnoreDataMember]
public List<UserActivity> Activities { get; set; }
}
public class UserActivity
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Project { get; set; }
public DateTime EntryDate { get; set; }
// relationship mapping
public User User { get; set; }
}
And in my repository class, I'm getting all my user activities this way:
public async Task<IEnumerable<UserActivity>> GetAll()
{
var result = await _context.UserActivities.Include(activity => activity.User).OrderByDescending(x => x.EntryDate).ToListAsync();
return result;
}
However, when I run my project, the User property of UserActivities is null. So I checked the Microsoft docs on EF Core relationships and updated my OnModelCreating method inside of my context to also do the mapping as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserActivity>().ToTable("UserActivities").Property(x => x.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<UserActivity>().ToTable("UserActivities").HasOne(x => x.User).WithMany();
modelBuilder.Entity<User>().ToTable("Users").Property(x => x.Id).ValueGeneratedOnAdd();
base.OnModelCreating(modelBuilder);
}
However, when I run the project again, my User property still isn't populated. I know this isn't a data issue as I have data inside of my User table and display that on a separate page.
I'm not sure what I'm doing wrong/missing with this. So any help would be appreciated
Try this code:
Your tables:
public partial class User
{
public User()
{
UserActivities = new HashSet<UserActivity>();
}
[Key]
public int Id { get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public string Email { get; set; }
[InverseProperty(nameof(UserActivity.User))]
public virtual ICollection<UserActivity> UserActivities { get; set; }
}
public partial class UserActivity
{
[Key]
public int Id { get; set; }
public int UserId { get; set; }
public string Project { get; set; }
public DateTime EntryDate { get; set; }
[ForeignKey(nameof(UserId))]
[InverseProperty("UserActivity")]
public virtual User User { get; set; }
}
dbcontext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserActivity>(entity =>
{
entity.HasOne(d => d.User)
.WithMany(p => p.UserActivities)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_UserActivity_User");
});
OnModelCreatingPartial(modelBuilder);
}

Multiply relationships ef core

I'm using entity core 3. I have 2 classes User and Ticket. User may have many Ticket's to me and many Ticket's from me. Ticket should have User-sender and User-receiver. I did so:
public class User
{
public int Id { get; set; }
public string PasswordHash { get; set; }
public string Email { get;
public ICollection<Ticket> TicketsToMe { get; set; }
public ICollection<Ticket> TicketsFromMe { get; set; }
}
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public int UserToId { get; set; }
public int UserFromId { get; set; }
public User UserTo { get; set; }
public User UserFrom { get; set; }
}
And I got the error: Unable to determine the relationship represented by navigation property 'Ticket.UserTo' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Do you have any ideas?:)
You can use one of these
1 - Metadata. you can use InverseProperty to define the relationships.
if you use the metadata you should set UserToId and UserFromId to Nullable
public class User
{
public int Id { get; set; }
public string PasswordHash { get; set; }
public string Email { get; set; }
[InverseProperty("UserTo")]
public ICollection<Ticket> TicketsToMe { get; set; }
[InverseProperty("UserFrom")]
public ICollection<Ticket> TicketsFromMe { get; set; }
}
2 - FluentApi
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(a => a.TicketsFromMe)
.WithOne(a => a.UserFrom)
.HasForeignKey(a => a.UserFromId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<User>()
.HasMany(a => a.TicketsToMe)
.WithOne(a => a.UserTo)
.HasForeignKey(a => a.UserToId).OnDelete(DeleteBehavior.Restrict);
}

One to Many relationship always bring me empty

I try to use Entity Framework with code first and fluent api to implement a one to many relationship
I have two classes
namespace Mantenimiento.Business.Entities
{
public class Personal : Entity
{
[Key]
public int Id { get; set; }
public int? Dni { get; set; }
public string Nombre { get; set; }
public string Apellido { get; set; }
public string Cuil { get; set; }
public string Legajo { get; set; }
[ForeignKey("Dni")]
public ICollection<ContactoEmergencia> Contacto { get; set; }
}
namespace Mantenimiento.Business.Entities
{
public class ContactoEmergencia : Entity
{
[Key]
public int Id { get; set; }
public int? Dni { get; set; }
public string ApellidoNombre { get; set; }
public string Vinculo { get; set; }
public string Domicilio { get; set; }
public string telefono { get; set; }
public string Comentario { get; set; }
public int CreateUserId { get; set; }
[ForeignKey("Dni")]
public virtual Personal Personal { get; set; }
}
}
This is my dbContext
#region personals
modelBuilder.Entity<Personal>().ToTable("InfoPersonal").HasKey(t => t.Id);
modelBuilder.Entity<Personal>().Property(c => c.Id).UseSqlServerIdentityColumn().IsRequired();
modelBuilder.Entity<Personal>().Property(c => c.CreatedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<Personal>().Property(c => c.LastModifiedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<Personal>().Property(c => c.Deleted).HasDefaultValue(false);
modelBuilder.Entity<Personal>().HasMany<ContactoEmergencia>(c => c.Contacto).WithOne(p => p.Personal).HasForeignKey(s => s.Dni);
#endregion
#region contactoEmergencias
modelBuilder.Entity<ContactoEmergencia>().ToTable("InfoEmergencia").HasKey(d => d.Dni);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.CreatedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.LastModifiedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.Deleted).HasDefaultValue(false);
#endregion
And my query is
return await _context.personals
.Include(c => c.Contacto)
.Where(p => p.Deleted == false)
.OrderBy(s => s.Apellido)
.ToListAsync(
);
But the properties is always empty.
i need to relate Personal.Di with Contacto.Dni, i had to change the key?
You should remove ForeignKey attribute from Personal entity. In one to many relationship only child entity could accept ForeignKey.

How to create a many-to-many mapping in Entity Framework?

Here is the case, I have 2 entities, such as Contract、Media。
public class Media : Entity
{
public string Name {get; set;}
public bool Enabled
*//other properties can be ignored..*
}
public class Contract : Entity
{
public string Code {get; set;}
*//other properties can be ignored..*
}
Contract has many Medias, it seems that they are many to many.
But!! at ef code first, i need 3 more fields in the ContractMedia table(ef auto generated).
such as StartDate,EndDate and Price. these could not be added in Media entity.
How to map at this case??
If you want to create many to many relationship with additional data in association table, you have to make the association table as entity. The pure many to many relationship is only in pure table with entity id's.
In you case it will be:
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class ContractMedia // Association table implemented as entity
{
public int MediaId { get; set; }
public int ContractId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public double Price { get; set; }
public virtual Media Media { get; set; }
public virtual Contract Contract { get; set; }
}
And after you created models/entities, you need to define relationships in context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ContractMedia>()
.HasKey(c => new { c.MediaId, c.ContractId });
modelBuilder.Entity<Contract>()
.HasMany(c => c.ContractMedias)
.WithRequired()
.HasForeignKey(c => c.ContractId);
modelBuilder.Entity<Media>()
.HasMany(c => c.ContractMedias)
.WithRequired()
.HasForeignKey(c => c.MediaId);
}
Also you can refer to these links:
Many to many mapping with extra fields in Fluent API
Entity Framework CodeFirst many to many relationship with additional information
Create code first, many to many, with additional fields in association table
Adding to #Tomas answer without having to use Fluent API.
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class ContractMedia // Association table implemented as entity
{
[Key]
[Column(Order = 0)]
[ForeignKey("Media")]
public int MediaId { get; set; }
[Key]
[Column(Order = 1)]
[ForeignKey("Contract")]
public int ContractId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public double Price { get; set; }
public virtual Media Media { get; set; }
public virtual Contract Contract { get; set; }
}
EF Core needs to use Fluent API but it would look like this:
internal class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId),
j => j
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId),
j =>
{
j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new { t.PostId, t.TagId });
});
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<Tag> Tags { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public ICollection<Post> Posts { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public DateTime PublicationDate { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Source:
https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration

Categories