RepoDb - Extract function throws exception when using ExecuteQueryMultiple

0

when i execute the code below, it generates an exception. where am i going wrong?

Code that generates exception specifically extractor.Extract:

public Visit Get(long id, bool loadGraph = false) {
                const string sqlVisit = @"SELECT * FROM Visit WHERE Id = @VisitId;";
                const string sqlCC = @"SELECT * FROM ChiefComplaint WHERE VisitId = @VisitId;";
                var sqlParam = new { VisitId = id };
    
                if (loadGraph) {
                    using (var extractor = base.ExecuteQueryMultiple(sqlVisit + sqlCC, sqlParam)) {
                        /*fails with exception*/var v = extractor.Extract<Visit>().FirstOrDefault();
                        var cc = extractor.Extract<ChiefComplaint>().AsList();
                        return new Visit(v.Id, v.DateOfService, cc);
                    }                
                }
                else {
                    using (var extractor = base.ExecuteQueryMultiple(sqlVisit, sqlParam)) {
                        return extractor.Extract<Visit>().FirstOrDefault();
                    }
                }
            }

The domain entity that corresponds to the Visit table:

    public class Visit : BaseEntity {

        private List<ChiefComplaint> _chiefComplaints = new List<ChiefComplaint>();
        public IReadOnlyList<ChiefComplaint> ChiefComplaints => _chiefComplaints.ToList();
        public DateTime DateOfService { get; }

        public Visit(long id, DateTime dateOfService, IEnumerable<ChiefComplaint> chiefComplaints) : base(id) {
            this.DateOfService = dateOfService;
            _chiefComplaints.AddRange(chiefComplaints);
        }
    }

The base domain entity that all domain entities inherit from:

 public abstract class BaseEntity {
        public long Id { get; }

        protected BaseEntity(long id) {
            this.Id = id;
        }

        public override bool Equals(object obj) {
            if (!(obj is BaseEntity other))
                return false;

            if (ReferenceEquals(this, other))
                return true;

            if (this.GetType() != other.GetType())
                return false;

            if (this.Id == 0 || other.Id == 0)
                return false;

            return this.Id == other.Id;
        }

        public static bool operator ==(BaseEntity a, BaseEntity b) {
            if (a is null && b is null)
                return true;

            if (a is null || b is null)
                return false;

            return a.Equals(b);
        }

        public static bool operator !=(BaseEntity a, BaseEntity b) {
            return !(a == b);
        }

        public override int GetHashCode() {
            return (this.GetType().ToString() + this.Id).GetHashCode();
        }

    }

The Visit and ChiefComplaint tables:

CREATE TABLE [dbo].[Visit] (
    [Id]            BIGINT        IDENTITY (1, 1) NOT NULL,
    [DateOfService] DATETIME2 (7) NOT NULL
);

CREATE TABLE [dbo].[ChiefComplaint] (
    [Id]          BIGINT         IDENTITY (1, 1) NOT NULL,
    [Description] NVARCHAR (MAX) NULL,
    [HpiId]       BIGINT         NULL,
    [VisitId]     BIGINT         NULL
);

The exception:

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=RepoDb
  StackTrace:
   at RepoDb.Reflection.Compiler.<>c__DisplayClass62_0`1.<GetClassPropertyParameterInfos>b__1(ParameterInfo parameterInfo)
   at System.Collections.Generic.List`1.ForEach(Action`1 action)
   at RepoDb.Reflection.Compiler.GetClassPropertyParameterInfos[TResult](IEnumerable`1 readerFieldsName, IDbSetting dbSetting)
   at RepoDb.Reflection.Compiler.GetMemberBindingsForDataEntity[TResult](ParameterExpression readerParameterExpression, IEnumerable`1 readerFields, IDbSetting dbSetting)
   at RepoDb.Reflection.Compiler.CompileDataReaderToDataEntity[TResult](DbDataReader reader, IEnumerable`1 dbFields, IDbSetting dbSetting)
   at RepoDb.Reflection.Compiler.CompileDataReaderToType[TResult](DbDataReader reader, IEnumerable`1 dbFields, IDbSetting dbSetting)
   at RepoDb.Reflection.FunctionFactory.CompileDataReaderToType[TResult](DbDataReader reader, IEnumerable`1 dbFields, IDbSetting dbSetting)
   at RepoDb.FunctionCache.DataReaderToTypeCache`1.Get(DbDataReader reader, IEnumerable`1 dbFields, IDbSetting dbSetting)
   at RepoDb.FunctionCache.GetDataReaderToTypeCompiledFunction[TResult](DbDataReader reader, IEnumerable`1 dbFields, IDbSetting dbSetting)
   at RepoDb.Reflection.DataReader.<ToEnumerable>d__0`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at RepoDb.Extensions.EnumerableExtension.AsList[T](IEnumerable`1 value)
   at RepoDb.QueryMultipleExtractor.Extract[TEntity](Boolean isMoveToNextResult)
   at Repositories.VisitRepository.Get(Int64 id, Boolean loadGraph) 

I tried to implement the property handler per the docs on RepoDb but with no success. Below is how I implemented the property handler.

    public class ChiefComplaintPropertyHandler : IPropertyHandler<string, ChiefComplaint> {
        public ChiefComplaint Get(string input, ClassProperty property) {
            return JsonConvert.DeserializeObject<ChiefComplaint>(input);
        }
        public string Set(ChiefComplaint input, ClassProperty property) {
            return JsonConvert.SerializeObject(input);
        }
    }

In my data layer project I have a DependencyInjection.cs class that implements configuration requirements and gets called in ConfigureServices as below. This is where I specify the FluentMapper mapping.

    public static class DependencyInjection {
        public static IServiceCollection AddDataCoreServices(this IServiceCollection services, IConfigurationSection dbConfigSection) {
            SqlServerBootstrap.Initialize();
            FluentMapper
                .Entity<Visit>()
                .PropertyHandler<ChiefComplaintPropertyHandler>(v => v.ChiefComplaints, true);

            services.Configure<AppSetting>(dbConfigSection);
            services.AddTransient<IUnitOfWork, UnitOfWork>();
            services.AddTransient<IVisitRepository, VisitRepository>();

            return services;
        }
    }

The ConfigureServices section of Startup.cs in the web api project.

        public void ConfigureServices(IServiceCollection services) {
            services.AddControllers();
            services.AddDataCoreServices(Configuration.GetSection("AppSettings"));
        }
orm
micro-orm
asked on Stack Overflow Jan 28, 2021 by Dennis Polley • edited Feb 3, 2021 by Dennis Polley

1 Answer

0

The latest version of the library has supported the immutable classes and that it requires the ctor fields to be matching from the data reader columns.

Below is our recommendation.

Your Visit class must not have the chiefComplaints argument in the ctor.

public Visit(long id, DateTime dateOfService) : base(id)
{
    this.DateOfService = dateOfService;
}

Then, add an additional method to set the local variable chief complaints (or just make it a public writable property like below).

public List<ChiefComplaint> ChiefComplaints { get; set; }

During the extract, simply set the property (see below).

var v = extractor.Extract<Visit>().FirstOrDefault();
var cc = extractor.Extract<ChiefComplaint>().AsList();
return new Visit(v.Id, v.DateOfService)
{
     ChiefComplaints = cc.AsList()
};

Though, we have added an issue on our GH page to improve this ctor constructions.


User contributions licensed under CC BY-SA 3.0