EF Core 3.0 1:0 relationship with fluent - entity-framework

EF Core 3.0... I can't find a precise answer for this completely normal mapping.
Principal to Dependent with no back pointer to Principal, 1:0 relationship, a Type Object / Lookup table set up. The problem is that the Object Key Name "RunId" is different than the EFCore generated key name "ServiceRunId"
How can I use Fluent API to replace the [ForeignKey("aServiceRun")] annotation?
This is my current Fluent set up, but I don't know where to put the ForeignKey mapping.
aBuilder.Entity<ServiceRun>().HasKey(new string[] { "RunId "});
aBuilder.Entity<Service>().HasOne(s => s.aServiceRun);
Class Service {
public int ServiceId {get; set;}
[ForeignKey("aServiceRun")]
public int RunId { get; set; }
public virtual ServiceRun aServiceRun { get; set; }
}
Class ServiceRun {
public int RunId { get; set; }
public string description {get ;set; }
}
Tables:
Service {
ServiceId int
RunId int
}
SerivceRun {
RunId int
Description string
}

How can I use Fluent API to replace the [ForeignKey("aServiceRun")] annotation?
You are seeking for HasForeignKey fluent API. But in order to get access to it (and other relationship configuration APIs), you need to define the relationship by using Has{One|Many} followed by With{One|Many}. For one-to-one relationships you also need to provide the generic type argument to HasForeignKey:
When configuring the relationship with the Fluent API, you use the HasOne and WithOne methods.
When configuring the foreign key you need to specify the dependent entity type - notice the generic parameter provided to HasForeignKey in the listing below. In a one-to-many relationship it is clear that the entity with the reference navigation is the dependent and the one with the collection is the principal. But this is not so in a one-to-one relationship - hence the need to explicitly define it.
Note that the entity containing the FK is always the dependent, so with your model the ServiceRun is the principal, Service is the dependent, and the fluent configuration is a follows:
modelBuilder.Entity<Service>()
.HasOne(s => s.aServiceRun) // navigation property
.WithOne() // no navigation property
.HasForeignKey<Service>(s => s.RunId); // foreign key

I found my answer to the above problem - I had a back-pointing list on my ServiceRun object that was not configured or ignored. I decided to leave this here as another example. Perhaps it will provide some worth to someone.
This is a 1:0 from Service to ServiceRunType where table names and property/field names don't match perfectly.
Tables
ServiceRun { //Does not match object name
int Id,
string Desc
}
Service {
int Id,
int RunId //Does not match object
}
Objects
Class ServiceRunType{ //Does not match table name
public int Id {get; set;}
public String Desc {get; set;}
}
Class Service{
public int Id {get; set;}
public int RunTypeId {get; set;} //Does not match table
public virtual ServiceRunType aServiceRunType { get; set; }
}
Fluent Code
modelBuilder.Entity<ServiceRunType>()
.ToTable("ServiceRun", schema: "load")
.HasKey(new string[] { "Id" });
modelBuilder.Entity<Service>()
.ToTable("Service", schema: "load") //Had to specify schema
.HasKey(new string[] { "Id" });
modelBuilder.Entity<Service>()
.Property("RunTypeId")
.HasColumnName("RunId");
modelBuilder.Entity<Service>()
.HasOne(s => s.aServiceRunType)
.WithOne()
.HasForeignKey<Service>(s => s.RunTypeId);

Related

EF Core: Change naming strategy of FK Shadow Properties?

