Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Extract function throws exception when using ExecuteQueryMultiple #761

Closed
mikependon opened this issue Feb 6, 2021 · 1 comment
Closed
Assignees
Labels
bug Something isn't working deployed Feature or bug is deployed at the current release fixed The bug, issue, incident has been fixed.

Comments

@mikependon
Copy link
Owner

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

@mikependon mikependon added the bug Something isn't working label Feb 6, 2021
@mikependon mikependon self-assigned this Feb 6, 2021
@mikependon
Copy link
Owner Author

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

mikependon added a commit that referenced this issue Feb 6, 2021
@mikependon mikependon added the fixed The bug, issue, incident has been fixed. label Feb 6, 2021
mikependon added a commit that referenced this issue Feb 6, 2021
#753 #761 - Added a descriptive message for the unmatched ctor argume…
mikependon added a commit that referenced this issue Feb 6, 2021
mikependon added a commit that referenced this issue Feb 6, 2021
@mikependon mikependon added the deployed Feature or bug is deployed at the current release label Feb 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working deployed Feature or bug is deployed at the current release fixed The bug, issue, incident has been fixed.
Projects
None yet
Development

No branches or pull requests

1 participant