0

I am relatively new to .NET and have recently 'upgraded' my application (started a new project and copied the files over) from EntityFramework 6.4.4 to EntityFramework core 7.0.7, and similarly .NET 4.8 to .NET 7.0.

For the application, there are two classes I have defined; Accounts and Sites. Each Site belongs to an Account and each Account owns a list of Sites. The Account class has an attribute of type ICollection<Site> to contain all of the Sites belonging to a particular Account. Similarly, the Site class has an Account attribute for the Account that owns that Site.

public class Account
{
    [Key][StringLength(50)] public string AccountId { get; set; }
    public ICollection<Site> Sites { get; set; }
}

public class Site
{
    [Key] public int SiteId { get; set; }
    public string AccountId { get; set; } = "";
    public Account Account { get; set; } = new Account();
}

Either than the change in frameworks, the only difference between the new and old applications is that the Account and AccountId attributes in the Site class are explicitly declared in the new app...

Old app:

public string Account { get; set; }
public string AccountId { get; set; }

New app:

public string Account { get; set; } = new Account();
public string AccountId { get; set; } = "";

Now, I am trying to write a function that returns all of the Sites for a given Account, or returns all of the Sites in the DB if no Account is specified, while including the reference to the owner Account. Note that "Sites" is a DbSet<Site> containing all of the Sites in the DB.

public List<Site> GetSites(string? AccountName = null){
    if(AccountName == null)
        return Sites.Include(x => x.Account).ToList(); //problem here
    else{
        var account = GetAccount(AccountName);
        return Sites.Where(x => x.AccountId == account.AccountId).Include(x => x.Account).ToList();  
    }
}

There is no problem when specifying the AccountName, however, when GetSites() is run to return all of the Sites in the DB, the updated application reports: "System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values." The exception appears to be thrown at the third line: Sites.Include(x => x.Account).ToList() Again, everything works as expected on the old application, I am only running into problems with the updated frameworks.

I realize that I can probably write a lengthy for loop to simulate the Include function by looping through all of the Sites and properly setting the Accounts, however, I am trying to see if there is a better way through Linq commands.

Any ideas or potential fixes would be much appreciated. I would be happy to supply more information if required.

Thanks!

I have tried replacing the troublesome Include() line with the following to no avail...

  • Sites.Include("Account").ToList(); //Same error as before
  • Sites.Where(x => x.Account != null).Include(x => x.Account).ToList(); //Same error
  • (from s in Sites join a in Accounts on s.Account equals a select s).ToList(); //sketchy

The hardcoded (sketchy) query did not result in an exception or error and appeared to work but my mentor advised that it would bypass the entity framework and would not update the object model.

donny_V
  • 1
  • 2
  • 1
    If your Account owns Sites I would do Accounts.Include(s=>s.Sites) as it looks like you can't have an Account without a Site? – AliK Aug 09 '23 at 23:59
  • Do not initialize reference navigation properties like `= new Account()`, this is the difference in your model and the cause of the problem, not "Include` or other stuff. Just don't "overcome" NRT or similar warnings by breaking functionality. – Ivan Stoev Aug 10 '23 at 00:27
  • Also note that in NRT enabled project `string` or `Account` means **required** property, i.e.. does not allow null, that's why you are getting runtime exception. While in old projects these were **optional** by default, breaking change introduced by the NRT. – Ivan Stoev Aug 10 '23 at 00:42
  • @IvanStoev , thanks for the info. Turned out we had a property in Account that was not made nullable but had instances of null in the DB. Regarding your comment about ` = new Account()`, what is best practice for constructing an object that will never be null? It makes sense why we should not create a new default account every-time because that immediately becomes garbage once we reference the proper account, however, I am unsure how to work around this. – donny_V Aug 11 '23 at 18:50
  • 1
    It's not about GC, all EFC loading related data methods (eager, explicit and lazy) are broken (won't work) if you initialize reference nav props with `new`. What about NRT and how to init something which never be null, the whole concept does not really apply to ref nav props because they **will* be null if you don't ask EF to load them. The whole thing is highly opinionated, here https://learn.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types you can see EFC team vision, I personally just `#nullable disable` code for entity model classes and just use them the good old way. – Ivan Stoev Aug 12 '23 at 01:18

0 Answers0