Why MapInheritedProperties() maps my entity into two tables? - c#

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

Related

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

Entity Framework Core 2 one to one relationship "on one table"

I have an entity that have a one to one relationship with itself:
public class Link
{
public long Id { get; set; }
public long OtherLinkId { get; set; }
public Link OtherLink { get; set; }
}
How can I define this relationship with fluent API?
My solution:
modelBuilder.Entity<Link>()
.HasOne(x => x.OtherLink)
.WithOne(x => x.OtherLink)
.OnDelete(DeleteBehavior.Restrict);
but on migration, it tries to create a second OtherLink.
You can achieve this by using a nullable ForeignKey attribute in your model class.
public class Link
{
public long Id { get; set; }
public long? OtherLinkId { get; set; } = null;
[ForeignKey("OtherLinkId")]
public Link OtherLink { get; set; }
}
You need to specify it. EF conventions can't find it without your help.
If you don't want to follow the convetions and need to specify it using data annotation attribute ForeignKey like below:
[ForeignKey("OtherLinkId")]
public Link OtherLink { get; set; }
Or using fluent configuration:
modelBuilder.Entity<Link>()
.HasOne(x => x.OtherLink)
.WithOne(x => x.OtherLink)
.HasForeignKey(p => p.OtherLinkId);
.OnDelete(DeleteBehavior.Restrict);
Important side note:
Your foreign key property should be nullable without that you'll not be able to insert item to your table OtherLink.
Your final code should look like this:
public class Link
{
public long Id { get; set; }
public long? OtherLinkId { get; set; } // <- Without this you'll be unable to insert a single link.
[ForeignKey("OtherLinkId")] // <- remove this if you use Fluent Configuration.
public Link OtherLink { get; set; }
}

How to query many-to-many releationship in EF Core

I'm using .NET Core and EF Core for a web project. I'm struggling how to query a many-to-many releationship. This is what my models look like:
public class Begrip
{
public int ID { get; set; }
public string Name { get; set; }
public string Desc { get; set; }
[Url]
public string URL { get; set; }
public ICollection<BegripCategory> Categories { get; set; }
}
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<BegripCategory> Begrippen { get; set; }
}
public class BegripCategory
{
public int begripId { get; set; }
public Begrip begrip { get; set; }
public int categoryId { get; set; }
public Category category { get; set; }
}
And my Database context:
public class PBBContext : DbContext
{
public PBBContext (DbContextOptions<PBBContext> options)
: base(options)
{
}
public DbSet<PBB.Models.Movie> Movie { get; set; }
public DbSet<PBB.Models.Begrip> Begrip { get; set; }
public DbSet<PBB.Models.Category> Category { get; set; }
public DbSet<PBB.Models.BegripCategory> BegripCategory { get; set; }
protected override void OnModelCreating(ModelBuilder modelbuilder)
{
modelbuilder.Entity<BegripCategory>().HasKey(bc => new { bc.begripId, bc.categoryId });
modelbuilder.Entity<BegripCategory>().HasOne(b => b.begrip).WithMany(bg => bg.Categories).HasForeignKey(bc => bc.begripId);
modelbuilder.Entity<BegripCategory>().HasOne(c => c.category).WithMany(ca => ca.Begrippen).HasForeignKey(cc => cc.categoryId);
}
}
What im trying to do is to return all the "Begrippen" in a JSON result with all the corresponding "Categories", however, I can't figure out how to get the list of "Categories" for them.
Any ideas? Thanks in advance.
EF Core won't load related properties automatically, so you'll need to explicitly do this, but something like the following should do the trick:
var result = context.Begrip
.Include(x => x.Categories)
.ThenInclude(x => x.category);
Note, intellisense doesn't always work on .ThenInclude at the moment, but the code should still compile even if it gets a red underline.
If you're returning this to the view or an API, you'll likely want to map it to a DTO so you don't have to deal with .Categories[0].category.Name etc.
If you need to filter a Many-to-Many relationship describe below I recomend to use a LinQ Enumerable Any method like this:
return result.Where(x => x.Categories.Any(c => c.category == categoryId));
To return a filtered list of entities related by a specific category.
EntityFrameworkCore Relationship query example
Extending #Richard's answer :
I noticed in Visual Studio 2017 15.5.6 when I do following:
return _context.Begrip
.Include(x => x.Categories)
.ThenInclude(y => y.<nothing typed in here yet>)
IntelliSense at first tells me that y if of type ICollection of BegripCategory presenting methods suitable for collections what is confusing especially that when I start typing "category" (in place of "nothing typed in here yet") IntelliSense changes as if we were dealing with only a single instance instead of ICollection
Just a tiny remark, but I hope it will help to save a few minutes time confusion.

Automapper source prefix property

