我通过反射映射的泛型类对自动映射模型进行了几个复杂的查询。
如果需要,我会在问题下方添加实体配置。
问题
至于问题,我有一个通过多次传递构建的查询,一个传递在基本存储库中,另一个在实体特定存储库中。
但完成生成的查询将是这样的:
var x = await _uow.Knowledge.Query.Include(i => i.Translates)
.ThenInclude(i => i.Language)
.Select(s => new
{
Item = s,
TranslateNative = s.Translates != null //.Any()
? s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.Native.Iso6391)
: null,
TranslateEnglish = s.Translates != null //.Any()
? s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.English.Iso6391)
: null,
TranslateSystem = s.Translates != null //.Any()
? s.Translates.FirstOrDefault(w => w.LanguageId == SystemLanguageId)
: null,
TranslateAnyNativePriority = s.Translates != null //.Any()
? (
s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.Native.Iso6391)
?? (SystemLanguageId.HasValue
? s.Translates.FirstOrDefault(w => w.Language.Id == SystemLanguageId.Value)
: s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.English.Iso6391))
?? s.Translates.FirstOrDefault()
)
: null,
TranslateAnySystemPriority = s.Translates != null //.Any()
? (
(SystemLanguageId.HasValue
? s.Translates.FirstOrDefault(w => w.Language.Id == SystemLanguageId.Value)
: s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.Native.Iso6391))
?? s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.English.Iso6391)
?? s.Translates.FirstOrDefault()
)
: null
})
.Select(s=> new KnowledgeListVm
{
Id = s.Item.Id,
Name = s.TranslateAnySystemPriority != null ? s.TranslateAnySystemPriority.Name : null,
NameEn = s.TranslateEnglish != null ? s.TranslateEnglish.Name : null
})
.ToListAsync();
现在,当我添加一个只有本地翻译的新实体时,我收到以下异常,没有任何内部(InnerException):
System.NullReferenceException:“对象引用未设置为对象的实例。”
即使我做了所有的空检查。
因为我自己可以摆脱 Null Reference Exception 的事情,所以我将在答案中讲述剩下的故事。但是如果有人知道幕后发生了什么,请告诉我写一个更好的代码。因为我认为我只是在我的代码中作弊。
模型配置 TL;DR;
public class Knowledge : IIdentityIdEntity<int>, IHasTranslateEntity<Knowledge, KnowledgeTranslate, int>
{
public Knowledge()
{
Translates = new HashSet<KnowledgeTranslate>();
CreatorKnowledges = new HashSet<CreatorKnowledge>();
}
public int Id { get; set; }
public ICollection<KnowledgeTranslate> Translates { get; set; }
public ICollection<CreatorKnowledge> CreatorKnowledges { get; set; }
}
public class KnowledgeTranslate : IIdentityIdEntity<int>, IIsTranslateEntity<Knowledge, KnowledgeTranslate, int>
{
public int Id { get; set; }
public int OwnerId { get; set; }
public Knowledge Owner { get; set; }
public int LanguageId { get; set; }
public Language Language { get; set; }
public string Name { get; set; }
}
public class Language: IIdentityIdEntity<int>, IHasTranslateEntity<Language, LanguageTranslate, int>
{
public Language()
{
Translates = new HashSet<LanguageTranslate>();
//Languages = new HashSet<LanguageTranslate>();
// we have so many joins for Languages ... with languageTranslate, with ProjectTranslate, with any kind of XTranslate but we don't need them
}
public int Id { get; set; }
public string Iso6391 { get; set; }
public string Iso6392T { get; set; }
public string Iso6392B { get; set; }
public string Iso6393 { get; set; }
/// <summary>
/// Translated information that one language have (Joined with LanguageId)
/// </summary>
public virtual ICollection<LanguageTranslate> Translates { get; set; }
///// <summary>
///// Language in which the translation is based on (Joined with OwnerId)
///// </summary>
//public virtual HashSet<LanguageTranslate> Languages { get; set; }
// we have so many joins for Languages ... with languageTranslate, with ProjectTranslate, with any kind of XTranslate but we don't need them
}
映射器像这样映射它们:
if ((interfaceType = entityType.ClrType.GetInterfaces()
.FirstOrDefault(w => w.IsGenericType
&& w.GetGenericTypeDefinition() == typeof(IHasTranslateEntity<,,>))) != null)
{
if (interfaceType.GetGenericArguments().Length != 3)
{
throw new NotImplementedException(@$"Cannot find implementation for ""{typeof(IHasTranslateEntity<,,>).Name}"" interface that take more than one argument in ""{nameof(ApplicationDbContext)}"" class.");
}
// var genericArgType = interfaceType.GenericTypeArguments[0]; // Here act same as ClrType
var translateArgType = interfaceType.GenericTypeArguments[1];
var idArgType = interfaceType.GenericTypeArguments[2];
builder.SetTranslatesMapping(entityType.ClrType, translateArgType, idArgType);
}
#region Translates
public static void SetTranslatesMapping(this ModelBuilder modelBuilder, Type entityType, Type translateEntityType, Type tId)
{
SetTranslatesMappingMethod.MakeGenericMethod(entityType, translateEntityType, tId)
.Invoke(null, new object[] { modelBuilder });
}
static readonly MethodInfo SetTranslatesMappingMethod = typeof(EFFilterExtensions)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.Single(t => t.IsGenericMethod && t.Name == nameof(SetTranslatesMapping));
private static void SetTranslatesMapping<TEntity, TTranslateEntity, TId>(this ModelBuilder modelBuilder)
where TEntity : class, IHasTranslateEntity<TEntity, TTranslateEntity, TId>
where TTranslateEntity : class, IIsTranslateEntity<TEntity, TTranslateEntity, TId>
{
// Is Duplicate
// modelBuilder.Entity<TEntity>().Property(e => e.Id);
// var translateType = modelBuilder.Entity<TEntity>().Property(p => p.Translates).Metadata.ClrType;
modelBuilder
.Entity<TEntity>()
.HasMany(m => m.Translates)
.WithOne(o => o.Owner)
.HasForeignKey(fk => fk.OwnerId)
.OnDelete(DeleteBehavior.Cascade);
}
#endregion Translates
笔记:
我已经更新了好几次我的包,因为我没有时间,我碰巧看到了很多版本的 EfCore 3.1 包,现在我在 3.1.10 上。