Table per Hierarchy (TPH) Discriminator Value - entity-framework

We're using the fluent API to map a domain model to an existing database.
The database has a column called Discriminator, but the value stored in the column is not the same as the Type that the row relates to.
Given:
public class Vehicle
{
public int Id { get; set; }
}
public class Car : Vehicle
{
public int Doors { get; set; }
}
public class Bike : Vehicle
{
public bool Stubby { get; set; }
}
Can anyone tell me why this works:
public class VehicleMapping : EntityTypeConfiguration<Vehicle>
{
public VehicleMapping()
{
HasKey(x => x.Id);
Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Map<Car>(x => x.Requires("Discriminato").HasValue("C"));
Map<Bike>(x => x.Requires("Discriminato").HasValue("B"));
}
}
and this does not (just the requires bit has changed)
public class VehicleMapping : EntityTypeConfiguration<Vehicle>
{
public VehicleMapping()
{
HasKey(x => x.Id);
Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Map<Car>(x => x.Requires("Discriminator").HasValue("C"));
Map<Bike>(x => x.Requires("Discriminator").HasValue("B"));
}
}
EDIT
Sorry, to clarify what I'm asking:
Why is it not possible to use the default column name, but change the value/type that's stored in that column?
Is it possible?
Is there a workaround if not? (other than changing our schema)

Entity Framework TPH by default uses a column called Discriminator and your second code block is messing with that column. From MSDN:
By default, the discriminator column is added to the table with the
name “Discriminator” and the CLR type name of each type in the
hierarchy is used for the discriminator values. You can modify the
default behavior by using the fluent API.
So if you need to modify it, you need to choose your own name for the column which is what your first code block is doing and calling it Discriminato.

Related

The column name is specified more than once inserting into entities with 1 to 1 relationship