In EF Core, when defining Relationships, one can either provide the necessary FK properties explicitly or not:
Explicit FK property:
public class Person
{
public Guid Id { get; set; }
public ICollection<ParentIdentity> Identities { get; set; }
...
}
public class PersonIdentity
{
public Guid Id { get; set; }
public PersonFK { get; set; } //Explicit Data storage FK field in System Logic Entity :-(
...
}
The relationship would be defined in Fluent API as follows:
model.HasMany(x => Identities) // Person can have multiple identities
.WithOne() // Identity does not need a Nav property back up to Person
.WithForeignKey(x => x.PersonFK) // Hardcoded the FK.
The upside is its eminently clarity of how it's hooked up.
The downside is the blurring of domains between system logic and storage -- in that the system entity now has Data storage specific attributes (PersonFK) that have nothing to do with system logic that developers should be concentrating on.
Shadow properties
The alternative is to let EF sort it out, using shadow properties, by not define an FK Property on the Entity:
public class Person
{
public Guid Id { get; set; }
public ICollection<ParentIdentity> Identities { get; set; }
...
}
public class PersonIdentity
{
public Guid Id { get; set; }
...
}
And define the relationship as follows:
model.HasMany(x => Identities) // Person can have multiple identities
.WithOne() // Identity does not need a Nav property back up to Person
//.WithForeignKey(x => x.PersonFK) // Don't provide an FK property
;
EF will step up and add a property to the db table named to the following convention:
<principal primary key property name>Id
//ie, will be created as `PersonId`
But let's say I want to change it to:
<principal primary key property name>FK
//ie, will be created as `PersonFK`
Question
How?
Foraging so far
In case it helps, I'm looking in the following direction:
I can see a SqlServerConventionSetBuilder that inherits from RelationalConventionSetBuilder that inherits from ProviderConventionSetBuilder.
ProviderConventionSetBuilder in turn calls
ForeignKeyIndexConvention
ForeignKeyPropertyDiscoveryConvention
ForeignKeyAttributeConvention
found some sparse documentation at https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.metadata.conventions.foreignkeyindexconvention?view=efcore-6.0
but not enough there to know where to look really.
Can someone point me in the right direction as to:
what convention to replace
how to replace it easily?
Thank you!

Code First: TPT inheritance - Specify a different name for the primary key column in each table

I've posted my problem on codeplex http://entityframework.codeplex.com/workitem/2087.
There are also some questions posted here but they are not successfully answered.
See
Mapping TPT in EF Code First 4.1 w/ Different Primary Keys
Entity Framework 4 - TPT Inheritance in Features CTP5 (code first): rename foreign key column on inherited table
How can I use TPT inheritance models when primary keys have different names?
Is it now possible to have different column names for the primary keys when using TPT?
May be with 6.1.0
In TPT you're essentially do not want to declare the key in the subclasses, you'd miss the point otherwise.
If you must have a different Id name, just make proxy properties in the subclasses mapping to the base Id one.
public class BaseEntity
{
public int Id { get; set; }
}
public abstract class SubEntity : BaseEntity
{
public BaseId
{
get => Id;
set => Id = value;
}
}
Consider marking the sub fields as NotMapped, which in case you shouldn't include them in your LINQ queries.
With EF 6.4 I was able to use the ColumnAttribute to rename the Primary Key column in the dependent class
[Table("Person")]
public class Person
{
[Key]
public virtual int PersonId { get; set; }
// Person atributes...
}
[Table("Employee")]
public class Employee : Person
{
[Column("EmployeeId")] // <- Name of the primary Key column in the Database
public override int PersonId { get; set }
// Employee Attributes
}
Look at this code snip. Its work correct for me:
public partial class Person
{
// Any other PK name can thrown an exception
public int ID { get; set; }
}
public partial class Employee : Person
{
// Hide base class ID
private new int ID { get; set }
// Define derived class ID (that wrapped inherited ID)
[NotMapped]
public int EmployeeID
{
get { return base.PersonID; }
set { base.PersonID = value; }
}
}
Now, we must rename the inherited ID (with fluent API) for database table:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.Property(e => e.ID)
.HasColumnName("EmployeeID");
}

Foreign Key Not Mapped Correctly from Inheritance

