Skip to content

Bug: Extract function throws exception when using ExecuteQueryMultiple #761

@mikependon

Description

@mikependon

Bug Description

Taken from: https://stackoverflow.com/questions/65946130/repodb-extract-function-throws-exception-when-using-executequerymultiple

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:

 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"));
}

Library Version:

Example: RepoDb v1.12.6 and RepoDb.SqlServer v1.1.2

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdeployedFeature or bug is deployed at the current releasefixedThe bug, issue, incident has been fixed.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions