Naomi
Naomi

Reputation: 718

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.

Upvotes: 0

Views: 672

Answers (1)

Slauma
Slauma

Reputation: 177133

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.

Upvotes: 1

Related Questions