If I have the following code generating my database it assigns a foreign key from the TankComponent table to the Asset table instead of the Tank table. Can someone explain why? Do I need to turn off a specific convention or change in the Fluent API? Is it really only looking at the column name?
[Table("Asset")]
public abstract class Asset
{
[Key]
public int AssetId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
[Table("Tank")]
public class Tank : Asset
{
public Tank()
{
this.TankCompnents = new Collection<TankComponent>();
}
public int TankField1 { get; set; }
public ICollection<TankComponent> TankCompnents { get; set; }
[NotMapped]
public IEnumerable<Floor> Floors { get { return this.TankCompnents.OfType<Floor>(); } }
}
[Table("TankComponent")]
public abstract class TankComponent
{
[Key]
public int TankComponentId { get; set; }
[ForeignKey("Tank")]
public int AssetId { get; set; }
public Tank Tank { get; set; }
public string Name { get; set; }
}
//forgot this in initial post
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Tank>()
.Map(m =>
{
m.Properties(a => new { a.AssetId, a.Name, a.Description });
m.Requires("AssetType").HasValue(1);
m.ToTable("Asset");
})
.Map(m =>
{
m.Properties(t => new { t.AssetId, t.TankField1 });
m.ToTable("Tank");
});
}
This mapping line...
m.Requires("AssetType").HasValue(1);
...and your comments seem to indicate that you possibly have a misunderstanding how Table-Per-Type (TPT) inheritance works.
EF does not need a specific column in the table of the base class Asset to detect what the actual type of the entity with a given primary key value is - unless you would use Table-Per-Hierarchy (TPH) inheritance mapping (i.e. a mapping without having [Table] attributes on your entities). For TPH a specific column - the discriminator - in indeed necessary to distinguish between the types because all properties of all entities in the inheritance tree would be stored in a single table. If you don't specify a discriminator explicitly - like AssetType - EF would create a column called Discriminator by default.
Now, TPT is a different story. If you query an entity that has other derived entities - for example...
var asset = context.Assets.First();
...EF will not only create a SQL query like SELECT TOP(1) * FROM ASSETS on the base table alone but instead a - possibly very complex - query with many LEFT OUTER JOINs to many other tables that belong to all possible derived entities. This query would either find a row in the Tank table or not. If it does find one EF will materialize a Tank object. If not it will materialize an Asset. (Cannot be the case here because Asset is abstract but assume for a moment it would not be abstract.) If Asset has other derived types EF will join their tables as well and decide again about the concrete entity type depending on the existence of joined rows.
So, with TPT the type is detected not by a special column but only by the result of (left outer) table joins.
The line above seems to confuse EF somehow. But it really doesn't belong into a TPT mapping and I would remove your whole mapping with Fluent API.
I've tested that the result is correct when you remove the mapping - i.e. the FK relationship will be created between TankComponent and Tank table (not Asset table).

EF and character PK/FK

