Foreign key navigation property naming convention alternatives - c#

When using Entity Framework 4.1, are there alternatives to the naming conventions for Navigation Properties?
For example instead of doing this:
public virtual MyObject MyObject { get; set; }
To be
public virtual MyObject SomeOtherName { get; set; }
UPDATE:
When the [ForeignKey("OldStepId")] and [ForeignKey("NewStepId")] attribute is added, the generated SQL then becomes:
{SELECT
`Extent1`.`CompletedId`,
`Extent1`.`OldStepId`,
`Extent1`.`NewStepId`,
`Extent1`.`Name`,
`Extent1`.`Step_StepId`,
`Extent1`.`Step_StepId1`
FROM `Completed` AS `Extent1`}
which, the last two columns do not exist.

You can use the Data Annotations or the Fluent API to do this
Attribute Way
public virtual Int32 MyObjectId{get;set;}
[ForeignKey("MyObjectId")]
public virtual MyObject SomeOtherName { get; set; }
Fluent Way
modelBuilder.Entity<Type>()
.HasRequired(p => p.SomeOtherName)
.WithMany(d => d.Type)
.HasForeignKey(p => p.MyObjectId)
RESPONSE TO UPDATE
If you have a List in your MyOjbect class, then you need to mark that List as [InverseProperty("SomeOtherName")]. This might be why you are getting extra columns in your SQL. This keeps two-way relationships from being doubled up by telling the generator where the main column really is.

I generally call them the same name as the Foreign Key for the Nav Props.

If you add a T4 template to generate the content, you can pretty much adjust the naming scheme to whatever you want...

You can name them as you like. You must distinguish between navigation properties which have a (scalar) foreign key property exposed in the class ("Foreign Key associations") and navigation properties which have not ("Independent Associations"):
Foreign Key associations:
[ForeignKey("VeryDifferentFKPropertyName")] // refers to property, NOT column
public virtual MyObject SomeOtherName { get; set; }
[Column("JustAnotherColumnName")] // map property to column name
public int VeryDifferentFKPropertyName { get; set; }
With Fluent API:
modelBuilder.Entity<SomeEntity>()
.HasRequired(e => e.SomeOtherName) // or .HasOptional(...)
.WithMany()
.HasForeignKey(e => e.VeryDifferentFKPropertyName);
modelBuilder.Entity<SomeEntity>()
.Property(e => e.VeryDifferentFKPropertyName)
.HasColumnName("JustAnotherColumnName");
Independent Associations:
public virtual MyObject SomeOtherName { get; set; }
You can map the foreign key column name only with Fluent API:
modelBuilder.Entity<SomeEntity>()
.HasRequired(e => e.SomeOtherName) // or .HasOptional(...)
.WithMany()
.Map(a => a.MapKey("JustAnotherColumnName"));

Related

How to define navigation for in-class encapsulated property?

