.Net Core - EF Core - Context modelBuilder issue - c#

I've upgraded my project from EF6 to .NET Core & EF Core, but what worked well in the old version, is now not working and I don't know how to modify the implementation, I would need little help.
I am connecting Teachers and Students with classes, Teacher and Student are profiles, which can be connected separately.
My models:
public class Teacher : Profile, IClsModel, IMemberContainer
{
public Teacher() :base()
{
Cls = new List<ProfileClass>();
}
public Teacher(RegisterModel model): base(model)
{
Cls = new List<ProfileClass>();
}
[DisplayName("Classes")]
public virtual ICollection<ProfileClass> Cls { get; set; }
}
public class Student : Profile, IClsModel
{
public Student():base()
{
Cls = new List<ProfileClass>();
}
public Student(RegisterModel model):base(model)
{
Cls = new List<ProfileClass>();
}
public string Name { get; set; }
[DisplayName("Classes")]
public virtual ICollection<ProfileClass> Cls { get; set; }
}
public class ProfileClass
{
public ProfileClass()
{
}
[Key]
public int ProfileClassId { get; set; }
public int? ClassId { get; set; }
public long? ProfileId { get; set; }
[ForeignKey("TeacherProfile")]
public virtual Profile TeacherProfile { get; set; }
[ForeignKey("StudentProfile")]
public virtual Profile StudentProfile { get; set; }
[ForeignKey("ClassId")]
public virtual Cls Cls { get; set; }
}
Builder in the context:
modelBuilder.Entity<Teacher>()
.HasMany(u => u.Cls)
.WithOne(ul => (Teacher)ul.TeacherProfile).IsRequired()
.HasForeignKey(ul => ul.ProfileId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<Student>()
.HasMany(u => u.Cls)
.WithOne(ul => (Student)ul.StudentProfile).IsRequired()
.HasForeignKey(ul => ul.ProfileId)
.OnDelete(DeleteBehavior.NoAction);
The exceptions are the followings:
System.InvalidOperationException: 'The property 'TeacherProfile' cannot be removed from entity type 'ProfileClass' because it is being used in the foreign key {'TeacherProfile'} on 'ProfileClass'.
All containing foreign keys must be removed or redefined before the property can be removed.'
System.InvalidOperationException: 'The property 'StudentProfile' cannot be removed from entity type 'ProfileClass' because it is being used in the foreign key {'StudentProfile'} on 'ProfileClass'.
All containing foreign keys must be removed or redefined before the property can be removed.'

If you are trying to delete related records, you must firstly delete from related tables. You can't delete StudentProfile or TeacherProfile without delete first from ProfileClass.

Related

EF Core 7 - how to set up multiple nullable GUID foreign keys on child object without triggering foreign key conflict

I have 3 entities: Person, User, and Location.
A Person can have multiple Locations
A User can have multiple Locations
My entities are set up as such:
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual IList<Location>? Locations { get; set; }
}
public class PersonEntityTypeConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder
.HasMany(b => b.Locations)
.WithOne(b => b.Person)
.HasForeignKey(b => b.PersonId)
.IsRequired(false);
}
}
public class User
{
public Guid Id { get; set; }
public Guid? Username { get; set; }
public virtual IList<Location>? Locations { get; set; }
}
public class UserEntityTypeConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder
.HasMany(b => b.Locations)
.WithOne(b => b.User)
.HasForeignKey(b => b.UserId)
.IsRequired(false);
}
}
public class Location : UdbObject
{
public Guid Id { get; set; }
public string Description { get; set; }
[ForeignKey(nameof(Person))]
public Guid? PersonId { get; set; }
public virtual Person? Person { get; set; }
[ForeignKey(nameof(User))]
public Guid? UserId { get; set; }
public virtual User? User { get; set; }
}
Problem: I tried to insert a User into my SQL Server DB. This user has one Location object within its IList<Location>? Locations collection. I am getting the following error: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Locations_Persons_PersonId".
Here is where it is going wrong:
Since Person.Id is a Guid? object, it automatically gets set to the equivalent of Guid.Empty before it is submitted to the DB. This causes the FK conflict, since the DB sees that there is no Person object in the DB with an Id set to the equivalent of Guid.Empty.
What I've tried: I saw that in previous version of EF Core, there is a .WithOptional() method that can be used in the Fluent API, but unfortunately this method is not recognized in EF Core 7. I tried to use the .IsRequired(false) method in the API, and it probably works from the DB standpoint, but my problem is that the GUID-based Id property is being set to Guid.Empty on the server before being passed to the DB, so .IsRequired(false) doesn't have the opportunity to do its job.
Am I missing something? Is there some other way to configure this?
Solution: I had a PersonDto that had a property public Guid Id { get; set; } instead of Guid? and it was being mapped back to the Person object with Guid.Empty loaded in it. Duh.
Just make them M2M relationships and the foreign keys will all be in bridge tables. eg
public class Location : UdbObject
{
public Guid Id { get; set; }
public string Description { get; set; }
public virtual ICollection<Person> Persons { get; } = new HashSet<Person>();
public virtual ICollection<User> Users { get; } = new HashSet<User>();
}

