Reputation: 2021
I had several complex query with auto mapped model through generics classes mapped by reflection.
I add the entity configuration below my issue, in case of need.
As for Issue, I have a query which is built through multiple pass, one pass is in Base Repository, and the other is in entity specific Repository.
but the completed generated query will be like this:
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();
Now when i add a new entity with only native translation, I received following exception, without any inner (InnerException):
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Even though I did all the null checks.
As I could get rid of the Null Reference Exception thing, myself, I will tell the rest of story in the answer. But if anyone knows what is going behind the scene please tell me to write a better code. as I think i just cheat in my code.
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
}
and the mapper map them like this:
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
I already updated my packages several time, as I had little time, I happen to see so many versions of EfCore 3.1 packages, and now I'm on 3.1.10.
Upvotes: 1
Views: 1430
Reputation: 2021
So every time i came across my project, I also visited this error, and since i had a little time, I end up checking all my checks, seeing all of them are right, and left the project without any change.
Today, I had a little bit more time, I start recreating the query as you see above, step by step, and i Understand that the bigger one does not fails (TranslateAnySystemPriority
) but the smaller one (TranslateEnglish
)
with following query parts:
Select 1:
TranslateEnglish = s.Translates != null //.Any()
? s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.English.Iso6391)
: null,
Select 2:
NameEn = s.TranslateEnglish != null ? s.TranslateEnglish.Name : null
Actually it's better if I say, the issue was with one that returned null in first Select.
I run several test, and I found:
If I null-check it once in first or second Select
, or do not run any null-check on it, it will work as what I'm not expecting, it work fine.
So I remove all the null checks.
But if, I do the null check on both Select
, It always throws NullReferenceException
as long as the returning data from first select
is empty. No matter how many null-check you do.
So the Fixed code is either of this:
NameEn = s.TranslateEnglish //!= null ? s.TranslateEnglish.Name : null
or
TranslateEnglish = //s.Translates != null //.Any()
//?
s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.English.Iso6391)
//: null,
,
Or doing both.
The final code for me is:
var x = await _uow.Knowledge.Query.Include(i => i.Translates)
.ThenInclude(i => i.Language)
.Select(s => new
{
Item = s,
TranslateNative = s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.Native.Iso6391),
TranslateEnglish = s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.English.Iso6391),
TranslateSystem = s.Translates.FirstOrDefault(w => w.LanguageId == SystemLanguageId),
TranslateAnyNativePriority =
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(),
TranslateAnySystemPriority = (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()
})
.Select(s => new //KnowledgeListVm
{
Id = s.Item.Id,
Name = s.TranslateAnySystemPriority.Name,
NameEn = s.TranslateEnglish.Name
})
.ToListAsync();
Upvotes: 1