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);
}
Related
So i want to add RelatedProducts to my products. So i applied the same relationship type as i did for category. The only difference there is no category class, but we target the same entity. (product). Because the regular many2many works fine, i removed that from my example.
Goal:
Any suggestions?
Exception:
"Exception occured: Cannot create a relationship between 'Product.RelatedProducts' and 'RelatedProduct.Related' because a relationship already exists between 'Product.RelatedProducts' and 'RelatedProduct.Product'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'RelatedProduct.Related' first in 'OnModelCreating'."
Product.cs:
public class Product : IExportable, IEntityBase
{
public int Id { get; set; }
public string Name { get; set;}
public ICollection<RelatedProduct> RelatedProducts { get; set; }
}
ProductEntityTypeConfiguration.cs
internal class ProductEntityTypeConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
config.HasKey(p => p.Id);
}
}
RelatedProducts.cs
public class RelatedProduct
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int RelatedId { get; set; }
public virtual Product Related { get; set; }
}
RelatedProductEntityTypeConfiguration.cs
public class RelatedProductsEntityConfiguration : IEntityTypeConfiguration<RelatedProduct>
{
public void Configure(EntityTypeBuilder<RelatedProduct> builder)
{
builder.HasKey(rp => rp.Id);
builder.ToTable("RelatedProducts");
builder
.HasOne(rp => rp.Product)
.WithMany(p => p.RelatedProducts)
.HasForeignKey(rp => rp.ProductId)
.OnDelete(DeleteBehavior.Restrict);
builder
.HasOne(rp => rp.Related)
.WithMany(p => p.RelatedProducts)
.HasForeignKey(rp => rp.RelatedId)
.OnDelete(DeleteBehavior.Restrict);
}
}
I am unable to test this suggestion but I am sure it might work. Why don't you just define your RelatedProduct as:
public class RelatedProduct
{
public int Id { get; set; }
public virtual ICollection<Product> Related { get; set; }
}
I also suggest you comment out the code in your RelatedProductsEntityConfiguration Configure method
In Entity Framework Core version 2.2 or 3.0, is it possible to use owned/complex types in such a way that this kind of configuration is possible:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public ProductProperties Properties { get; set; }
}
public class ProductProperties {
public List<ProductSize> Sizes { get; set; }
}
public class Size {
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductSize {
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Product)
.WithMany(x => x.Properties.Sizes)
.HasForeignKey(x => x.ProductId);
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Size)
.WithMany()
.HasForeignKey(x => x.SizeId);
The error message which is seen for this kind of approach usually ends up in:
'x => x.Properties.Sizes' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'
An earlier found answer is almost exactly matching my question, but this was posted in 2013. By the time it was almost certainly not possible.
HasForeignKey relationship through a Complex Type property
The sources on Microsoft are only giving examples for creating an entity with the complex type itself, not for creating relationships between them.
The cause of the issue
In your sample code it's quite clear there is no specific Many to Many relation. To make my argument a bit more convincing what follows is a model of your entities and their relations:
The new class structure
For a Many to Many relation to work in EF the product and size tables need to have an implicit relation with each other through a singular junction table. In my proposed solution I've chosen the ProductProperty table. There I've added the fields from the productsize junction table:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
public class ProductProperty
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
public class Size
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
The functions
modelBuilder.Entity<ProductProperty>()
.HasKey(pp => new { pp.ProductId, pp.SizeId });
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Product)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.ProductId);
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Size)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.SizeId);
Additional advice (EDIT)
Make the "Size" class a generic property class. This way the Many-to-Many relation won't get broken and querying will also be very easy:
public class Property
{
public int Id { get; set; }
public PropertyType propType { get; set; }
public string propValue { get; set; }
}
public enum PropertyType
{
Size,
Fontsize,
...
}
As a final argument this change will make it easier to change existing properties or add new ones
Sources
https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
You can check the owned entity types released in 2019 Check documentation here
An example from the link is the following:
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
The owns many function should help you like this:
modelBuilder.Entity<Distributor>().OwnsMany(p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
Let me know if I misunderstood your question.
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());
I'm trying to map my entities following the TPC pattern.
I have an abstract base class Agent :
public abstract class Agent
{
public int Id { get; set; }
public DateTime? ChangeDate { get; set; }
public DateTime CreationDate { get; set; }
public string insuranceCompanyPolicyNumber { get; set; }
public int? KeySys { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
public string Status { get; set; }
public int? Transactionid { get; set; }
}
I have a child entity Suscriber :
public partial class Suscriber : Agent
{
public int? id_federateur { get; set; }
public string Number { get; set; }
public bool? suivi_client { get; set; }
public virtual ICollection<Contract> Contracts { get; set; }
}
When i try to configure the Suscriber like the following, i've noticed that the request generated by Entity Framework is wrong :
public class SuscriberConfiguration : EntityTypeConfiguration<Suscriber>
{
public SuscriberConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
m.Property(s => s.Number).HasColumnName("numero_souscripteur");
// Property(s => s.Number).HasColumnName("numero_souscripteur");
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
}
}
The request generated by Entity Framework is (The table Agent2 doesn't exist):
SELECT E'0X0X' AS "C1",
"Extent1"."id_personne",
"Extent1"."suivi_client",
"Extent1"."id_federateur",
"Extent2"."date_mod",
"Extent2"."date_cre",
"Extent2"."insuranceCompanyPolicyNumber",
"Extent2"."keySys",
"Extent2"."id_role",
"Extent2"."statut",
"Extent2"."vsc_transactionid",
"Extent2"."numero_souscripteur"
FROM "atlas"."Agent2" AS "Extent1"
INNER JOIN "atlas"."T_ACTEUR_SOUSCRIPTEUR" AS "Extent2" ON
"Extent1"."id_personne" = "Extent2"."id_personne"
I know that if i changed the configuration of suscriber like this, it will work
public class SuscriberConfiguration : EntityTypeConfiguration<Suscriber>
{
public SuscriberConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
// m.Property(s => s.Number).HasColumnName("numero_souscripteur");
Property(s => s.Number).HasColumnName("numero_souscripteur");
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
}
}
In other words, if i configure the property Number outside the scope of the method Map or i don't apply the method Property to the parameter of the lambda, it works. (Property(s => s.Number).HasColumnName("numero_souscripteur");)
Could somebody explain me how the method MapInheritedProperties() works?
Why Entity Framework generate a non existing table?
Thanks
This is because everything that you put in one mapping fragment...
Map(m => { ... })
...is mapped to the table that the fragment applies to. In fact it's saying: map all properties from Agent and property Number from Subsriber to table "T_ACTEUR_SOUSCRIPTEUR". EF silently assumes that the leftovers are mapped to another table it devices by naming conventions. This happens as soon as you map one property from the derived type. If you only use MapInheritedProperties(), all properties, including the derived ones, are mapped to the table in the mapping fragment.
I don't know if this is a bug or a feature. It's not very clear at least. Anyway, you should map other property names outside the mapping fragment, as you already found out. Of course it's it more clear if you do that in a separate statement:
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
Property(s => s.Number).HasColumnName("numero_souscripteur");
Or if you like:
Map(m => m.MapInheritedProperties());
ToTable("T_ACTEUR_SOUSCRIPTEUR");
Property(s => s.Number).HasColumnName("numero_souscripteur");
I designed employee and employee relation tables as shown below:
public class Employee
{
public int EmployeeId { get; set; }
public EmployeeType EmployeeType { get; set; }
public int? ReportingManagerId { get; set; }
public Employee ReportingManager { get; set; }
public virtual List<EmployeeRelation> Colleagues { get; set; }
}
public class EmployeeRelation
{
public int EmployeeRelationId { get; set; }
public int EmployeeId { get; set; }
public Employee Employee { get; set; }
public int ColleagueId { get; set; }
public Employee Colleague { get; set; }
}
And corresponding configuration classes as:
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
HasKey(e => e.EmployeeId);
Property(e => e.EmployeeId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
HasOptional(e => e.ReportingManager).WithMany().HasForeignKey(e => e.ReportingManagerId);
}
}
public class EmployeeRelationConfiguration : EntityTypeConfiguration<EmployeeRelation>
{
public EmployeeRelationConfiguration()
{
HasRequired(e => e.Employee).WithMany().HasForeignKey(e => e.EmployeeId).WillCascadeOnDelete(false);
HasRequired(e => e.Colleague).WithMany().HasForeignKey(e => e.ColleagueId).WillCascadeOnDelete(false);
}
}
With the above code, everything is fine except that, for the table EmployeeRelations, EF 6.0.2 is creating one additional ForeignKey for EmployeeRelation as Employee_EmployeeId (in addition to EmployeeId & ColleagueId ForeignKeys). But if, I use Data Annotations instead of Fluent API, this extra ForeignKey is gone. I don't like Annotations and want to use Fluent API only. Can someone help me what is wrong with my code?
Thanks in advance.
UPDATE:
I resolved it myself.
Problem is multiple relations. If we look at EmployeeRelation class, it has 2 properties of type Employee which are binding to the Employee class and this is where EF is getting confused. To avoid this problem, we need to use overloaded version of WithMany API:
HasRequired(e => e.Employee).WithMany().HasForeignKey(e => e.EmployeeId).WillCascadeOnDelete(false);
HasRequired(e => e.Colleague).WithMany(e => e.Colleagues).HasForeignKey(e => e.EmployeeId).WillCascadeOnDelete(false);