Rename foreign key column not working in fluent api - c#

I am trying to make a self referencing table
public class Category
{
// PK
public int CategoryId { get; set; }
// Property
public string CategoryName { get; set; }
// FK
public int? ParentCategoryId { get; set; }
public virtual ICollection<Category> ParentCategories { get; set; }
public virtual ICollection<Product> Products { get; set; } // Product is defined later
}
and the configuration:
public class CategoryConfiguration : EntityTypeConfiguration<Category>
{
public CategoryConfiguration():base()
{
HasKey(c => new { c.CategoryId });
HasOptional(c => c.ParentCategories)
.WithMany()
.HasForeignKey(c => c.ParentCategoryId );
}
}
the idea is to use ParentCategoryId as the column name, but it's not working. Instead it generated a column named: Category_CategoryId.
I have tried to use .Map(c => c.MapKey("ParentCategoryId")) and the result was the same.
I don't think it is the reason of self referencing because the same thing happen in the many-to-many relationship :
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
and the Product configuration
public class ProductConfiguration:EntityTypeConfiguration<Product>
{
public ProductConfiguration():base()
{
// Many-to-Many
HasMany(c => c.Categories)
.WithMany()
.Map(p =>
{
p.MapLeftKey("ProductRefId");
p.MapRightKey("CategoryRefId");
p.ToTable("ProductCategory");
});
}
}
The table name is ProductCategories instead of ProductCategory
The foreign key is Product_ProductId and Category_CategoryId
They all not what is expecting.
how can i solve the problem? Please help.
Thank you!
Update 1
strange thing is if I define it via DbModelBuilder, then it works
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>()
.HasOptional(c => c.ParentCategories)
.WithMany()
.HasForeignKey(c => c.ParentCategoryId);
}
the foreign key become ParentCategoryId as Expected.

Problem solved, I made a mistake. I didn't hook the configuration into DbContext.
by add these into the DbContext, the columns are renamed as expected.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new CategoryConfiguration());
modelBuilder.Configurations.Add(new ProductConfiguration());
}

Related

Cascade delete in Entity Framework 6 fails to delete dependents. Any suggestions

I am new to Eternity Framework 6 and I read that by default Cascade Delete is enabled. I currently have the following models
public class Student
{
public int Id { get; set; }
public List<Sport> Sports { get; set; }
public string StudentName { get; set; }
}
public class Sport
{
public int Id { get; set; }
public string SportName { get; set; }
public List<Coach> {get; set;}
}
public class Coach
{
public int Id { get; set; }
public string CoachName { get; set; }
}
This is what my context looks like
public class MyContext: DbContext
{
public MyContext(): base("name=MContext")
{
}
public DbSet<Student> Students{ get; set; }
}
Now this is how I am removing Sports played by a student. If cascade delete is turned on then all the coaches who are asscociated with a Sport should also be deleted but they are not being deleted.
This is how I am removing items
//Currently there is only 1 student in the DB
Student st = context.Students.SingleOrDefault();
// Get all the Sports of this student
this.context.Entry(st).Collection(x => x.Sports).Load();
// Clear the actions
dm.Sports.Clear();
The above basically assigns the Foreign Key of Student in Sports Table to NULL and it does not delete any of the asscociated coaches with a sport in Coach Table. Any suggestions on why the Coach name is not being deleted and how I can fix this ?
You need in your DBContext to represent relationships
entity.HasOne(d => d.IdStudentNavigation)
.WithMany(p => p.Sports)
.HasForeignKey(d => d.IdStudent)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_Sports_Students");
this article helped me get the idea
You will have to use the fluent API to do this.
Try adding the following to your DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOptional(a => a.UserDetail)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
}

One to one EF, where one of the objects has a composite key