I inherited a shared project, where models are defined. For easier XML serialization they are in the form:
public class Blog
{
public int Id { get; set; }
public Posts Posts { get; set; }
}
public class Posts
{
public List<Post> PostsCollection { get; set; }
}
public class Post
{
public int BlogId { get; set; }
public int PostId { get; set; }
public string Content { get; set; }
}
How do I specify EF DbContext in OnModelCreating method to use Posts.PostsCollection as navigation property? Let's assume, I am not allowed to change anything in Post and Blog classes. I just need to programmatically specify relations for EF. Is it possible? I have read about defining relationships on MS site and also other topics about defining model on this site and various others, but couldn't find anything for my scenario.
It's possible, but the intermediate class must be mapped as fake entity, serving as principal of the one-to-many relationship and being dependent of one-to-one relationship with the actual principal.
Owned entity type looks a good candidate, but due to EF Core limitation of not allowing owned entity type to be a principal, it has to be configured as regular "entity" sharing the same table with the "owner" (the so called table splitting) and shadow "PK" / "FK" property implementing the so called shared primary key association.
Since the intermediate "entity" and "relationship" with owner are handled with shadow properties, none of the involved model classes needs modification.
Following is the fluent configuration for the sample model
modelBuilder.Entity<Posts>(entity =>
{
// Table splitting
entity.ToTable("Blogs");
// Shadow PK
entity.Property<int>(nameof(Blog.Id));
entity.HasKey(nameof(Blog.Id));
// Ownership
entity.HasOne<Blog>()
.WithOne(related => related.Posts)
.HasForeignKey<Posts>(nameof(Blog.Id));
// Relationship
entity
.HasMany(posts => posts.PostsCollection)
.WithOne()
.HasForeignKey(related => related.BlogId);
});
The name of the shadow PK/FK property could be anything, but you need to know the owner table name/schema and PK property name and type. All that information is available from EF Core model metadata, so the safer and reusable configuration can be extracted to a custom extension method like this (EF Core 3.0+, could be adjusted for 2.x)
namespace Microsoft.EntityFrameworkCore
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Metadata.Builders;
public static class CustomEntityTypeBuilderExtensions
{
public static CollectionNavigationBuilder<TContainer, TRelated> HasMany<TEntity, TContainer, TRelated>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, TContainer>> containerProperty,
Expression<Func<TContainer, IEnumerable<TRelated>>> collectionProperty)
where TEntity : class where TContainer : class where TRelated : class
{
var entityType = entityTypeBuilder.Metadata;
var containerType = entityType.Model.FindEntityType(typeof(TContainer));
// Table splitting
containerType.SetTableName(entityType.GetTableName());
containerType.SetSchema(entityType.GetSchema());
// Shadow PK
var key = containerType.FindPrimaryKey() ?? containerType.SetPrimaryKey(entityType
.FindPrimaryKey().Properties
.Select(p => containerType.FindProperty(p.Name) ?? containerType.AddProperty(p.Name, p.ClrType))
.ToArray());
// Ownership
entityTypeBuilder
.HasOne(containerProperty)
.WithOne()
.HasForeignKey<TContainer>(key.Properties.Select(p => p.Name).ToArray());
// Relationship
return new ModelBuilder(entityType.Model)
.Entity<TContainer>()
.HasMany(collectionProperty);
}
}
}
Using the above custom method, the configuration of the sample model will be
modelBuilder.Entity<Blog>()
.HasMany(entity => entity.Posts, container => container.PostsCollection)
.WithOne()
.HasForeignKey(related => related.BlogId);
which is pretty much the same (just one additional lambda parameter) as the standard configuration if collection navigation property was directly on Blog
modelBuilder.Entity<Blog>()
.HasMany(entity => entity.PostsCollection)
.WithOne()
.HasForeignKey(related => related.BlogId);
It's not clear from the question, but I assume you only have the Blog and Post table in your database, and the Posts table does not exists and only has a class in the code.
You could have the Blog and Posts entities mapped to the same table as a splitted table and define the navigation property for that. For this you need to add one property to the Posts class (the Id as in the Blog) but you said you are only not allowed to change the Blog and Post classes, and if you need it to XML serialization, you can just mark this property with the [XmlIgnoreAttribute] attribute.
public class Posts
{
[XmlIgnoreAttribute]
public int Id { get; set; }
public List<Post> PostsCollection { get; set; }
}
Then in your OnModelCreating method:
modelBuilder.Entity<Blog>(entity => {
entity.ToTable("Blog");
entity.HasOne(b => b.Posts).WithOne().HasForeignKey<Blog>(b => b.Id);
});
modelBuilder.Entity<Posts>(entity => {
entity.ToTable("Blog");
entity.HasOne<Blog>().WithOne(b => b.Posts).HasForeignKey<Posts>(p => p.Id);
entity.HasMany(p => p.Post).WithOne().HasForeignKey(p => p.BlogId).HasPrincipalKey(p => p.Id);
});
modelBuilder.Entity<Post>(entity => {
entity.ToTable("Post");
entity.HasOne<Posts>().WithMany().HasForeignKey(p => p.BlogId).HasPrincipalKey(p => p.Id);
});

Entity Framework, Foreign key constraint may cause cycles or multiple cascade paths

