Skillzore
Skillzore

Reputation: 851

Entity Framework Code First using virtual property internally, null reference when called

I am completely new to Entity Framework. I have created a model to watch services on remote machines. Currently it looks like this:

Note: I have done my best to use understandable generic names since this is from a corporate environment.

public class RemoteService
{
    [Key, Column(Order = 0)]
    public string Name { get; set; }

    [Key, Column(Order = 1)]
    public string MachineName { get; set; }

    [ForeignKey("MachineName")]
    public virtual RemoteEnvironment RunningAt { get; set; }
}

public class RemoteEnvironment
{
    [Key]
    public string MachineName { get; set; }

    public string Configuration { get; set; }

    public string EnvironmentNr { get; set; }

    public virtual ICollection<RemoteService> Services { get; set; }
}

With context class:

public class MyDBContext : DbContext
{
    public DbSet<RemoteEnvironment> RemoteEnvironments { get; set; }
    public DbSet<RemoteService> RemoteServices { get; set; }
}

This model works fine when creating the database and seeding it. The RemoteService class also contains this Version property, to get the version from the Octopus Deploy API.

private string _version = "N/A";
public string Version
{
    get
    {
        var item = GetItemFromOctopus(Name, RunningAt.EnvironmentNr);
        if (item != null)
        {
            _version = item.ReleaseVersion;
        }
        return _version;
    }
    set
    {
    }
}

This also works fine when creating and seeding the database. My issue appears when I try to grab data from the database. Something like this:

using (MyDBContext context = new MyDBContext())
{
    var serviceNames = new List<string>(); 
    foreach (RemoteService service in context.RemoteServices)
    {
        serviceNames.Add(service.Name); 
    }
}

While executing the above code I get a NullReferenceException, because RunningAt is null. NullReference

From debugging I have gathered that it is when I grab service from context.RemoteServices that it fetches all the properties. First Name, then MachineName, skips RunningAt (why? Lazy Loading?) and then tries to get Version, but since RunningAt is null, it can't.

I have tried defining the RunningAt get and set methods using

private RemoteEnvironment _runningAt 

but the issue stays the same, both RunningAt and _runningAt are null while fetching data from the database.

It seems that I somehow need to load the reference to the RemoteEnvironment from the database in the get of Version. But creating a context here seems a bit much.

What else can I do? I would really like to keep Version as is. If it can't be done, I will have to make a workaround. Checking version separately, actively getting and setting it instead.

Edit 1: I have now tried three different ways to "pre-load" the RemoteEnvironments.

First: "Pre-loading" by getting all RemoteEnvironments from database.

using (MyDBContext context = new MyDBContext())
{
    var serviceNames = new List<string>();
    List<M3BEnvironment> activateEnv = db.M3BEnvvironments.ToList(); //added line
    foreach (RemoteService service in context.RemoteServices)
    {
        serviceNames.Add(service.Name); 
    }
}

Still getting NullReferenceException since RunningAt is null.

Second: Removing the virtual keyword.

[ForeignKey("MachineName")]
public RemoteEnvironment RunningAt { get; set; } // virtual keyword removed

Still getting NullReferenceException since RunningAt is null.

Third: Using Include to force eagerly loading.

using (MyDBContext context = new MyDBContext())
{
    var serviceNames = new List<string>(); 
    var services = context.RemoteServices.Include(s => s.RunningAt).ToList();
    foreach (RemoteService service in services)
    {
        serviceNames.Add(service.Name); 
    }
}

Still getting NullReferenceException since RunningAt is null.

I have even tried all combinations of the above, including all three at the same time. Still getting NullReferenceException since RunningAt is null.

I also tried going the other way, through RemoteEnvironments and including Services:

using (MyDBContext context = new MyDBContext())
{
    var environments = context.RemoteEnvironments.Include(env => env.Services).ToList(); // NullRefereceException happens at this call
    var serviceNames = new List<string>(); 
    var services = context.RemoteServices.Include(s => s.RunningAt).ToList();
    foreach (RemoteService service in services)
    {
        serviceNames.Add(service.Name); 
    }
}

Still getting NullReferenceException since RunningAt is null. Only this time it happens while the environments call is trying to include them.

What I want to do does not seem possible. Using the referenced RunningAt property within the Version property, because it is never loaded from the database. Not even when I force it to load the RemoteEnvironments before the call to the RemoteService and subsequently the call to Version.

Upvotes: 0

Views: 910

Answers (1)

Skillzore
Skillzore

Reputation: 851

What I am trying to do does not seem possible. The core issue is having a navigation property and then referencing it in another property's get or set method. This does not seem to work. I have tried turning off lazy loading, forcing loading with Include and also pre-loading the DbSet which the navigation property points to. In each and every case the navigation property reference, in my case RunningAt, has been null inside the other property's get/set method.

The Entity Framework does not seem to support referencing a navigation property inside another property. If anyone knows otherwise please tell me how. In the mean time, this will be the accepted answer.

Upvotes: 1

Related Questions