I have two tables that join in a 1 to 1 relationship
using System.ComponentModel;
public class Product
{
public Product()
{
MaterialProperties = new MoreProductInfo
{
Product = this
};
}
[Key]
public int ItemId { get; set; }
[ForeignKey("ItemId")]
public virtual MoreProductInfo MaterialProperties { get; set; }
}
public class MoreProductInfo : IObjectSpaceLink, IElipseLookUp
{
[Key]
public int ItemID { get; set; }
[ForeignKey("ItemId")]
public virtual Product Product { get; set; }
}
The relationship is set up in FluentAPI like this:
modelBuilder.Entity<Product>()
.HasOne<MoreProductInfo>(x => x.MaterialProperties)
.WithOne(x => x.Product)
.HasForeignKey<MoreProductInfo>(m => m.ItemID);
When I try to save a new product I get
Microsoft.Data.SqlClient.SqlException
HResult=0x80131904
The column name 'ItemID' is specified more than once in the SET clause or column list of an INSERT. A column cannot be assigned more than one value in the same clause. Modify the clause to make sure that a column is updated only once. If this statement updates or inserts columns into a view, column aliasing can conceal the duplication in your code.
Source=Core Microsoft SqlClient Data Provider
StackTrace:
at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
I am using XAF 21.2.8 on .NET 6 and Entity Framework Core 5.0.14
I tried the following;
modelBuilder.Entity<Product>()
.HasOne(x => x.MaterialProperties)
.WithOne(x => x.Product)
.HasForeignKey<MoreProductInfo>(m => m.ItemID)
.HasForeignKey<Product>(x => x.ItemId)
.HasPrincipalKey<Product>(x => x.ItemId)
.HasPrincipalKey<MoreProductInfo>(x = >x.ItemID);
But this gives the error
System.InvalidOperationException
HResult=0x80131509
Message=The principal and dependent ends of the relationship cannot be inverted once foreign key or principal key properties have been specified.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.Metadata.Builders.InvertibleRelationshipBuilderBase..ctor(InternalForeignKeyBuilder builder, InvertibleRelationshipBuilderBase oldBuilder, Boolean inverted, Boolean foreignKeySet, Boolean principalKeySet, Boolean requiredSet)
I got it working by adjusting the attributes to use a different column name in one of the entities as follows
public class MoreProductInfo : IObjectSpaceLink, IElipseLookUp
{
[Column("ItemID")]
[Key] public int ExtItemID { get; set; }
[ForeignKey("ExtItemID")]
public virtual Product Product { get; set; }
// etc
and correcting the Fluent api
modelBuilder.Entity<Product>().HasOne(x => x.MaterialProperties).WithOne(x => x.Product)
.HasForeignKey<MoreProductInfo>(m => m.ExtItemID).HasPrincipalKey<Product>(x => x.ItemId);

Entity Framework Core : invalid column name 'UserId1'

I am trying to use Entity Framework Core / .NET 5 to interact with my databases.
When I try to query DbContent.UserClaims I get the following error:
Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'UserId1'.
I am not sure where UserId1 us coming from. I have a property called UserId which is the foreign key. Here are the relation mapping
Here is what I tried to do in the DbContext class
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User>(user =>
{
user.HasKey(r => r.Id);
user.HasMany(x => x.UserRoles).WithOne().HasForeignKey(x => x.UserId);
user.HasMany(x => x.UserClaims).WithOne().HasForeignKey(x => x.UserId);
user.HasMany(x => x.UserTokens).WithOne().HasForeignKey(x => x.UserId);
});
builder.Entity<UserClaim>(userClaim =>
{
userClaim.HasKey(r => r.Id);
userClaim.HasOne(r => r.User).WithOne().HasForeignKey<UserClaim>(x => x.UserId);
});
}
Here is the UserClaim class which is derived from IdentityUserClaim
public class UserClaim : IdentityUserClaim<string>
{
public virtual User User { get; set; }
}
Here is the User class which is derived from IdentityUser
public class User : IdentityUser<string>
{
public virtual ICollection<UserToken> UserTokens { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
public virtual ICollection<UserClaim> UserClaims { get; set; }
}
Here is the query that EF5 is generating
SELECT [u].[Id], [u].[ClaimType], [u].[ClaimValue],[u].[UserId], [u].[UserId1]
FROM [UserClaims] AS [u]
How can I fix this issue in Entity Framework Core?
You're using shadow properties here, and on top of that, trying to add UserId foreign key to the User itself. Since UserId is an already defined property in that class, it's adding a suffix to the property name every time you're trying to add a foreign key in the user table by the same name.
It should be something like this:
modelBuilder.Entity<UserClaim>()
.Property<int>("UserForeignKey");
modelBuilder.Entity<UserClaim>()
.HasOne(a => a.User)
.WithMany(b => b.UserClaims)
.HasForeignKey("UserForeignKey")
Read the documentation on how to configure Fluent API for shadow properties, and some other ways to use the Fluent API.

Make reference table data read-only - EF Core

I have a table (Commodity) which has a one-to-one relationship with another table (CommodityMaterial), in my GET endpoint the Commodity returns it's own columns and also the columns (and values) of the referenced table which works perfectly. However, in the POST operation of the endpoint, a user should not be able to POST data of the reference table (CommodityMaterial), how can this be achieved? I used to disable this by using a DataContract, however, because I need the columns for my GET operator, this is not an option.
I already tried, following this post: https://csharp.christiannagel.com/2016/11/07/efcorefields/, removing the SET on the reference table and making a backing field but this does not seem to work (error that the backing field is read-only).
I also tried setting the SET to protected, but this is not working.
So the question is, how to make the reference table read-only (only available for my GET endpoint and not my POST endpoint).
The Commodity POCO class:
[DataContract]
public class Commodity
{
public Commodity()
{
}
public Commodity(CommodityMaterial commodityMaterial)
{
CommodityMaterial = commodityMaterial;
}
[DataMember]
public long CommodityID { get; set; }
[DataMember]
public long CommodityMaterialID { get; set; }
[DataMember]
public decimal? SpecficWeight { get; set; }
[DataMember]
public CommodityMaterial CommodityMaterial { get; }
}
Fluent part:
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.CommodityMaterialID)
.HasColumnName("CommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.CommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.CommodityMaterialID);
});
The parameters your action accepts should represent what your action does/is allowed to do. If a client should not be able to update a related entity, then the class you bind the request body to, should not have that entity available. Use a view model, essentially:
public class CommodityRequest
{
// all properties you want editable
// exclude `CommodityMaterial` obviously
}
Then:
public IActionResult Update(CommodityRequest model)

Entity Framework Many To Many exception with inheritance