EF Core 6 cycle reference add new property in shadow state for one to one relationship

I have a .NET Core Razor Pages proyect in Visual Studio 2022 with Entity Framework Core 6 and Postgres 13 that models companies and cars.
A Car belongs to only one Company and a Company can have many Cars.
In turn, a Company may or may not have an active Car.
My model is:
public class Company {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Car>? Cars { get; set; }
public int? ActiveCarId { get; set; }
public Car? ActiveCar { get; set; }
}
public class Car {
public int Id { get; set; }
public string LicensePlate { get; set; }
public int CompanyId { get; set; }
public Company? Company { get; set; }
}
In the context I have:
public class CompanyCarsContext : DbContext {
public CompanyCarsContext(DbContextOptions<CompanyCarsContext> options)
: base(options) {
}
protected override void OnModelCreating(ModelBuilder builder) {
builder.Entity<Company>().HasMany<Car>(e => e.Cars).WithOne(p => p.Company);
builder.Entity<Company>().HasOne<Car>(e => e.ActiveCar);
}
public DbSet<CompanyCars.Models.Company> Companys { get; set; } = default!;
public DbSet<CompanyCars.Models.Car> Cars { get; set; }
}
When I do the migration it gives me the warning:
Microsoft.EntityFrameworkCore.Model.Validation[10625]
The foreign key property 'Company.ActiveCarId1' was created in shadow state because a conflicting property with the simple name
'ActiveCarId' exists in the entity type, but is either not mapped,
is already used for another relationship, or is incompatible with the
associated primary key type. See
https://aka.ms/efcore-relationships for information on mapping
relationships in EF Core.
But when I remove the property ActiveCarId in class Company (removing the migrations folder as well) and do the migration again, it gives me error:
Both relationships between 'Car.Company' and 'Company.Cars' and
between 'Car' and 'Company.ActiveCar' could use {'CompanyId'} as the
foreign key. To resolve this, configure the foreign key properties
explicitly in 'OnModelCreating' on at least one of the relationships.
What is the correct way to do it?

entities that do not expose foreign key properties for their relationships (EF) [duplicate]

This question already has answers here:
An error occurred while saving entities that do not expose foreign key properties for their relationships
(16 answers)
Closed 2 months ago.
When trying to call SaveChanges(), I get the following error:
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. See the InnerException for details.'
SqlException: Invalid column name 'Artwork_Id'
I am using Entity Framework.
I'm trying to add an artworkImage that has the Id of an artwork as a reference. All information is being passed correctly but it's not saving.
I've tried adding foreign keys to my models and dbcontext but I've not gotten further than the code below.
public partial class ArtworkImage
{
[Key]
public int Id { get; set; }
public string ImageURL { get; set; }
public Artwork Artwork { get; set; }
}
public partial class Artwork
{
[Key]
public int Id { get; set; }
public string Category { get; set; }
public ICollection<ArtworkImage> ArtworkImage { get; set; }
}
My DbContext:
public DbContext()
: base("name=DbConnection")
{
this.Configuration.AutoDetectChangesEnabled = false;
}
public virtual DbSet<Artwork> Artworks { get; set; }
public virtual DbSet<ArtworkImage> ArtworkImages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Artwork>()
.Property(e => e.Category)
.IsFixedLength();
modelBuilder.Entity<Artwork>()
.HasKey(b => b.Id);
modelBuilder.Entity<ArtworkImage>()
.HasKey(b => b.Id);
Database.SetInitializer<DbContext>(null);
base.OnModelCreating(modelBuilder);
}
I believe I should be adding something like this to my dbcontext but I haven't quite figured it out yet.
modelBuilder.Entity<ArtworkImage>()
.HasRequired(p => p.Artwork)
.WithMany(d => d.ArtworkImage)
.HasForeignKey(p => p.Artwork);
If any information is missing please point it out and I'll add it.
You have to declare primary key on each table. it is a rare occasion when a table has no PK. almost never.
[Key]
public int Id { get; set; }
So part of your problem might be that you don't define the relationship in reverse which I believe is important in how it establishes if the relationship is one-to-one or one-to-many. So you will likely need to add a property on the Artwork class that is of type ArtworkImage (if it is one-to-one). if it is one-to-many you will need to make the property some generic collection with the generic of type ArtworkImage.
One-to-one
public partial class Artwork
{
[Key]
public int Id { get; set; }
public string Category { get; set; }
public ArtworkImage ArtworkImage { get; set; }
}
One-to-many
public partial class Artwork
{
[Key]
public int Id { get; set; }
public string Category { get; set; }
public IEnumerable<ArtworkImage> ArtworkImages { get; set; }
}

Entity Framework Core Put Operation Not Working