I use Entity Code first for my project. Basically I have 3 class Users,Branchs and UsersBranchs.
Users contains UserID, Name ,...
Branchs contains BranchID, Location, ... and UserID which is refer to creator of branch
and UsersBranchs just have have two column BranchID and UserID which is define which user is in which branch
the problem is I get this error:
'FK_dbo.UsersBranchs_dbo.Users_UsersID' on table 'UsersBranchs' may
cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or
ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Can you help me please?
Update
It's UsersBranchs Class
[ForeignKey("UserID")]
public CoreUsers User { get; set; }
public Guid UsersID { get; set; }
[ForeignKey("BranchID")]
public Branchs Branch { get; set; }
public Guid BranchID { get; set; }
And also add this line to DbContext class to use both UserID and BranchID as key
modelBuilder.Entity<UsersBranchs>().HasKey(x => new { x.UserID, x.BranchID });
Branchs Class is
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid ID { get; set; }
[ForeignKey("UserID")]
public CoreUsers User { get; set; }
public Guid UserID { get; set; }
public .....
Users Class is
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid ID { get; set; }
public .....
Not being able to handle multiple cascade paths and cascade delete to same table has been a limitation of Sql Server for a long time. Just Google the error message. Basically, if you want to use cascade delete then you'll have to make sure that there is only a single cascade path.
At the moment you have two paths from Branchs -> UsersBranchs and Branchs -> Users -> UsersBranchs.
EF by default sets cascade delete but it may be stopped by removing the conventions in your DbContext.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Manually set cascade delete behaviour
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
base.OnModelCreating(modelBuilder);
}
Then you'll have to set WillCascadeOnDelete(true) on any relationships where you want cascade delete. See the Entity Framework documentation.
Other than that your model seems a bit strange. You look like you're trying to make a many-to-many link/join table, UsersBranchs, but you also have a single User on the Branchs which doesn't really make sense. Do you even need the UsersBranchs table in that case? Did you mean to have a collection of Users on your Branchs, i.e. a navigation property rather than a foreign key, which gives a one-to-many relationship Branchs -> Users?
As an aside I really dislike using plurals for single entities.
I think you are getting the problem because you didn't tell Entity framework how it will treat these classes on delete on cascade
in your DbContext class, override the OnModelCreating method and write this code
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<UserBranch>()
.HasRequired(t => t.CoreUsers)
.WithMany()
.HasForeignKey(t => t.UserID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserBranch>()
.HasRequired(t => t.Branch)
.WithMany()
.HasForeignKey(t => t.BranchID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Branch>()
.HasRequired(t => t.User)
.WithMany()
.HasForeignKey(t => t.UserID)
.WillCascadeOnDelete(false);
}
hope this will help you

How to expose Foreign Key property to existing entity having navigational property using EF6 Code First

I have an entity which is already being used with an underlying database, and it was created with just the navigational property to an optional entity (1:0..1). So by default conventions, EF created a nullable foreign key column in the DB and gave it the "MyProp_Id" name with underscore, according to that convention.
Now, I wish to expose that foreign key as a property on the entity, because it will make certain scenarios easier for me. I don't want to rename/change the underlying foreign key column in the DB (the MyProp_Id one). In fact, there shouldn't be any underlying DB updates, I just want to expose that FK on the entity. A code sample to clarify:
public class MyEntityA
{
public long Id { get; set; }
//public long? MyOptionalEntityB_Id { get; set; } <== this is what I am trying to add
//public long? MyOptionalEntityBId { get; set; } <== this didn't work either
public MyEntityB MyOptionalEntityB { get; set; }
}
I've tried just simply adding the "MyOptionalEntity_Id" property as property on the entity, hoping that EF would "automagically" see that because the names are the same, it would just map and be happy. NO DICE.
Then I tried to name my property "MyOptionalEntityId" (no underscore), but still NO DICE.
Then I tried adding an explicit mapping configuration to say:
this.Property(p => p.MyOptionalEntityId).HasColumnName("MyOptionalEntity_Id");
NO DICE
Is there a way to do this? Is this clear and make sense?
Try adding foreign key attribute.
public long? MyOptionalEntityB_Id { get; set; }
[ForeignKey("MyOptionalEntityB_Id")]
public MyEntityB MyOptionalEntityB { get; set; }
Or using fluent api.
modelBuilder.Entity<MyEntityA >()
.HasOptional(x => x.MyOptionalEntityB)
.WithMany().HasForeignKey(x => x.MyOptionalEntityB_Id);
// ^^^ -> if MyEntityB has collection of MyEntityA, mention it

Entity Framework Parent Child - Child refers to parent more than once

I have a situation with EF5 and a complex object. The basics is that I have a parent to child complex object, but the child refers back to the parent, more than once. I have tried various options but am not finding a solution that answers the question. The closest I have got is this answer (option 2c)
My model looks like below:
public class StaffMember
{
public virtual Guid StafId { get; set; }
// other props
// List of leave apps (Approved/Cancelled etc)
public virtual ICollection<StaffLeaveApp> LeaveApps { get; set; }
}
//Staff Leave Application
public class StaffLeaveApp
{
public virtual Guid LeaveId { get; set; }
public virtual Guid StaffId { get; set; }
// other props...
// Leave approved by? (2 approvals required)
public virtual StaffMember ApprovedBy1 { get; set; }
public virtual StaffMember ApprovedBy2 { get; set; }
}
my mappings look like this
public class StaffMap : EntityTypeConfiguration<StaffMember>
{
public StaffMap()
{
ToTable("tblStaffMembers");
HasKey(x => x.StaffId);
// other mappings...
HasMany(x => x.LeaveApps);
}
}
public class StaffLeaveAppMap: EntityTypeConfiguration<StaffLeaveApp>
{
public StaffLeaveAppMap()
{
ToTable("tblStaffMembersLeaveApps");
HasKey(x => x.LeaveId);
Property(x => x.StaffId).HasColumnName("StaffID");
//Child Relationships
HasOptional(x => x.ApprovedBy1).WithMany().Map(m => m.MapKey("LeaveApprovedBy1"));
HasOptional(x => x.ApprovedBy2).WithMany().Map(m => m.MapKey("LeaveApprovedBy2"));
}
}
Table (sorry, no images)
StaffID uniqueidentifier (FK - tblStaffMembers)
LeaveID uniqueidentifier (PK)
LeaveApprovedBy1 uniqueidentifier (FK - tblStaffMembers)
LeaveApprovedBy2 uniqueidentifier (FK - tblStaffMembers)
The business rule says: a staff member has "many" leave applications and a leave application belongs to a single staff member. Each application requires the approval of 2 staff members (managers) before it is "approved".
How would I map the above using EF so that a single staff member has a "many" leave applications (working already) and a leave application is mapped back to a staff member whom approved it for the first approval and then again for the seond approval. If I use the one mapping for "ApprovedBy1" only then EF is happy and all works as expected. The moment I add the second approval mapping EF struggles with the SQL queries it generates.
I am not sure how to tell EF to map back to the StaffMembers table to specify whom approved the application at level 1 and whom approved it at level 2. It almost ends up being a many to many relationship.
Any ideas?
You are looking for the inverse property, which is the property at the other end of an association. In EF, there are two way to mark a property as inverse.
Data annotations: InversePropertyAttribute.
Fluent mapping
As you already have fluent mapping I'll show you how you'd do it there:
HasOptional(x => x.ApprovedBy1).WithMany(x => x.LeaveApps)
.HasForeignKey(s => s.StaffId);
HasOptional(x => x.ApprovedBy2).WithMany()
.Map(m => m.MapKey("LeaveApprovedBy2"));
The HasOptional(...).WithMany(...) pair is a way to map inverse properties. Coming from the other side you can use e.g. HasMany(....).WithOptional(...).

EF code-first with 2 non-null Foreign Keys in the same table

I am using Entity Framework code first.
I was trying to put 2 foreign keys on the same table but I am not being allowed to do so.
It's like that(edited) :
The table is called Avaliacao:
[Required]
public int AvaliacaoId { get; set; }
[Required]
public int LivroId { get; set; }
public int? AutorId { get; set; }
public virtual Livro Livro { get; set; }
public virtual Autor Autor { get; set; }
I wanted AutorId not to be null but it only works that way.
I wish I could have 2 non-nullable FK but only one Delete on Cascade.
How do I achieve this with Entity Framework code-first?
Somebody help me please
thx in advance
ZeCarioca
EDIT:
I have not tested this, but if you are using EF5 you could make use of the OnModelCreating method by overiding it in your DbContext. You can call the same entity multiple times to add configuration so you could specify a second foreign key, set its HasRequired property and set its WillCascadeOnDelete property to true.
Something like this for the first foreign key.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Avaliacao>()
.HasRequired(a => a.LivroId)
.HasForeignKey(m => a.LivroId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Avaliacao>()
.HasRequired(a => a.AutorId)
.HasForeignKey(m => a.AutorId)
.WillCascadeOnDelete(false);
}
For more reference on the method you can look here at the MSDN Docs: DbModelBuilder
As mentioned I have not tested this myself so you might need to change some of the properties.
Hope it helps
You can do this through the fluent API, for example:
modelBuilder.Entity<MyEntity>().HasRequired(t => t.Livro);
modelBuilder.Entity<MyEntity>().HasRequired(t => t.Autor);

Categories