Entity Framework Core Put Operation Not Working - c#

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());

Related

.Net Core - EF Core - Context modelBuilder issue

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.

How to Map Twitter follower/following type of relation in EF Core 5

How do you configure something similar to Twitter Following and Follower type of relationship using EF Core 5 with the Fluent API? I tried various different ways of configuring it and the only few ways I was able to get it to work is if I ignored the navigation properties on the User entity. I am currently migrating my code from EF Core 2.1 to 5. The following configuration worked earlier. (Not sure if it is misconfigured)
public class User
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<UserFollower> Followers { get; set; }
public ICollection<UserFollower> Following { get; set; }
}
public class UserFollower
{
public long UserId { get; set; }
public User User { get; set; }
public long FollowedById { get; set; }
public User FollowedBy { get; set; }
}
public class UserFollowerConfiguration : IEntityTypeConfiguration<UserFollower>
{
public void Configure(EntityTypeBuilder<UserFollower> builder)
{
builder.HasKey(p => new { p.UserId, p.FollowedById });
builder.HasOne(p => p.User)
.WithMany(i => i.Followers)
.HasForeignKey(i => i.UserId);
builder.HasOne(p => p.FollowedBy)
.WithMany(i => i.Following)
.HasForeignKey(i => i.FollowedById);
}
}
This configuration throws an error when saving to the database.
SqlException: Violation of PRIMARY KEY constraint 'PK_UserFollower'.
Cannot insert duplicate key in object 'dbo.UserFollower'. The duplicate key value is (111, 111).
Even when trying to directly add to the DbContext and calling SaveChanges() on it.
Context.Add(new UserFollower() {UserId = 222, FollowedById = 111});
What is the recommended way of mapping such a relationship with EF Core 5? Note that I do need to access the UserFollowers table without going through the Navigation properties of the User.
Edit #1
The following is the OnModelCreating() for the DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfigurations(typeof(DbContext).Assembly);
/*few configurations unrelated to UserFollower entity*/
}
User entity has the following configuration,
builder.HasKey(i => i.Id);
builder.Property(i => i.Id).ValueGeneratedOnAdd();
Try configuring it like this.
builder.Entity<User>().HasMany(s => s.Followers)
.WithOne(f => f.FollowedBy);
builder.Entity<User>().HasMany(s => s.Following)
.WithOne(f => f.);
Also, The PK is missing for the UserFollower table, I don't know if an Id is being generated somehow somewhere. If not, maybe this is why it's trying to wrongly use FollowedById as key, but define an Id for the UserFollower table and see.
public class UserFollower
{
public long Id {get;set;}
public long UserId { get; set; }
public User User { get; set; }
public long FollowedById { get; set; }
public User FollowedBy { get; set; }
}
Even if this works, I would recommend you change the structure of your model, it looks ambigous for the twitter requirements you described. If I query Userfollowers
var userFollowers = _context.UserFollowers.ToList();
For each result in the list, there is no way for me to tell if the user is following or being followed. You could change your models to these ones;
public class User
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<UserFollower> Followers { get; set; }
public ICollection<UserFollowing> Following { get; set; }
}
public class UserFollower
{
public long UserId { get; set; }
public User User { get; set; }
public long UserFollowingMeId { get; set; }
public User UserFollowingMe { get; set; }
}
public class UserFollowing
{
public long UserId { get; set; }
public User User { get; set; }
public long UserIAmFollowingId { get; set; }
public User UserIAmFollowing { get; set; }
}
This way, everybody knows when they check the UserFollowings table, the UserId is the Id of the person that is following and vice versa for the UserFollowers table. If I had an Id of 8 in the system, I can query my followers and people I follow like this;
var myFollowers = _context.UserFollowers.Where(UserId = 8);
var peopleIFollow = _context.UserFollowing.Where(UserId = 8);

Entity Framework One to Many with no Collection navigation property

In Entity Framework with Fluent Configuration, I have a LeaseTrackingType entity which has a one to many relationship with LeaseTracking. Where each lease tracking has a lease tracking type.
However from a code point of view it doesn't really make sense to have a LeaseTrackings collection. Like you are never going to use this navigation property.
Question: How do I model the one to many relationship without LeaseTrackings collection navigation property?
Entities:
public class LeaseTracking
{
public int Id { get; set; }
public int LeaseTrackingTypeId { get; set; }
public LeaseTrackingType LeaseTrackingType { get; set; }
}
public class LeaseTrackingType
{
public int Id { get; set; }
public string Name { get; set;}
public virtual Collection<LeaseTracking> LeaseTrackings { get; set;}
}
And mapping configuration:
public class LeaseTrackingConfiguration : EntityTypeConfiguration<LeaseTracking>
{
public LeaseTrackingConfiguration()
{
ToTable("LeaseTracking");
Property(entity => entity.Id);
HasRequired(entity => entity.LeaseTrackingType)
.WithMany(entity => entity.LeaseTrackings)
.HasForeignKey(entity => entity.LeaseTrackingTypeId);
}
}
public class LeaseTrackingTypeConfiguration : EntityTypeConfiguration<LeaseTrackingType>
{
public LeaseTrackingTypeConfiguration()
{
ToTable("LeaseTrackingType");
Property(entity => entity.Id);
Property(entity => entity.Name).;
}
}
in the constructor don't point it out in WithMany function and get rid of the virtual Collection in LeaseTrackingType class
public LeaseTrackingConfiguration()
{
ToTable("LeaseTracking");
Property(entity => entity.Id);
HasRequired(entity => entity.LeaseTrackingType)
.WithMany()
.HasForeignKey(entity => entity.LeaseTrackingTypeId);
}

Navigation property that depends on existing property

I have a class Log that has a property Entity. This entity will be a reference to some other object within the App (either Customer, Supplier, Invoice or Credit) that share a base class.
public class Log
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public LogCode Code { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
public string SourceID { get; set; }
public DateTime DateCreated { get; internal set; }
public bool Acknowledged { get; set; }
public EntityType EntityType { get; set; }
public BaseModel Entity { get; set; }
}
The EntityType property contains an enum that I can use to determine the type of the entity.
public enum EntityType
{
Customer,
Supplier,
Invoice,
Credit,
}
The table in the database stores the ID of this entity but as each entity type is stored in a different table I'm having difficulties gathering this entity.
I've tried modifying the setter of EntityType to gather the correct entity but the Log doesn't have any reference to the DbContext.
Without EF I would switch on the entity type and load the correct entity using different service objects but is there a way that I can set up entity framework to use the EntityType to gather the correct Entity?
I think you should implement Table per Concrete Type (TPC) approach. The only one restriction: you have to use Guid type of Id instead of int, or leave int, but at this case you should assign values to it manually.
public abstract class BaseModel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id {get;set;}
public string SomeCommonProperty {get;set;}
}
public class Log
{
//other properties...
//EntityType is not needed, but you can leave it
public EntityType EntityType { get; set; }
public virtual BaseModel Entity { get; set; }
}
public class MyContext : DbContext
{
public DbSet<BaseModel> Entities { get; set; }
public DbSet<Log> Logs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Customers");
});
modelBuilder.Entity<Supplier>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Suppliers");
});
}
}
Usage:
var suppliers = ctx.Entities.OfType<Supplier>()
.Where(x => x.SupplierProperty == "1").ToList();
var supplier = (log.Entity as Supplier);
var customer = (log2.Entity as Customer);

EF Code First many-to-many: how to specify name of th third table and add some other properties to it?

I need to implement the many-to-many relationship in the Entity Framework Code First, and map this relationship to the third table. And I want to add to this table some other fields such as autoincremented Id and the AppointmentDateTime for exaple:
public class User {
public int Id { get; set; }
// some other properties ......
public virtual List<Role> Roles { get; set; }
}
public class UserTypeConfiguration : EntityTypeConfiguration<User> {
public UserTypeConfiguration() {
HasKey(k => k.Id);
Property(p => p.Email).IsRequired();
//[∞ — ∞]
HasMany<Role>(u => u.Roles).WithMany(r => r.Users).Map(m => m.MapLeftKey("UserId").MapRightKey("RoleId").ToTable("UserRoles"));
}
}
But Entity Framework generates a table with the wrong name that which I have passed, and wrong names navigation proiperties I have passed to the mapping.
The name of table "RoleUsers" and the names of navigation proiperties are "User_Id" and "Role_Id".
How to implement correct names of mapping and how to add some other properties to the UserRoles table?
Since you need to add additional properties to describe the relationship, you have to consider many-to-many association as two one-to many ones:
public class User
{
// other properties omitted
public virtual List<UserRole> UserRoles { get; set; }
}
public class Roles
{
// other properties omitted
public virtual List<UserRole> UserRoles { get; set; }
}
public class UserRole
{
public int Id { get; set; }
public DateTime AppointmentDateTime { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
}
Configuration:
public class UserRoleConfiguration : EntityTypeConfiguration<UserRole>
{
public UserRoleConfiguration()
{
// scalar properties config omitted
HasRequired(_ => _.User)
.WithMany(_ => _.UserRoles)
.HasForeignKey(_ => _.UserId);
HasRequired(_ => _.Role)
.WithMany(_ => _.UserRoles)
.HasForeignKey(_ => _.RoleId);
ToTable("UserRoles");
}
}

Categories