I am a newbie in the EF. I read http://msdn.microsoft.com/en-us/data/gg193958.aspx and still confused.
I have an existing database and I'm writing a Code First model. I have Operators table with op_code Char(6) Primary Key. In the Operator class I named it OperatorCode, e.g.
[Key]
[Column("op_code",TypeName = "char")]
[DisplayName("Operator")]
public virtual string OperatorCode { get; set; }
In several of my tables I have EnteredBy and in some ModifiedBy columns that are FK to the op_code.
Say, for the Clients table I have both of these fields.
So, I added to the Operator class at the bottom:
[InverseProperty("EnteredBy")]
public virtual ICollection<Client> ClientsEnteredBy { get; set; }
[InverseProperty("ModifiedBy")]
public virtual ICollection<Client> ClientsUpdatedBy { get; set; }
and I added the following into the Client class:
public virtual Operator EnteredBy { get; set; }
public virtual Operator ModifiedBy { get; set; }
and I am getting a run-time error about EnteredBy_OperatorCode and ModifiedBy_OperatorCode columns.
What should I fix /add to let EF know my column names?
Thanks in advance.
Your foreign column names in the database do not match the default convention for FK names which is NavigationPropertyName_PrimaryKeyNameinTargetClass. Because your navigation properties are called EnteredBy and ModifiedBy and the primary key property is called OperatorCode EF expects - according to the mentioned convention - EnteredBy_OperatorCode and ModifiedBy_OperatorCode as foreign key columns. But those do not exist in the database which is the reason for your exception. Instead your FK columns are EnteredBy and ModifiedBy.
So, to fix the problem you must override the convention.
If you don't have FK properties in your model use Fluent API:
modelBuilder.Entity<Operator>()
.HasMany(o => o.ClientsEnteredBy)
.WithRequired(c => c.EnteredBy) // or HasOptional
.Map(m => m.MapKey("EnteredBy")); // mapping for the FK column name
modelBuilder.Entity<Operator>()
.HasMany(o => o.ClientsUpdatedBy)
.WithRequired(c => c.ModifiedBy) // or HasOptional
.Map(m => m.MapKey("ModifiedBy")); // mapping for the FK column name
(With this mapping you can remove the InverseProperty attribute.)
An alternative approach is to expose the FKs as properties in the model. Rename the navigation properties and use their names for the FK properties. The mapping is then possible with data annotations.
In Client class:
[ForeignKey("EnteredByOperator")]
public string EnteredBy { get; set; }
[InverseProperty("ClientsEnteredBy")]
public virtual Operator EnteredByOperator { get; set; }
[ForeignKey("ModifiedByOperator")]
public string ModifiedBy { get; set; }
[InverseProperty("ClientsUpdatedBy")]
public virtual Operator ModifiedByOperator { get; set; }
And remove the InverseProperty attributes in the Operator class.
Instead of the data annotations you can also use Fluent API:
modelBuilder.Entity<Operator>()
.HasMany(o => o.ClientsEnteredBy)
.WithRequired(c => c.EnteredByOperator) // or HasOptional
.HasForeignKey(c => c.EnteredBy);
modelBuilder.Entity<Operator>()
.HasMany(o => o.ClientsUpdatedBy)
.WithRequired(c => c.ModifiedByOperator) // or HasOptional
.HasForeignKey(c => c.ModifiedBy);
If both relationships are required you will need to disable cascading delete for at least one of the relationships (append .WillCascadeOnDelete(false) at the end of one of the mappings), otherwise SQL Server will throw an error that multiple cascading delete paths between the tables are not allowed.
I would suggest to use the "alternative approach" (expose foreign keys as properties) because it is easier to work with in most cases.

Mapping properties to (differently named) foreign key fields in Entity Framework CTP5

I'm trying to use the Entity Framework CTP5 Fluent API to map an exist database. I have the following classes:
public class Shop
{
public long Id
{
get;
set;
}
}
public class Sale
{
public long Id
{
get;
set;
}
public virtual Shop Shop
{
get;
set;
}
}
The corresponding tables are called "Stores" and "Sales". Sales has a StoreId foreign key that points to the Id field in the Stores table.
I'm struggling to map the Sale.Shop.Id to the StoreId in the table. I'm not at liberty to change it to ShopId, so need to map it.
In CTP4, I was using:
modelBuilder.Entity<Sale>().MapSingleType(x =>
new
{
Id = x.Id,
StoreId = x.Shop.Id
});
I tried the following:
modelBuilder.Entity<Sale>().Property(x => x.Shop.Id).HasColumnName("StoreId");
However, it seems this only works with a primitive type.
How do I specify this mapping?
Update: I've added a revised version for the Release Candidate of EF 4.1 below
After some hunting, I've found the answer that works for me:
EF4.1 RC version:
modelBuilder.Entity<Booking>().HasRequired(b => b.Booker)
.WithMany(m => m.BookedSlots).Map(p=>{
p.MapKey("BookerID");
});
in your case:
modelBuilder.Entity<Sale>().HasRequired(sale => sale.Shop)
.WithMany().Map(s=> {
s.MapKey("StoreId");
});
My version is slightly different because I have navigation properties on both sides of the relationship.
I think the best way to solve this would be to upgrade your independent Association to be a Foreign Key Association meaning that instead of hiding the foreign key ShopId, actually including it in Sale class. Then you can use Data Aannotations/Fluent API to change its column name to match to your existing schema:
public class Shop
{
public long Id { get;set; }
}
public class Sale
{
public long Id { get; set; }
[Column(Name="StoreID")]
public long ShopId { get; set; }
public virtual Shop Shop { get; set; }
}
Which results to the desired DB Schema:
I think what you're looking for is the RelatedTo attribute. More information in this ADO.NET team blog post.