简体   繁体   中英

EF5 thinks my foreign key is null… when it isn't

I have an existing database that I'm using code first with. I did some cleanup on the tables in my SQL database to make sure I have foreign keys where needed. I have one table called Inventory_Base that has a nullable foreign key called ClientID that points to a table called Entity_Company. Inventory_Base maps to my entity object called ComputerEntity, and Entity_Company maps to my entity object called CompanyEntity. I believe I have the navigation props set correctly, but please let me know if I goofed. The issue I am having is that the CompanyEntity navigation property is never loaded. I tried removing the navigation properties on both so I could look at the value of the ClientID in the ComputerEntity object, but it is never loaded even though that column exists in the database! I suspect that EF conventions are seeing the word 'ID' and doing some black magic with it.

I have my ComputerEntity defined as such, and I really really want the CompanyEntity to load:

[Table("Inventory_Base")]
public class ComputerEntity
{
    // primary key
    [Key]
    public int AssetID { get; set; }

    // foreign keys
    [ForeignKey("CompanyEntity")]
    public int? ClientID { get; set; }

    // navigation props
    [ForeignKey("ClientID")]
    public virtual CompanyEntity CompanyEntity { get; set; }

    // these props are fine without custom mapping
    public string Hostname { get; set; }
    public string ServiceTag { get; set; }
}

This is my fabled CompanyEntity that never loads:

[Table("Entity_Company")]
public class CompanyEntity
{
    protected CompanyEntity() {}

    // primary key
    [Key]
    public int ClientID { get; set; }

    // foreign key

    // nav props
    public virtual ICollection<ComputerEntity> ComputerEntities { get; set; }

    // regular props

    // custom mappings
    [Column("CompanyName", TypeName = "nvarchar(MAX)")]
    public string Name { get; set; }

    [Column("FQDN", TypeName = "nvarchar(MAX)")]
    public string Domain { get; set; }
}

I am trying to find a computer where its hostname property matches an input, and the computer belongs to a particular company. I have a DbSet that I am querying and assuming that since navigation properties are present it can reach out to the associated CompanyEntity object's properties. So far I have assumed wrong and for the life of me can't see what is causing the problem. Here is how I am trying to fetch a computer:

    public ComputerEntity FindComputerByHostname(string hostname, string client)
    {
        var computer = DbSet.Where(x => x.Hostname == hostname && x.CompanyEntity.Name == client).FirstOrDefault(); // <-- always null!
        var test = DbSet.Where(x.CompanyEntity.Name == client).ToList() // <-- never finds anything, which is why I suspect a non-working relationship
        return computer ;
    }

Another bit of strangeness is that in my ComputerEntity I have to make the ClientID nullable with the int? otherwise I get an exception that ClientID cannot be null. When I looked at the database, the column is nullable but there are no null values in it. Weird. Is the fact that is an int? breaking the relationship somehow?

UPDATE
So I reverse engineered using the EF power tool to try and get more detail. It gave me all my ugly table names as entities, which is fine. CompanyEntity is now Entity_Company, and it has the ICollection's for all the things that refer to it. ComputerEntity is now Inventory_Base, and I have the same issue where it is never loaded. To add to the riddle, CompanyEntity (now Entity_Company)'s other collections DO navigate as expected! I checked out the mapping files that were autogenerated, and the busted collections all have .IsRequired() for every property. The collections that do work normally don't have this. Also, the busted collections all have their ClientID is int? and not a regular non-nullable int. Although I don't know why, I think this is a problem. Focusing on the problem at hand between companies and their respective computers, the ComputerEntity (now Inventory_Base) has the dreaded int? in the autogenerated mapping file. I checked the SQL database with a SELECT * FROM Inventory_Base WHERE ClientID IS NULL, and I get nothing. I try to modify the table to make the ClientID column non-nullable, and SQL complains that it can't do it because the table can't be recreated. Normally I would expect this is there was a null value in there... but there isn't. I can modify my other tables that correspond to entity collections that play nicely already without issue. Maybe the issue is a corrupted SQL table? I didn't think that was possible, but if someone knows a DBCC command to check that would be great. To keep my solution consistent with this question, I reverted my changes and am back to using my hand coded ComputerEntity and CompanyEntity as defined in this question.

Inside of ComputerEntity I changed:

[ForeignKey("CompanyEntity")]
public int? ClientID {get;set;}

to:

[ForeignKey("CompanyEntity")]
public int ClientID {get;set;}

Now when I test I get an an exception of:
The navigation property 'ClientID' is not a declared property on type 'ComputerEntity'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.

Stack trace: at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.ConfigureAssociations(EdmEntityType entityType, EdmModel model) at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.Configure(EdmEntityType entityType, EdmModel model) at System.Data.Entity.ModelConfiguration.Configuration.ModelConfiguration.ConfigureEntities(EdmModel model) at System.Data.Entity.ModelConfiguration.Configuration.ModelConfiguration.Configure(EdmModel model) at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo) at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection) at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext) at System.Data.Entity.Internal.RetryLazy 2.GetValue(TInput input) at System.Data.Entity.Internal.LazyInternalContext.InitializeContext() at System.Data.Entity.Internal.InternalContext.Initialize() at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType) at System.Data.Entity.Internal.Linq.InternalSet 2.GetValue(TInput input) at System.Data.Entity.Internal.LazyInternalContext.InitializeContext() at System.Data.Entity.Internal.InternalContext.Initialize() at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType) at System.Data.Entity.Internal.Linq.InternalSet 1.Initialize() at System.Data.Entity.Internal.Linq.InternalSet 1.get_InternalContext() at System.Data.Entity.Infrastructure.DbQuery 1.System.Linq.IQueryable.get_Provider() at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable 1 source, Expression 1 predicate) at Reporting.Data.InventoryRepository.FindComputerByHostname(String hostname, String client) in c:\\Projects\\Reporting\\Data\\Reporting.Data\\InventoryRepository.cs:line 22 at Reporting.Services.InventoryService.GetComputerDetails(String hostname, String client) in c:\\Projects\\Reporting\\Business\\Reporting.Services\\InventoryService.cs:line 29 at Reporting.Services.Tests.InventoryTests.GetComputerDetailsTest() in c:\\Projects\\Reporting\\Tests\\Reporting.Services.Tests\\InventoryTests.cs:line 39

Holy crap, the answer to this is stupid. For my unit testing, in my app.config I was pointing to my cloned database. I never updated the FK to have no nulls in the test database. After changing my app.config to point to my production DB, of course it found the proper relationship between CompanyEntity and ComputerEntity because ClientID was no longer null and it fetches data correctly.

If I set the foreign key ClientID to int? it also magically works now too. I suspect this is because there are no more null keys, even though I say it's nullable. I don't plan on reintroducing null values into the table where ComputerEntity lives, but as I work through this project I'm pretty sure I'll find more situations where that is the case. If someone can give me a solid answer on how nullable foreign keys work with automatic EF mapping, I'll gladly accept that answer instead. I'm trying to stay out of the FluentAPI and go straight data annotations. Maybe the answer is to remove whatever convention that is causing me heartburn.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM