EF Code First created two properties for one field - c#

In database, I have two tables - Show and Language. Table Show besides other things has Foreign Key to Language. Its one-to-many relationship (Show has one language).
When I run Code First from ADO.NET Entity Data Model creates two properties for language field:
public class ShowModel
{
...
public LanguageModel Language1 {get; set;}
public string Language {get; set;}
...
}
public class LanguageModel
{
...
public string Language {get; set;}
...
}
When I debug on sample data, value of Language field from DB is inserted into string property.
Question is - Why it generates those properties? Is it because I can add Language object into the LanguageModel one, but from DB, it always write to string one?
EDIT
modelBuilder.Entity<LanguageModel>()
.HasMany(e => e.Shows)
.WithOptional(e => e.Language1)
.HasForeignKey(e => e.Language);
modelBuilder.Entity<ShowModel>()
.Property(e => e.Language)
.IsUnicode(false);

If you are hinting at the fact that the field 'Language' is the foreign key to the Language table for the 'Laguage1' navigation property, then I can give you a couple of pointers:
The key in the Language table is your Language property, but might not be marked as the key. EF doesn't figure this out by itself unless you name the field Id or something, or mark it with a [Key] attribute.
EF doesn't see the 'Language' field as the Foreign key, but as another scalar property, this will result in having 2 Language fields on the Show property. This can be solved by telling the ModelBuilder to use the proper navigation property.
The syntax to solve no. 2 is similar to this:
modelBuilder.Entity<ShowModel>()
.HasRequired(t => t.Language1)
.WithMany(t => t.Shows)
.HasForeignKey(d => d.Language);

Related

Entity Framework Core 2.1.14 DB First Missing Foreign Key on non-primary candidate key of a misconfigured data type

