Having an IList<Guid>
, I wish to locate all matches from the context and then include a number of related tables, where outdated data are not included.
Because of the size of the data, I try to use EF Plus IncludeFilter, to avoid loading all to memory and perform the filtering there.
The problem occurs when I call ToListAsync()
on the query. IncludeFilter (as far as I can see) then throws a System.MissingMethodException : Cannot create an instance of an interface
exception.
The project is done in .NET Core 2.2, and I using Z.EntityFramework.Plus.EFCore 2.0.7
Sample project
This sample recreates the issue: https://dotnetfiddle.net/MYukHp
Data structure
The database structure centers around the Facts table which contains an immutable Guid, identifing each entry. Entries in all other tables link to this guid to bind together to a single entry. The other tables contain a DateTime ValidTo
to track changes. No entries are ever updated or deleted. Instead, on change a new entry is made with ValidTo = DateTime.MaxValue
, and the entry being updated has it's ValidTo
set to DateTime.Now
.
This ensures that all changes are preserved historically.
Over time, the vast majority of data will be historical, so it's crucial that we can filter this out in the SQL query.
The data structure for the Fact-table is like this:
public class FactModel
{
public Guid FactId { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
// Navigation properties
public IEnumerable<PersonModel> Persons { get; set; }
// Repeat for all other tables
}
All other tables inherits from a ModelBase, linking them to the Fact table.
public class ModelBase
{
public Guid FactId { get; set; } // Link to the Fact
public FactModel Fact { get; set; } // Navigation property
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; } // ValidTo == DateTime.MaxValue -> active record
}
Example tables for Person and Patient
public class PersonModel : ModelBase
{
public Guid PersonId { get; set; } // Key - A new is created on every update
public string FirstName { get; set; } // data
public string LastName { get; set; } // data
}
public class PatientModel : ModelBase
{
public Guid PatientId { get; set; } // Key - A new is created on every update
public Guid ActiveCompanyId { get; set; } // Data
public int HealthInsuranceGroup { get; set; } // Data
public PatientStatusType Status { get; set; } // Data
}
Changing the parameter to IQueryable produces a new error:
System.InvalidCastException : Unable to cast object of type System.Guid' to type 'System.String'
Call sequense
The call sequence is rather complex, but simplified we start by declaring the call parameter IQueryable<FactModel> facts
. This is filtered by only adding Patients from the company the user is logged into. Then the search term is applied. Finally, the parameter is transformed into a list containing only the need guids, before calling AssignDataToPatientByFactId
.
// Performing a search for an Address
IQueryable<FactModel> facts = null;
facts = _context.Facts.AsNoTracking().Where(p => p.Patients.Any(a => a.ActiveCompanyId == _identificationHandler.Identification.CompanyId));
facts = facts.AsNoTracking().Where(p => p.Addresses.Where(z => z.ValidTo == DateTime.MaxValue).Any(q => q.Street.Any() && q.Street.StartsWith(searchDTO.SearchString)));
return await AssignDataToPatient(facts.Select(x => x.FactId).ToList()), cancel);
public async Task<List<FactModel>> AssignDataToPatientByFactId(IList<Guid> factIds, CancellationToken cancel)
{
return await _context.Facts.Where(x => factIds.Contains(x.FactId))
.IncludeFilter(x => x.Patients.Where(c => c.ValidTo == DateTime.MaxValue))
.IncludeFilter(x => x.Persons.Where(c => c.ValidTo == DateTime.MaxValue))
.IncludeFilter(x => x.Communications.Where(c => c.ValidTo == DateTime.MaxValue))
.IncludeFilter(x => x.Addresses.Where(c => c.ValidTo == DateTime.MaxValue))
.ToListAsync(cancel);
}
So AssignDataToPatientByFactId
takes a list of guids, finds all matching in the Facts-table and then adds the entries from the other tables where the ValidTo
timestamp is Max. So all other entries should not be included.
Separating the code into several statements reveal that IncludeFilter seems to be working, but calling ToListAsync produces the error.
Disclaimer: I'm the owner of the project Entity Framework Plus
The version v2.0.8 has been released fixing this issue.
The issue was caused because the class Fact
was using IEnumerable<T>
properties which were not yet supported by our library.
The Fiddle is now working as expected: https://dotnetfiddle.net/MYukHp