I am trying to create two many-to-many relationship maps on a Record object:
Record object that is inherited from
public class Record {
public virtual ICollection<Language> SourceLanguages { get; set; }
public virtual ICollection<Language> TargetLanguages { get; set; }
}
Second Object
public class Language
{
public int Language { get; set; }
public string Locale { get; set; }
public string LanguageName { get; set; }
public virtual ICollection<Record> Records { get; set; }
}
Map for Record
public class RecordMap : EntityTypeConfiguration<Record>
{
this.HasMany(r => r.SourceLanguages)
.WithMany(c => c.Records)
.Map(sl =>
{
sl.ToTable("SourceLanguageRecordMap", "dbo");
sl.MapLeftKey("RecordId");
sl.MapRightKey("LanguageId");
});
this.HasMany(r => r.TargetLanguages)
.WithMany(c => c.Records)
.Map(tl =>
{
tl.ToTable("TargetLanguageRecordMap", "dbo");
tl.MapLeftKey("RecordId");
tl.MapRightKey("LanguageId");
});
}
When I run migration on the object listed above I get the following error:
System.Data.Entity.Core.MetadataException: Schema specified is not
valid. Errors: The relationship
'Toolbox.EntityModel.Contexts.Record_SourceLanguages' was not loaded
because the type 'Toolbox.EntityModel.Contexts.Language' is not
available. ...
Schema specified is not valid. Errors: The relationship
'Toolbox.EntityModel.Contexts.Record_SourceLanguages' was not loaded
because the type 'Toolbox.EntityModel.Contexts.Language' is not
available.
If I comment the following line out, it will work with just one many to many map, however, it will add RecordId_Record to Language Table. Any idea why?
this.HasMany(r => r.TargetLanguages)
.WithMany(c => c.Records)
.Map(tl =>
{
tl.ToTable("TargetLanguageRecordMap", "dbo");
tl.MapLeftKey("RecordId");
tl.MapRightKey("LanguageId");
});
Any idea as to what I am doing wrong?
If you have 2 Many-to-Many relationships to the same table you need to create 2 separate ICollection properties in order for Entity Framework to fully pick up on what you're trying to do. You can't combine them into one, or else you'll get that lovely error that you're seeing there.

Table-per-Hierarchy and Many to Many relationship

this may seem very easy to solve, but the complication I'm having is that I have a table per hierarchy to store all of the entities and I'm not able to create the relationships over the same table. Here is what I have in the DB and classes:
I have just one table named BaseObject with an ID, name and type. I will create two classes for those entities stored in there. Master and Component. The type column is the discriminator. I have another table to store the relationships between both: A master can have many components and a component also can have many other components.
This is the code I have for the classes:
public partial class BaseObject
{
public BaseObject()
{
}
public System.Guid ID { get; set; }
public string Name { get; set; }
public Nullable<int> Type { get; set; }
}
public class MasterObject : BaseObject
{
public virtual ICollection<ComponentObject> Components { get; set; }
public MasterObject()
{
this.Components = new List<ComponentObject>();
}
}
public class ComponentObject : BaseObject
{
public virtual ICollection<MasterObject> MasterObjects { get; set; }
public ComponentObject()
{
this.MasterObjects = new List<MasterObject>();
}
}
And these are the mappings:
public class BaseObjectMap : EntityTypeConfiguration<BaseObject>
{
public BaseObjectMap()
{
// Primary Key
this.HasKey(t => t.ID);
// Table & Column Mappings
this.ToTable("BaseObject");
this.Property(t => t.ID).HasColumnName("ID");
this.Property(t => t.Name).HasColumnName("Name");
//configure the inheritance in here
this.Map<MasterObject>(m => m.Requires("Type").HasValue(1));
this.Map<ComponentObject>(m => m.Requires("Type").HasValue(2));
}
}
public class MasterObjectMap : EntityTypeConfiguration<MasterObject>
{
public MasterObjectMap()
{
this.HasMany<ComponentObject>(t => t.Components)
.WithMany(t => t.MasterObjects)
.Map(c =>
{
c.ToTable("ObjectComponents");
c.MapLeftKey("ComponentObjectID");
c.MapRightKey("BaseObjectID");
});
}
}
public class ComponentObjectMap : EntityTypeConfiguration<ComponentObject>
{
public ComponentObjectMap()
{
this.HasMany<MasterObject>(t => t.MasterObjects)
.WithMany(t => t.Components)
.Map(m =>
{
m.ToTable("ObjectComponents");
m.MapLeftKey("BaseObjectID");
m.MapRightKey("ComponentObjectID");
});
}
}
The thing is that when quering the DB, I can get a Master by accessing the DBSet Masters, but the Component collection always gives a non-sense exception saying that "Problem in mapping fragments starting at line 6:Condition member 'BaseObject.Type' with a condition other than 'IsNull=False' is mapped. Either remove the condition on BaseObject.Type or remove it from the mapping."
I don't understand what's happening. Of course if the classes where pointing each to a table, this would be very easy, but I suspect that is the root of my problem.
Also, I'm just starting with EF. I wanted to create the classes based on my existing DB that I wouldn't like to modify at all. Unless it's really needed. Please guide me if what I'm trying to do is right or wrong, or what should I do first to fully implement EF on this project that is currently using NHibernate.
Any help here? Thanks
Finally I found the issue thanks to this answer: EF4.1 Exception creating Database with table-per-hierarchy inheritance
The problem is in the mapping of the Base class. When using a base class and a discriminator to get the children classes, the discriminator MUST NOT BE a property in neither instance. Just removed the discriminator as property and it worked fine. In my example, column 'Type' is the discriminator.
Hope this helps to someone else.