We are designing a temporal system where the definition of an entity can change. I am trying to setup Automapper but can't quite work out how the prefix should work.
As an example, I would have the following entity:
public class ReferenceDataDefinition
{
public string Name { get; set; }
}
public class ReferenceData
{
public int Id { get; set; }
public ReferenceDataDefinition Current { get; set; }
}
With the following DTO:
public class ReferenceDataDTO
{
public int Id { get; set; }
public string Name { get; set; }
}
I know I can use
CreateMap<ReferenceData, ReferenceDataDTO>()
.ForMember(p => p.Id, o => o.MapFrom(s => s.Id)
.ForMember(p => p.Name, o => o.MapFrom(s => s.Current.Name);
But I feel there must be something smarter I can do?
I've tried adding RecognizePrefixes("Current") but that had no effect.
I've tried adding RecognizePrefixes("Current")
This isn't how prefix are used. They are for a scenario where your properties start with a prefix (often because of a database naming schema).
For example, If you had the following classes:
public class ReferenceData
{
public int Ref_Id { get; set; }
public string Ref_Name { get; set; }
}
public class ReferenceDto
{
public int Id { get; set; }
public string Name { get; set; }
}
You could recognize the following prefix:
cfg.RecognizePrefixes("Ref_");
AutoMapper would then be able to map those two objects without you having to define specific mappings with .ForMember.
Regarding you own mapping, since both Id properties on ReferenceData and ReferenceDataDTO have the same name, you should be able to remove the Id member mapping as AutoMapper can infer it automatically:
CreateMap<ReferenceData, ReferenceDataDTO>()
.ForMember(p => p.Name, o => o.MapFrom(s => s.Current.Name);
This should suffice.
As for .Current using Flattening you could remove it if you would change your DTO class to rename it to CurrentName.
Please check this documentation:
Recognizing pre/postfixes
Also the RecognizePrefixes works for source object prefixes
Use RecognizeDestinationPrefixes method
Check these previous posts:
AutoMapper with prefix
https://github.com/AutoMapper/AutoMapper/issues/421

Entity Framework Core code first - 0..1 to many relationship and cascade delete

I'm trying to create a commenting system backed by Entity Framework Core where multiple entities of different type can have comments attached to them.
These are my entities. (In the real application there are about 7 in total with varying relationships but this is how it generally looks)
public class Comment : IEntityBase
{
public int Id { get; set; }
public int? FreezerId{ get; set; }
public Freezer Freezer { get; set; }
public int? BoxId{ get; set; }
public Box Box{ get; set; }
public string Author { get; set; }
public DateTime CreatedAt { get; set; }
public string Content { get; set; }
}
public class Freezer: IEntityBase
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public ICollection<Box> Boxes{ get; set; }
public ICollection<Comment> Comments { get; set; }
}
public class Box: IEntityBase
{
public int Id { get; set; }
public Freezer Freezer{get; set;}
public int FreezerId{get; set;}
public string Data{ get; set; }
public ICollection<Comment> Comments { get; set; }
}
I want the Comment entity to be attached to one Freezer or one Box, but not both at the same time.
I defined the relationship in the fluent API as the following:
builder.Entity<Box>(boxBuilder=>
{
boxBuilder.HasOne(box=> box.Freezer)
.WithMany(freezer => freezer.boxes)
.HasForeignKey(box => box.FreezerId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
boxBuilder.HasMany(box => box.Comments)
.WithOne(comment => comment.Box)
.HasForeignKey(comment => comment.BoxId)
.OnDelete(DeleteBehavior.Cascade);
});
builder.Entity<Freezer>(freezerBuilder =>
{
freezerBuilder.HasMany(freezer=> freezer.Comments)
.WithOne(comment => comment.Freezer)
.HasForeignKey(comment => comment.FreezerId)
.OnDelete(DeleteBehavior.Cascade);
});
When I try to update the database to this model I get the following error:
System.Data.SqlClient.SqlException: Introducing FOREIGN KEY constraint 'FK_Comment_Boxes_BoxId' on table 'Comment' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I think the error comes from the Box and the Freezer property in the Comment class not being optional which would make this a 1 to many relationship instead of a 0..1 to many relationship which is what I want.
With Entity Framework 6 I would just use the .HasOptional() method, but this doesn't exist in Entity Framework Core
I think one way to solve this would be to just subclass the Comment class and create a unique comment class for each entity that can be commented on and move the foreign key and reference property to that subclass instead.
But it feels like I shouldn't have to do it this way.
You have to disable the cascade delete(DeleteBehavior.Restrict) then it will works for you:
modelBuilder.Entity<Box>(boxBuilder =>
{
boxBuilder.HasOne(box => box.Freezer)
.WithMany(freezer => freezer.Boxes)
.HasForeignKey(box => box.FreezerId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
boxBuilder.HasMany(box => box.Comments)
.WithOne(comment => comment.Box)
.HasForeignKey(comment => comment.BoxId)
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity<Freezer>(freezerBuilder =>
{
freezerBuilder.HasMany(freezer => freezer.Comments)
.WithOne(comment => comment.Freezer)
.HasForeignKey(comment => comment.FreezerId)
.OnDelete(DeleteBehavior.Restrict);
});
base.OnModelCreating(modelBuilder);
}
Usage:
using (var myConext = new MyDbContext())
{
myConext.Database.EnsureCreated();
myConext.Boxes.Add(new Box() {Freezer = new Freezer()});
myConext.SaveChanges();
}

Categories