Using Entity Framework Core 2.1.14 and MySQL database first:
The tl;dr
...Can't change the database.
I have two broken tables that have a "foreign key" relationship between them that is implicit, on a false candidate key rather than the primary, and the "foreign key" field is the wrong data type. There is no formal foreign key in the database between these two tables. I'm putting "foreign key" in scare quotes because it is not a foreign key but is expected to behave like one.
The referenced Parent candidate key is of type int.
The referencing Child "foreign key" field is of type bigint.
The details:
I am needing to write data into a database and directly match an implicit table relationship between tables which do not maintain a formal foreign key, their relationship exists on a false candidate(it is supposed to be unique but since there are no data integrity controls on this table, it inevitably received duplicates and accepted them) rather than the primary, and the data types got crossed up in the referencing table. I need Entity Framework to write both in the same context.SaveChanges(); operation and manage this relationship properly.
I've already extended the DbContext, I've set up ValueConverters, set up a PrincipalKey, and set up my Navigation Properties. Whenever I attempt a SaveChanges(), I get a mismatch on the data types when I use the Converted CandidateKeyField and the BorkedForeignKey for this relationship.
When I use the PrincipalKey and the BorkedForeignKey I get an error from the context that PrincipalKey is not mapped whenever I go to add a new BorkedParentEntity with BorkedChildEntity entries in its Nav collection.
Here is a representation of what I have done:
public partial class BorkedParentEntity
{
//PrimaryKeyField is generated in the generated partial to this class; including for context
//public long PrimaryKeyField{get;set;}
public ICollection<BorkedChildEntity> ChildrenNavigation {get;set;}
//CandidateKeyField is generated in the generated partial to this class; including for context
//public int CandidateKeyField { get; set; }
//An attempt to bypass the type mismatch since ValueConverter is not being honored
public long PrincipalKey { get { return (long)PrimaryKeyField; } }
}
public partial class BorkedChildEntity
{
//BorkedForeignKeyField is generated in the other partial to this class; including for context
//public long BorkedForeignKeyField {get;set;}
public BorkedParentEntity ParentNavigation {get;set;}
}
public class myDatabaseContextNavigable : myDatabaseContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BorkedParentEntity>(entity =>
{
entity.Property(e=>e.Id).ValueGeneratedOnAdd();
entity.Ignore(e=>e.PrincipalKey);
entity.Property(e => e.CandidateKeyField).HasConversion<long>();
entity.HasMany(r => r.ChildrenNavigation)
.WithOne(s => s.ParentNavigation)
//.HasPrincipalKey(s => s.PrincipalKey) //=>Added this PrincipalKey because my conversion was not being honored at runtime.
//.HasPrincipalKey(s =>(long)s.CandidateKeyField) //=>Added this PrincipalKey to attempt to resolve the mismatch or at least change the error I was getting
.HasForeignKey(r => r.BorkedForeignKeyField);
});
modelBuilder.Entity<BorkedChildEntity>(entity =>
{
entity.Property(e=>e.Id).ValueGeneratedOnAdd();
});
}
Here are the outputs I receive for each of the attempts:
Note that the derived PrincipalKey property was only included in the partial entity class for attempts which made a direct reference to it.
Value Converter on the CandidateKeyField and a ForeignKey
entity.Property(e => e.CandidateKeyField).HasConversion<long>();
entity.HasMany(r => r.ChildrenNavigation)
.WithOne(s => s.ParentNavigation)
.HasPrincipalKey(s => s.CandidateKeyField)
.HasForeignKey(r => r.BorkedForeignKeyField);
...
context.BorkedParents.Add(_new_record);//=>throws InvalidOperationException
context.SaveChanges();//=>never reached
System.InvalidOperationException: 'The types of the properties specified for the foreign key {'BorkedForeignKeyField'} on entity type 'BorkedChildEntity' do not match the types of the properties in the principal key {'CandidateKeyField'} on entity type 'BorkedParentEntity'.'
Manual Cast of the CandidateKeyField
entity.HasMany(r => r.ChildrenNavigation)
.WithOne(s => s.ParentNavigation)
.HasPrincipalKey(s => (long)s.CandidateKeyField)
.HasForeignKey(r => r.BorkedForeignKeyField);
...
context.BorkedParents.Add(_new_record);//=>throws InvalidOperationException
context.SaveChanges();//=>never reached
System.InvalidOperationException: 'The types of the properties specified for the foreign key {'BorkedForeignKeyField'} on entity type 'BorkedChildEntity' do not match the types of the properties in the principal key {'CandidateKeyField'} on entity type 'BorkedParentEntity'.'
Addition of PrincipalKey field (without ignore of the property)
entity.HasMany(r => r.ChildrenNavigation)
.WithOne(s => s.ParentNavigation)
.HasPrincipalKey(s => s.PrincipalKey)
.HasForeignKey(r => r.BorkedForeignKeyField);
...
context.BorkedParents.Add(_new_record); //=>succeeds
context.SaveChanges();//=>throws DbUpdateException
Message: Unknown column 'PrincipalKey' in 'field list'
PrincipalKey field (with ignore of the property)
entity.Ignore(e => e.PrincipalKey);
entity.HasMany(r => r.ChildrenNavigation)
.WithOne(s => s.ParentNavigation)
.HasPrincipalKey(s => s.PrincipalKey)
.HasForeignKey(r => r.BorkedForeignKeyField);
...
context.BorkedParents.Add(_new_record); //=>succeeds
context.SaveChanges();//=>throws DbUpdateException
Message: Unknown column 'PrincipalKey' in 'field list'
No PrincipalKey, no ValueConverter
entity.HasMany(r => r.ChildrenNavigation)
.WithOne(s => s.ParentNavigation)
.HasForeignKey(r => r.BorkedForeignKeyField);
...
context.BorkedParents.Add(_new_record); //=>succeeds
context.SaveChanges();//=>succeeds with bad data
The BorkedForeignKeyField on the BorkedChildEntity table row has the value of the BorkedParent's Primary Key rather than the CandidateKey it is supposed to have.
I'm fresh out of ideas. Can anyone help match the BorkedChildEntitys bigint BorkedForeignKeyField to the BorkedParentEntitys int CandidateKeyField in a way that puts the CandidateKeyField of BorkedParentEntity into BorkedChildEntity's BorkedForeignKeyField in the database?
The solution to this compounded problem is an "all of the above approach"++.
The 3 problems have to be treated interdependently.
First, eliminate the PrincipalKey property on the BorkedParentEntity. It is unnecessary and counterproductive.
Implicit foreign key on the Candidate Key is itself resolved with 2 steps:
a) Manually add the appropriate NavigationProperties to partials of the Entity classes. In my case, that sends ChildrenNavigation to the BorkedParentEntity and ParentNavigation to the BorkedChildEntity
public partial class BorkedParentEntity
{
public ICollection<BorkedChildEntity> ChildrenNavigation { get;set; }
}
public partial class BorkedChildEntity
{
public BorkedParentEntity ParentNavigation {get;set;}
}
b) manually define the relationship between these tables through these navigation properties.
public class myDatabaseContextNavigable: myDatabaseContext
{
public override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BorkedParentEntity>(entity =>
{
entity.HasMany(s => s.ChildrenNavigation)
.WithOne(r => r.ParentNavigation)
.HasPrincipalKey(r => r.CandidateKeyField)
.HasForeignKey(s => s.BorkedForeignKeyField);
});
}
}
This addresses the missing relationship and the accommodation to key off of the Candidate.
Fix the column type mismatch
No matter what I did(I think I attempted every available permutation of conversion) I could not get Entity Framework to honor my converted type with the mapped property types being misaligned. My solution to the problem is to add a duplicate implementation of the CandidateKeyField in that Entity's partial class with instructions to anyone encountering a build error on this field to go delete the corresponding generated property in the scaffolded Entity class. The manually entered property is entered with the relationship's expected data type.
//so `BorkedParentEntity` becomes:
public partial class BorkedParentEntity
{
public ICollection<BorkedChildEntity> ChildrenNavigation { get;set; }
//This is my note. There are many like it, but this one is mine
//If you have a build error on a duplicated property, delete the other one or you will break stuff
public long CandidateKeyField { get; set; }
}
and delete the property of the exact same name from the generated entity type.
The last remaining step is to instruct the database that this runtime long is actually mapped to a database int
Inside OnModelCreating(ModelBuilder modelBuilder) when the BorkedParentEntity is being configured, now add
entity.Property(e => e.CandidateKeyField).HasConversion<int>();
The entities will now persist as I expected.

Many-to-Many EF relationship with a custom primary key on one side

I feel silly asking this question, but I never had to do something like this and I am stuck.
I have a pleasure working on a legacy design that has a few "gotchas" here and there. One of them is an improperly defined many-to-many relationship between two tables. It goes like this:
Table A
Id, Number
Table B
Id
Table AToB
NumberOfA, IdOfB
So to join it with SQL one would have to do something like this:
SELECT A.*, B.*
FROM A
JOIN AToB ON AToB.NUmberOfA = A.Number
JOIN B ON AToB.IdOfB = B.Id
Yes, I know. This is a bad design. I should change it as soon as possible. And I will. But for the time being, I need a quick-and-dirty solution for the EF-based prototype. This obviously does not work
public class A
{
public long Id {get; set;}
public string Name {get; set;}
}
public class B
{
public long Id {get; set;}
}
...
internal class BConfiguration : EntityTypeConfiguration<B>
{
this.ToTable("B")
.HasKey(t => t.Id);
...
internal class AConfiguration : EntityTypeConfiguration<A>
{
this.ToTable("A")
.HasKey(t => t.Id);
this.HasMany<B>(t => t.EntitiesOfB)
.WithMany(t => t.EntitiesOfA)
.Map(m =>
{
m.MapLeftKey("IdOfB");
m.MapRightKey("NumberOfA");
m.ToTable("AToB");
});
since it expects AToB's mappings to refer to A and B's primary key columns on both ends, while I expect it to use AToB.NumberOfA=A.Number instead, but only in this particular mapping. All other mappings using table A in their relationship should be still able to use it's correctly defined Id key column.
Does a EF6 FluentAPI solution for such case exist?

WillCascadeOnDelete without base entity knowing of foreign entity

I have an entity in my core application:
public class Contact : BaseEntity
{
//Some properties
}
I then have an entity in a plugin (not part of the main DLL) like so:
public class AdditionalContactData
{
public string SomePropertyThatIsntOnTheMainContact { get; set; }
public Contact Contact { get; set;}
}
After binding the models etc via EF, this creates the tables with the foreign keys as I would expect. The issue I have is that I want to be able to delete a Contact entity and it cascade down to remove AdditionalContactData. This wouldn't be an issue if Contact could know about AdditionalContactData i.e:
HasRequired(m => m.Contact)
.WithOptional(m => m.AdditionalContactData)
.WillCascadeOnDelete();
This would work (and would be what I'd do in normal circumstances)
How can I achieve the same thing BUT without Contact knowing about AdditionalContactData. Is this possible?
I'm hoping I've been clear enough but please let me know if more information is required.
Assuming, that this is code from AdditionalContactData configuration, just remove optional property expression from WithOptional:
HasRequired(m => m.Contact)
.WithOptional()
.WillCascadeOnDelete();
You can create an unidirectional relationship:
HasRequired(m => m.Contact)
.WithOptional()
.WillCascadeOnDelete();
Check this link for more info about this.

EF Code First Declaring Entity relationships 2 fields to the same collection

I am using Entity Framework with Code First. I have a table in my database that stores relationships between users. The table structure looks a lot like this:
RequestID
UserFromID
UserToID
Both UserFromID and UserToID are foreign keys to my User table.
In my User entity I have a virtual property called Relationships setup. I want this property to be a collection of of all RelationshipRequests from the table listed above where the current users UserID is either the UserFromID OR the UserToID.
These are my bindings in the context:
modelBuilder.Entity<UserProfile>()
.HasMany(e => e.Relationships).WithRequired(e => e.UserFrom)
.HasForeignKey(e => e.UserFromID).WillCascadeOnDelete(false);
modelBuilder.Entity<UserProfile>()
.HasMany(e => e.Relationships).WithRequired(e => e.UserTo)
.HasForeignKey(e => e.UserToID).WillCascadeOnDelete(true);
But once the relationships are retrieved for a user, the only relationships in the collection are the one's where the users ID is the UserToID. I have tried swapping them around in which case the collection only contains the relationships where the users ID is the UserFromID. It seems as though the second binding is overriding the first instead of appending to it like I expected. I'm obviously doing this wrong. My question, is there another way to do this binding so that both keys are bound the way I want, or is this something I will have to implement another way?
Thanks!
the problem is that you use twice the same property, and of course your second binding overrides the first. You have to create 2 navigation properties on your user model, one for each foreign key
modelBuilder.Entity<UserProfile>()
.HasMany(e => e.FromRelationships).WithRequired(e => e.UserFrom)
.HasForeignKey(e => e.UserFromID).WillCascadeOnDelete(false);
modelBuilder.Entity<UserProfile>()
.HasMany(e => e.ToRelationships).WithRequired(e => e.UserTo)
.HasForeignKey(e => e.UserToID).WillCascadeOnDelete(true);
And on the model you will have something like this
public virtual List<Relationships> FromRelationships { get; set; }
public virtual List<Relationships> ToRelationships { get; set; }

How to model look-up tables with EF DbContext

I am using Entity Framwork DbContexts with a legacy database. I have 2 different properties of an entity that both need to reference the same lookup table, like so:
public class Address
{
public virtual AddressType AnAddressType {get; set;}
public virtual AddressType AnotherAddressType {get; set;}
}
// now here's a LINQ query that just flat won't work:
from a in Addresses select a;
The exception indicates that we tried to include a completely fictional field in the select list -- the field doesn't appear in my POCO or the table -- it looks like it was expected by convention, it's named AnAddressType_AddressType or something close to that
The AddressType entity does not have a corresponding navigation property on it. I cannot seem to get this to work. When I attempt to select data with my LINQ query, I get runtime errors.
Edit
I have other relations that are working (this code is generated from the "stock" DbContext generator). The thing about this one relation that is different is that the lookup table does not have a navigation property back to the main table (the lookup table is used all over the place, so I don't really want to add nav properties from it to everything that uses it). EF seems to be having a problem with that. It's probably a configuration vs. convention thing, and I have inadvertently tripped on some kind of convention problem.
You have a foreign key column name in your legacy database which doesn't follow the EF conventions, for example: The foreign key column in your Addresses table to the AddressTypes table for the AnAddressType relationship has the name MyCrazyAnAddressTypeNumberCodeKeyId.
But EF will assume by convention that the FK column name is: [Nav.property]_[KeyColumn]. For example: If AddressType has a PK with name AddressTypeId EF will assume the FK column has the name AnAddressType_AddressTypeId. Because this doesn't match you get the exception you describe. You must specify the FK column name explicitely to fix this problem:
modelBuilder.Entity<Address>()
.HasRequired(a => a.AnAddressType)
.WithMany()
.Map(c => c.MapKey("MyCrazyAnAddressTypeNumberCodeKeyId"))
.WillCascadeOnDelete(false);
(code snippet partially stolen from Ladislav's answer for convenience)
That's my hypothesis.
Edit
Alternatively you can introduce a foreign key property into your model and tell EF by data annotations that this property is a FK to the corresponding navigation property:
public class Address
{
[ForeignKey("AnAddressType")]
public int MyCrazyAnAddressTypeNumberCodeKeyId {get; set;}
public virtual AddressType AnAddressType {get; set;}
}
If you don't want navigation properties on both sides of the relation you should help EF with fluent mapping so that model is represented correctly:
public class YourContext : DbContext
{
public DbSet<Address> Addresses { get; set; }
public DbSet<AddressType> AddresTypes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Address>()
.HasRequired(a => a.AnAddressType)
.WithMany()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Address>()
.HasRequired(a => a.AnotherAddressType)
.WithMany()
.WillCascadeOnDelete(false);
}
}

Categories

Resources