EF Code first one way navigation property - c#

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

Related

Efcore issue with many 2 many on same entity (Products & RelatedProducts)

So i want to add RelatedProducts to my products. So i applied the same relationship type as i did for category. The only difference there is no category class, but we target the same entity. (product). Because the regular many2many works fine, i removed that from my example.
Goal:
Any suggestions?
Exception:
"Exception occured: Cannot create a relationship between 'Product.RelatedProducts' and 'RelatedProduct.Related' because a relationship already exists between 'Product.RelatedProducts' and 'RelatedProduct.Product'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'RelatedProduct.Related' first in 'OnModelCreating'."
Product.cs:
public class Product : IExportable, IEntityBase
{
public int Id { get; set; }
public string Name { get; set;}
public ICollection<RelatedProduct> RelatedProducts { get; set; }
}
ProductEntityTypeConfiguration.cs
internal class ProductEntityTypeConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
config.HasKey(p => p.Id);
}
}
RelatedProducts.cs
public class RelatedProduct
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int RelatedId { get; set; }
public virtual Product Related { get; set; }
}
RelatedProductEntityTypeConfiguration.cs
public class RelatedProductsEntityConfiguration : IEntityTypeConfiguration<RelatedProduct>
{
public void Configure(EntityTypeBuilder<RelatedProduct> builder)
{
builder.HasKey(rp => rp.Id);
builder.ToTable("RelatedProducts");
builder
.HasOne(rp => rp.Product)
.WithMany(p => p.RelatedProducts)
.HasForeignKey(rp => rp.ProductId)
.OnDelete(DeleteBehavior.Restrict);
builder
.HasOne(rp => rp.Related)
.WithMany(p => p.RelatedProducts)
.HasForeignKey(rp => rp.RelatedId)
.OnDelete(DeleteBehavior.Restrict);
}
}
I am unable to test this suggestion but I am sure it might work. Why don't you just define your RelatedProduct as:
public class RelatedProduct
{
public int Id { get; set; }
public virtual ICollection<Product> Related { get; set; }
}
I also suggest you comment out the code in your RelatedProductsEntityConfiguration Configure method

How to code C# MVC model for many to many join table for sqlite database migration { edited }

I am creating a sqlite database to track users assigned to teams. This would mean many-to-many table relationship. I need help making the model for the migration.
users table:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<Photo> Photos { get; set; }
public virtual ICollection<UserTeam> UserTeams { get; set; }
}
team table:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public User User { get; set; }
public int UserId { get; set; }
public virtual ICollection<UserTeam> UserTeams{ get; set; }
}
UserTeam Table:
public class UserTeam
{
public User User { get; set; }
public int UserId{ get; set; }
public Team Team{ get; set; }
public int TeamId{ get; set; } //... I have this one for owner of the team
public string Title { get; set; }
}
DataContext:
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base (options) {}
public DbSet<Value> Values { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Photo> Photos { get; set; }
public DbSet<Team> Teams { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserTeam>()
.HasKey(ut => new { ut.UserId, ut.TeamId });
modelBuilder.Entity<UserTeam>()
.HasOne(ut => ut.User)
.WithMany(u => u.UserTeams)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTeam>()
.HasOne(ut => ut.Team)
.WithMany(t => t.UserTeams)
.HasForeignKey(ut => ut.TeamId);
}
}
From what I read declaring I ICollection on team class and user class would force a join table creating. However when I try to migrate, I get this message: Unable to determine the relationship represented by navigation property 'User.Teams' of type 'ICollection'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Any suggestions on how to correctly make join tables in sqlite would be helpful
looks like you are using EF Core 2.x. It is necessary to define the entity in the model. This means you will have to define relationship by overriding it in the OnModelCreating method.
I would further normalize your structure by creating another table to hold the reference for the user and the team the user belongs to and vice-versa. I would call this table UserTeam (for the lack of better naming)
So I will have something like:
public class User
{
//....omitted
public virtual ICollection<UserTeam> UserTeams { get; set; }
}
public class UserTeam
{
public int UserId{ get; set; }
public User User { get; set; }
public int TeamId{ get; set; }
public Team Team{ get; set; }
}
public class Team
{
//.... omitted
public virtual ICollection<UserTeam> UserTeams{ get; set; }
}
The UserTeam table will need to be configured so that EFCore can map it successfully. This is where we define the many-to-many relationship
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserTeam>()
.HasKey(ut => new { ut.UserId, ut.TeamId });
modelBuilder.Entity<UserTeam>()
.HasOne(ut => ut.User)
.WithMany(u => u.UserTeams)
.HasForeignKey(ut => ut.UserId);
modelBuilder.Entity<UserTeam>()
.HasOne(ut => ut.Team)
.WithMany(t => t.UserTeams)
.HasForeignKey(ut => ut.TeamId);
}

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

Rename foreign key column not working in fluent api

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

EF 4.1 Code First Mapping Problem

All my attempts to map the ID of SitePage to the database column ID (SitePages table, ID column of type bigint) has failed. It keeps looking for column SitePage_ID to map it.. Can you see where I am doing wrong? All related code is below;
public class Site : EntityBase<Int64>
{
public virtual string Url { get; set; }
public virtual IList<SitePage> Pages { get; set; }
}
public class SitePage : EntityBase<Int64>
{
public virtual Site Site { get; set; }
public virtual string Url { get; set; }
public virtual string Html { get; set; }
public virtual string Text { get; set; }
public virtual string Language { get; set; }
}
public abstract class EntityBase<T> : IComparable
{
public virtual T ID { get; set; }
protected EntityBase() : this(default(T))
{
}
protected EntityBase(T id)
{
this.ID = id;
if (this.ID == null)
this.ID = default(T);
}
}
public class SpellCrawlerContext : DbContext
{
public SpellCrawlerContext(){}
public DbSet<Site> Sites { get; set; }
public DbSet<SitePage> SitePages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Site>()
.HasMany(s => s.Pages)
.WithRequired(p => p.Site)
.Map(s => s.MapKey("SiteID"));
modelBuilder.Entity<SitePage>()
.HasKey(p => p.ID);
modelBuilder.Entity<SitePage>()
.Property(p => p.ID)
.HasColumnName("ID");
}
}
You are not doing anything wrong. The code you shown does everything correctly. You even don't need to explicitly define the name of ID in SitePage because it will be defined like ID anyway.
SitePage_ID is used by default naming convention for foreign keys created for independent associations. So do you have any other one-to-many relation between SitePage and any other entity? If you didn't map foreign key in dependent entity it will be defined as SitePage_ID by default.

Categories