I have
public class Expense
{
public int Id { get; private set; }
[ForeignKey("AccountId")]
public virtual Account Account { get; private set; }
[Required]
public int AccountId { get; private set; }
public virtual ExpenseCategory ExpenseCategory { get; private set; }
public Expense(... params ...)
{
this.ExpenseCategory = new ExpenseCategory();
}
protected Expense()
{
}
}
public class Account
{
[Key]
public int Id { get; private set; }
public virtual List<Expense> Expenses { get; private set; }
...
}
public class ExpenseCategory
{
[ForeignKey("CategoryId")]
public virtual BaseCategory Category { get; private set; }
public Guid? CategoryId { get; private set; }
public virtual Expense Expense { get; private set; }
[Key, ForeignKey("Expense")]
public int ExpenseId { get; private set; }
// EF is a 'friend' assembly, don't worry about the internal
internal ExpenseCategory()
{
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// This deletes the Expense entity when it is removed from the Account
modelBuilder.Entity<Expense>()
.HasKey(t => new { t.Id, t.AccountId })
.Property(t => t.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// PROBLEM STARTS HERE MAYBE
modelBuilder.Entity<ExpenseCategory>()
.HasKey(e => e.ExpenseId);
modelBuilder.Entity<Expense>()
.HasRequired(s => s.ExpenseCategory)
.WithRequiredPrincipal(tc => tc.Expense);
}
My relation between Expense and Account is fine - it works exactly as I need it. I am now trying to introduce a 1:1 (I know MSSQL does not support it natively, but EF works around this) relation between the Expense and a Category. I want ExpenseCategory to be my mapping between an Expense and a Category. There must be only one ExpenseCategory per Expense and I want the Key of that mapping to be the ID of the Expense.
I am having trouble, with anything I try. With the current setup I am getting:
the number of properties in the dependent and principal roles in a
relationship constraint must be identical.
I think the issue might be coming from the composite key on the Expense.
Any help?
Figured it out. Added the following on my ExpenseCategory
public int AccountId { get; private set; }
And the following Fluent API:
modelBuilder.Entity<ExpenseCategory>()
.HasKey(e => new { e.ExpenseId, e.AccountId });
modelBuilder.Entity<Expense>()
.HasRequired(s => s.ExpenseCategory)
.WithRequiredPrincipal(tc => tc.Expense)
.WillCascadeOnDelete(true);

Add a one-to-many connection to Many-to-Many

I have a Many To Many relationship with some additional fields. But as there are Photos added to the many to many relationship which might apply to other relations I wanted to seperate it so I can change it by just altering the One to many relation. This is the model
public class Segment
{
public int SegmentId { get; set; }
public int ConnectionPointIdEnd { get; set; }
public string ConnectionName { get; set; }
public string ConnectionInformation { get; set; }
public string Image { get; set; }
public string Direction { get; set; }
public ICollection<ConnectionPointRoute> ConnectionPointRoutes { get; set; }
}
public class ConnectionPointRoute
{
public int ConnectionPointId { get; set; }
public int RouteId { get; set; }
public int SegmentId { get; set; }
public int Position { get; set; }
public ConnectionPoint ConnectionPoint { get; set; }
public Route Route { get; set; }
public Segment Segment { get; set; }
}
And the modelbuilder looks like this :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ConnectionPointRoute>()
.HasKey(c => new { c.ConnectionPointId, c.RouteId, c.SegmentId });
modelBuilder.Entity<ConnectionPoint>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.ConnectionPoint)
.HasForeignKey(c => c.ConnectionPointId);
modelBuilder.Entity<Route>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.Route)
.HasForeignKey(c => c.RouteId);
modelBuilder.Entity<Segment>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.Segment)
.HasForeignKey(c => c.SegmentId);
}
And this all works well for getting the items, but for some reason it doesn't allow me to post a new Route for instance, it gets me the error:
"Multiplicity constraint violated. The role
'Segment_ConnectionPointRoutes_Source' of the relationship
'InBuildingNavigator.Data.Models.Segment_ConnectionPointRoutes' has
multiplicity 1 or 0..1."
Any thoughts?
Fixed this! I had an error in my Post code, I added the full child objects which doesn't make a whole lot of sense in my case.
Ask me if you want a more detailed fix!
Just two more things to this:
I would recommend you to use an extra object for the many-to-many relationship (if you don't already do this). This will give you more control over the table name and over selections you may want to do.
use the virtual keyword for your properties, which you do not need directly (for your collections) - this will allow ef to implement lazy loading on them.

EF Code first one way navigation property

I have the following entities:
[KnownType(typeof(Script))]
public class Application : IEntity
{
public long ID { get; set; }
public string Name { get; set; }
public long? StartScriptID { get; set; }
// Other properties...
// Navigation properties:
public virtual Script StartScript { get; set; } // new
public virtual List<Script> Scripts { get; set; }
}
[KnownType(typeof(Application))]
public class Script : IEntity
{
public long ID { get; set; }
public string Name { get; set; }
// Other properties...
// Navigation properties:
public virtual Application Application { get; set; }
}
I have no fluent configurations on the DbContext
So the model exists of Scripts which must be bound to an application. Applications can have a startScript defined. All this I already have working.
Now I am in need of a navigation property: Application.StartScript.
The question is how can I add the navigation property on Application without having to add a the equivalent navigation property on Script.
Thanks in advance!
EDIT: When I run Add-Migration the following migration code is generated:
public override void Up()
{
DropForeignKey("dbo.Scripts", "ApplicationID", "dbo.Applications");
AddColumn("dbo.Scripts", "Application_ID", c => c.Long());
CreateIndex("dbo.Applications", "StartScriptID");
CreateIndex("dbo.Scripts", "Application_ID");
AddForeignKey("dbo.Applications", "StartScriptID", "dbo.Scripts", "ID");
AddForeignKey("dbo.Scripts", "Application_ID", "dbo.Applications", "ID");
}
This creates a new column on Scripts which I don't need, as I already have the StartScriptID column.
EDIT: Update after #haim770 answer
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Application>().HasOptional(x => x.StartScript).WithOptionalDependent(x => x.Application);
}
public override void Up()
{
DropForeignKey("dbo.Scripts", "ApplicationID", "dbo.Applications");
DropIndex("dbo.Scripts", new[] { "ApplicationID" });
RenameColumn(table: "dbo.Applications", name: "ApplicationID", newName: "StartScript_ID");
AddColumn("dbo.Scripts", "Application_ID", c => c.Long());
CreateIndex("dbo.Applications", "StartScript_ID");
CreateIndex("dbo.Scripts", "Application_ID");
AddForeignKey("dbo.Scripts", "Application_ID", "dbo.Applications", "ID");
AddForeignKey("dbo.Applications", "StartScript_ID", "dbo.Scripts", "ID");
}
It doesn't understand that it should use the already existing StartScriptID. Is there a way to point it in the right direction?
EDIT: Wanted database structure:
Applications:
ID, PK, bigint, not null
Name, nvarchar(max), not null
StartScriptID, bigint, null
Scripts:
ID, PK, bigint, not null
Name, nvarchar(max), not null
ApplicationID, FK, bigint, not null
EDIT:
public class Application
{
[InverseProperty("StartScript")]
public long? StartScriptID { get; set; }
[ForeignKey("StartScriptID")]
public virtual Script StartScript { get; set; }
}
I was thinking there was no changes needed in the database, so I have tried adding migration with -IgnoreChanges. But then I got an EntityCommandExecutionException when querying for entities: "Invalid column name 'Application_ID'". So the Entity Framework needs some configuration for telling to use the StartScriptID property.
You can modify the mapping using the ForeignKeyAttribute and the InversePropertyAttribute.
If that doesn't work the way you want, you can always edit the migration yourself before applying it.
If you want to customize the column name, try the ColumnAttribute:
public class Application
{
[Column("DatabaseColumnName")]
public long? StartScriptID { get; set; }
[ForeignKey("StartScriptID")]
public virtual Script StartScript { get; set; }
}
A longer explanation can be found here: http://msdn.microsoft.com/de-de/data/gg193958.aspx
Try to use EntityTypeConfiguration It makes more easy to handle this kind of problem. Then:
public class Application
{
public long ID { get; set; }
public string Name { get; set; }
public long? StartScriptID { get; set; }
// Other properties...
// Navigation properties:
public virtual Script StartScript { get; set; } // new
public virtual ICollection<Script> Scripts { get; set; }
}
public class Script
{
public long ID { get; set; }
public string Name { get; set; }
public int ApplicationId { get; set; }
// Other properties...
// Navigation properties:
public virtual Application Application { get; set; }
}
public class ApplicationMap : EntityTypeConfiguration<Application>
{
public ApplicationMap()
{
this.HasKey(t => t.ID);
this.ToTable("TB_APLICATION", "aplication");
this.Property(t => t.ID).HasColumnName("id");
this.Property(t => t.Name).HasColumnName("name");
this.Property(t => t.StartScriptID).HasColumnName("startscript_id");
this.HasOptional(t => t.StartScript)
.WithMany()
.HasForeignKey(t => t.StartScriptID);
}
}
public class ScriptMap : EntityTypeConfiguration<Script>
{
public ScriptMap()
{
this.HasKey(t => t.ID);
this.ToTable("TB_APLICATION", "aplication");
this.Property(t => t.ID).HasColumnName("id");
this.Property(t => t.Name).HasColumnName("name");
this.HasRequired(t => t.Application)
.WithMany(w => w.Scripts)
.HasForeignKey(t => t.ApplicationId);
}
}
Don't forget to tell EF about the Mappings: (Call it inside OnModelCreating)
public static class MappingConfig
{
public static void ConfigureMap(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ApplicationMap());
modelBuilder.Configurations.Add(new ScriptMap());
}
}

