繁体   English   中英

EF Core throws Null Reference Exception when Null Check is done (Secondary Select with null check throws Null Reference Exception)

[英]EF Core throws Null Reference Exception when Null Check is done (Secondary Select with null check throws Null Reference Exception)

我有几个复杂的查询,通过反射映射的 generics 类自动映射 model 。

如果需要,我会在问题下方添加实体配置。

问题

至于问题,我有一个通过多次传递构建的查询,一个传递在基本存储库中,另一个在实体特定存储库中。

但完成生成的查询将是这样的:

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:“对象引用未设置为 object 的实例。”

即使我做了所有 null 检查。

因为我可以摆脱 Null 参考异常的事情,我自己会在答案中讲述 rest 的故事。 但是如果有人知道幕后发生了什么,请告诉我写一个更好的代码。 因为我认为我只是在我的代码中作弊。

Model 配置 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
}

和映射器 map 他们像这样:

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 上。

所以每次我遇到我的项目时,我也访问了这个错误,因为我有一点时间,我最终检查了我所有的检查,发现它们都是正确的,并且没有任何改变就离开了项目。

今天,我有更多时间,我开始按照您在上面看到的那样一步一步地重新创建查询,我明白较大的查询不会失败( TranslateAnySystemPriority )但较小的查询( TranslateEnglish

带有以下查询部分:

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

实际上,如果我说,问题出在第一个 Select 中返回 null 会更好。 我运行了几个测试,我发现:如果我在第一次或第二次Select中对其进行一次空值检查,或者不对它运行任何空值检查,它将按我的预期工作,它工作正常。

所以我删除了所有 null 检查。

但是,如果,我对Select进行 null 检查,只要从第一个select的返回数据为空,它总是会抛出NullReferenceException 不管你做了多少次空检查。

所以固定代码是这样的:

NameEn = s.TranslateEnglish //!= null ? s.TranslateEnglish.Name : null

或者

TranslateEnglish = //s.Translates != null //.Any()
                    //? 
                s.Translates.FirstOrDefault(w => w.Language.Iso6391 == FixedData.Language.English.Iso6391)
                    //: null,
                    ,

或者两者都做。

我的最终代码是:

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();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM