У меня была похожая проблема с многоуровневой иерархией классов, где использование .OfType<>()
приводило к "преждевременному" (на мой взгляд) отключению базы данных для получения всех данных, поэтому может сделать фильтрацию в памяти, что нежелательно!
Это иллюстрирует мою иерархию:
public abstract class BaseSetting {}
public abstract class AccountSetting : BaseSetting {}
public abstract class UserSetting : BaseSetting {}
public class AccountSettingA : AccountSetting {}
public class AccountSettingB : AccountSetting {}
public class UserSettingA : UserSetting {}
public class UserSettingB : UserSetting {}
И это настройка для DbContext
:
public class DataContext : DbContext
{
public virtual DbSet<BaseSetting> Settings { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<BaseSetting>(e =>
{
e.ToTable("Settings");
e.HasDiscriminator<string>("Type");
});
}
}
Тогда я бы попытался получить все настройки для одной учетной записи, например:
AccountSetting[] settings = context.Settings
.OfType<AccountSetting>()
.Where(s => s.Account.Id == accountId)
.ToArray();
В результате SQL-запрос выглядит примерно так:
SELECT *
FROM [Settings] AS [s0]
WHERE [s0].[Type] IN (N'AccountSettingA',N'AccountSettingB',N'UserSettingA',N'UserSettingB')
как раз перед тем, как выбрасывает NullReferenceException
в бит .Where(s => s.Account.Id == accountId)
запроса, потому что Account
является нулем. Вероятно, это можно было бы «исправить», добавив .Include(...)
к запросу, чтобы тоже пропустить Account
, но это только увеличит объем данных, которые мы получаем из базы данных. (Следует отметить, что если вы конфигурируете контекст для выдачи ошибок при попытке выполнить оценку на клиенте в соответствии с комментарием @ PanagiotisKanavos к исходному вопросу, то вместо этого вы получите QueryClientEvaluationWarning
).
Решение (по крайней мере для меня) было добавить это к методу OnModelCreating
в моем DbContext
:
typeof(BaseSetting).Assembly.GetTypes()
.Where(t => t != typeof(BaseSetting) && typeof(BaseSetting).IsAssignableFrom(t))
.Each(s => builder.Entity(s).HasBaseType(s.BaseType));
Это пройдёт через все мои разные классы настроек (которые наследуются от BaseSetting
) и сообщит Entity Framework, что их базовый тип - это их Type.BaseType
. Я бы подумал, что EF может решить это самостоятельно, но после этого я получаю SQL вроде этого (и без QueryClientEvaluationWarning
исключений!):
SELECT *
FROM [Settings] as [a]
INNER JOIN [Accounts] AS [a.Account] ON [a].[AccountId] = [a.Account].[Id]
WHERE ([a].[Type] IN (N'AccountSettingA',N'AccountSettingB',N'UserSettingA',N'UserSettingB')
AND ([a.Account].[Id] = @__accountId)
Который, очевидно, возвращает только настройки учетной записи для интересующей меня учетной записи, а не все настройки учетной записи и все пользовательские настройки как это было раньше.