I am getting
Unhandled Exception Error: the property 'Id' on entity type 'Vehicle' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principalThe property 'Id' on entity type 'Vehicle' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal
Here is my Put API:
[HttpPut("{id}")]
public IActionResult UpdateVehicle(int id, [FromBody] SaveVehicleResource vehicleResource)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var vehicle = context.Vehicles.Include(v => v.Features).SingleOrDefault(v => v.Id == id);
if (vehicle == null)
return NotFound();
mapper.Map(vehicleResource, vehicle);
vehicle.LastUpdate = DateTime.Now;
context.SaveChanges();
var result = mapper.Map<Vehicle, SaveVehicleResource>(vehicle);
return Ok(result);
}
Here is DbContext:
public class VegaDbContext : DbContext
{
public DbSet<Make> Makes { get; set; }
public DbSet<Feature> Features { get; set; }
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Model> Models { get; set; }
public VegaDbContext(DbContextOptions<VegaDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<VehicleFeature>().HasKey(vf => new { vf.VehicleId, vf.FeatureId });
}
}
Vehicle class:
public class Vehicle
{
public int Id { get; set; }
public int ModelId { get; set; }
public Model Model { get; set; }
public bool IsRegistered { get; set; }
public Contact Contact { get; set; }
public ICollection<VehicleFeature> Features { get; set; }
public DateTime LastUpdate { get; set; }
public Vehicle()
{
Features = new Collection<VehicleFeature>();
}
}
VehicleFeature class:
public class VehicleFeature
{
public int VehicleId { get; set; }
public int FeatureId { get; set; }
public Vehicle Vehicle { get; set; }
public Feature Feature { get; set; }
}
Disclaimer: it really looks like you're using AutoMapper. So let's take a look at your AutoMapper configuration. Specifically, see if you can find something like .CreateMap<Vehicle, SaveVehicleResource> in there.
One of these two things is happening:
Your AutoMapper is configured to explicitly CreateMap for these classes and it includes a statement similar to .ForMember(x => x.Id, x.MapFrom(y => y.Id))
Your AutoMapper is not configured explicitly which means it is finding the property .Id because both classes define it with the same name. You must explicitly ignore that member.
Regardless which of those things has happened, you'll have to tell AutoMapper to ignore that property.
CreateMap<Vehicle, SaveVehicleResource>(...)
.ForMember(x => x.Id, y => y.Ignore());

Relationship troubles with Entity Framework

I need help creating the relationship in entity framework as everything I have tried gives me errors when trying to add the migration or if I get passed that then I try to update the database and get an error about indexes with the same name.
public class Profile
{
public Profile()
{
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string VersionCreated { get; set; }
public string DiskLocation { get; set; }
public string Name { get; set; }
public DateTime DateTime { get; set; }
public virtual Product Product { get; set; }
public virtual Instance OriginalInstance { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
public class Instance
{
public Instance()
{
TestResults = new HashSet<TestResult>();
Environments = new HashSet<Environment>();
}
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public string UserFriendlyName { get; set; }
public virtual Product Product { get; set; }
public virtual Profile LastKnownProfile { get; set; }
public virtual Computer Computer { get; set; }
public virtual ICollection<TestResult> TestResults { get; set; }
public virtual ICollection<Environment> Environments { get; set; }
}
The problem with the above classes is that the OrginalInstance property on the Profile class and the LastKnownProfile in the Instance class are supposed to just be foreign keys to those specific tables and they probably won't be the same very often. They can also both possibly be null.
I have tried:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile);
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance);
This gave me an Unable to determine the principal end of an association between the types 'EcuWeb.Data.Entities.Instance' and 'EcuWeb.Data.Entities.Profile'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations. error.
and with:
modelBuilder.Entity<Instance>().HasRequired(i => i.LastKnownProfile).WithOptional();
modelBuilder.Entity<Profile>().HasRequired(p => p.OriginalInstance).WithOptional();
The database adds a foreign key reference back to itself.
...that the OrginalInstance property on the Profile class and the
LastKnownProfile in the Instance class are supposed to just be foreign
keys to those specific tables and they probably won't be the same very
often. They can also both possibly be null.
In this case you actually want two one-to-many relationships between Profile and Instance if I don't misunderstand your quote above. It would mean that many Profiles can have the same OriginalInstance and that many Instances can have the same LastKnownProfile. The correct mapping would look like this then:
modelBuilder.Entity<Profile>()
.HasOptional(p => p.OriginalInstance)
.WithMany()
.Map(m => m.MapKey("OriginalInstanceId"));
modelBuilder.Entity<Instance>()
.HasOptional(i => i.LastKnownProfile)
.WithMany()
.Map(m => m.MapKey("LastKnownProfileId"));
The lines with MapKey are optional. Without them EF will create a foreign key with a default name.
Also note that you must use HasOptional (instead of HasRequired) if "both can possibly be null".

Categories