Entity Framework - Link Table Name Issue

I have a table called "Articles" and then a linking table called "RelatedArticles".
The RelatedArticles table simply maps an article to another article. Entity Framework is looking for the table "ArticleArticles". What can I put in the OnModelCreating method that tells Entity Framework the actual name of this linking table?
Table RelatedArticles {
int articleId
int relatedArticleId
}
There is no class "RelatedArticles". Only a List property in the class Article:
public class Article
{
public Article()
{
Links = new HashSet<Link>();
RelatedArticles = new HashSet<Article>();
}
public int Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public System.DateTime Created { get; set; }
public virtual ICollection<Link> Links { get; set; }
public virtual ICollection<Article> RelatedArticles { get; set; }
}
You can override OnModelCreating to specify the table and column names:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Article>()
.HasMany(a => a.RelatedArticles)
.WithMany()
.Map(t => t.MapLeftKey("articleId")
.MapRightKey("relatedArticleId")
.ToTable("RelatedArticles"));
}
If it were a class you could use the attribute [Table(Name = "RelatedArticles"]. See the docs about TableAttribute on MSDN.
You've mentioned you don't, so I think you want this:
modelBuilder
.Entity<Article>
.HasMany(a => a.RelatedArticles)
.WithMany()
.Map(a => a.ToTable("RelatedArticles");

Categories