I’m creating a complex object from the result of a query in legacy SQL Server database with Dapper and using Fluent Builder pattern. Here’s all of my code below.
- My entities:
public sealed class Entity
{
public int Id { get; private set; }
public List<Plan> Plans { get; private set; } = new List<Plan>();
public bool IsMultiSponsored { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
internal Entity()
{
}
public Entity(
int id,
bool isMultiSponsored,
string name,
string description,
List<Plan> plans)
{
Id = id;
IsMultiSponsored = isMultiSponsored;
Name = name;
Description = description;
Plans = plans;
}
public Entity(
int id,
bool isMultiSponsored,
string name,
string description,
params Plan[] plans)
{
Id = id;
IsMultiSponsored = isMultiSponsored;
Name = name;
Description = description;
Plans.AddRange(plans);
}
}
public sealed class Plan
{
public int Id { get; private set; }
public int EntityId { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public List<Sponsor> Sponsors { get; private set; } = new List<Sponsor>();
internal Plan()
{
}
public Plan(
int id,
int entityId,
string name,
string description,
List<Sponsor> sponsors)
{
Id = id;
EntityId = entityId;
Name = name;
Description = description;
Sponsors = sponsors;
}
public Plan(
int id,
int entityId,
string name,
string description,
params Sponsor[] sponsors)
{
Id = id;
EntityId = entityId;
Name = name;
Description = description;
Sponsors.AddRange(sponsors);
}
}
public sealed class Sponsor
{
public int Id { get; private set; }
public int PlanId { get; private set; }
public List<Branch> Branches { get; private set; } = new();
public int Alias { get; private set; }
public string Name { get; private set; }
public bool IsActive { get; private set; }
public DateTime UpdatedAt { get; private set; }
public DateTime? EffectiveDate { get; private set; }
internal Sponsor()
{
}
public Sponsor(
int id,
int planId,
int alias,
string name,
bool isActive,
DateTime updatedAt,
DateTime? effectiveDate,
List<Branch> branches)
{
Id = id;
PlanId = planId;
Alias = alias;
Name = name;
IsActive = isActive;
UpdatedAt = updatedAt;
EffectiveDate = effectiveDate;
Branches = branches;
}
public Sponsor(
int id,
int planId,
int alias,
string name,
bool isActive,
DateTime updatedAt,
DateTime? effectiveDate,
params Branch[] branch)
{
Id = id;
PlanId = planId;
Alias = alias;
Name = name;
IsActive = isActive;
UpdatedAt = updatedAt;
EffectiveDate = effectiveDate;
Branches.AddRange(branch);
}
}
public sealed class Branch
{
public int Id { get; private set; }
public int SponsorId { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public string Alias { get; private set; }
public bool IsActive { get; private set; }
public DateTime? UpdatedAt { get; private set; }
internal Branch()
{
}
public Branch(
int id,
int sponsorId,
string name,
string description,
string alias,
bool isActive,
DateTime? updatedAt)
{
Id = id;
SponsorId = sponsorId;
Name = name;
Description = description;
Alias = alias;
IsActive = isActive;
UpdatedAt = updatedAt;
}
}
- My Fluent Builders (all builders are transient lifetime):
public sealed class EntityBuilder : IEntityBuilder
{
private int _id;
private string _name;
private string _description;
private bool _isMultiSponsored;
private Plan _entityPlan;
private readonly IPlanBuilder _planBuilder;
public IDictionary<int, Entity> InstanceCache { get; set; }
public EntityBuilder(IPlanBuilder planBuilder)
{
InstanceCache = new Dictionary<int, Entity>();
_planBuilder = planBuilder;
}
public EntityBuilder WithId(int id)
{
_id = id;
return this;
}
public EntityBuilder WithName(string name)
{
_name = name;
return this;
}
public EntityBuilder WithDescription(string description)
{
_description = description;
return this;
}
public EntityBuilder IsMultiSponsored(bool isMultiSponsored)
{
_isMultiSponsored = isMultiSponsored;
return this;
}
public EntityBuilder HavingPlan(Action<IPlanBuilder> expression)
{
expression(_planBuilder);
var createdPlan = _planBuilder.Build();
if(createdPlan.EntityId == _id)
_entityPlan = createdPlan;
return this;
}
public Entity Build()
{
if(InstanceCache.TryGetValue(_id, out var existingEntity))
{
var planFounded = existingEntity.Plans.Find(existingPlan => existingPlan.EntityId == _entityPlan.Id);
if(planFounded is null)
existingEntity.Plans.Add(planFounded);
return existingEntity;
}
var entity = new Entity(
_id,
_isMultiSponsored,
_name,
_description,
_entityPlan);
InstanceCache.Add(entity.Id, entity);
return entity;
}
}
public sealed class PlanBuilder : IPlanBuilder
{
private int _id;
private int _entityId;
private string _name;
private string _description;
private Sponsor _planSponsor;
private readonly ISponsorBuilder _sponsorBuilder;
public IDictionary<int, Plan> InstanceCache { get; set; }
public PlanBuilder(ISponsorBuilder sponsorBuilder)
{
InstanceCache = new Dictionary<int, Plan>();
_sponsorBuilder = sponsorBuilder;
}
public PlanBuilder WithId(int id)
{
_id = id;
return this;
}
public PlanBuilder WithId(int id, int entityId)
{
_id = id;
_entityId = entityId;
return this;
}
public PlanBuilder WithEntityId(int entityId)
{
_entityId = entityId;
return this;
}
public PlanBuilder WithName(string name)
{
_name = name;
return this;
}
public PlanBuilder WithDescription(string description)
{
_description = description;
return this;
}
public PlanBuilder HavingSponsor(Action<ISponsorBuilder> expression)
{
expression(_sponsorBuilder);
var createdSponsor = _sponsorBuilder.Build();
if(createdSponsor.PlanId == _id)
_planSponsor = createdSponsor;
return this;
}
public Plan Build()
{
if(InstanceCache.TryGetValue(_id, out var existingPlan))
{
var sponsorFounded = existingPlan.Sponsors.Find(existingSponsor => existingSponsor.Id == _planSponsor.Id && existingSponsor.PlanId == _planSponsor.PlanId);
if(sponsorFounded is null)
existingPlan.Sponsors.Add(_planSponsor);
return existingPlan;
}
var plan = new Plan(
_id,
_entityId,
_name,
_description,
_planSponsor
);
InstanceCache.Add(plan.Id, plan);
return plan;
}
}
public sealed class SponsorBuilder : ISponsorBuilder
{
private int _id;
private int _planId;
private string _name;
private int _alias;
private bool _isActive;
private DateTime? _effectiveDate;
private DateTime _updatedAt;
private Branch _sponsorBranch;
private readonly IBranchBuilder _branchBuilder;
public IDictionary<(int, int), Sponsor> InstanceCache { get; set; }
public SponsorBuilder(IBranchBuilder branchBuilder)
{
InstanceCache = new Dictionary<(int, int), Sponsor>();
_branchBuilder = branchBuilder;
}
public SponsorBuilder WithId(int id)
{
_id = id;
return this;
}
public SponsorBuilder WithId(int id, int planId)
{
_id = id;
_planId = planId;
return this;
}
public SponsorBuilder WithName(string name)
{
_name = name;
return this;
}
public SponsorBuilder WithAlias(int alias)
{
_alias = alias;
return this;
}
public SponsorBuilder IsActive(bool isActive)
{
_isActive = isActive;
return this;
}
public SponsorBuilder WithEffectiveDate(DateTime? effectiveDate)
{
_effectiveDate = effectiveDate;
return this;
}
public SponsorBuilder WithUpdatedAt(DateTime updatedAt)
{
_updatedAt = updatedAt;
return this;
}
public SponsorBuilder HavingBranch(Action<IBranchBuilder> expression)
{
expression(_branchBuilder);
var createdBranch = _branchBuilder.Build();
if (createdBranch.SponsorId == _id)
_sponsorBranch = createdBranch;
return this;
}
public Sponsor Build()
{
if (InstanceCache.TryGetValue((_id, _planId), out var existingSponsor))
{
var branchFounded = existingSponsor.Branches.Find(existingBranch => existingBranch.Id == _sponsorBranch.Id);
if(branchFounded is null)
existingSponsor.Branches.Add(_sponsorBranch);
return existingSponsor;
}
var sponsor = new Sponsor(
_id,
_planId,
_alias,
_name,
_isActive,
_updatedAt,
_effectiveDate,
_sponsorBranch
);
InstanceCache.Add((sponsor.Id, sponsor.PlanId), sponsor);
return sponsor;
}
}
public sealed class BranchBuilder : IBranchBuilder
{
private int _id;
private int _sponsorId;
private string _name;
private string _description;
private string _alias;
private bool _isActive;
private DateTime? _updatedAt;
public IDictionary<(int, int), Branch> InstanceCache { get; set; }
public BranchBuilder()
{
InstanceCache = new Dictionary<(int, int), Branch>();
}
public BranchBuilder WithId(int id)
{
_id = id;
return this;
}
public BranchBuilder WithId(int id, int sponsorId)
{
_id = id;
_sponsorId = sponsorId;
return this;
}
public BranchBuilder WithSponsorId(int sponsorId)
{
_sponsorId = sponsorId;
return this;
}
public BranchBuilder WithName(string name)
{
_name = name;
return this;
}
public BranchBuilder WithAlias(string alias)
{
_alias = alias;
return this;
}
public BranchBuilder WithDescription(string description)
{
_description = description;
return this;
}
public BranchBuilder IsActive(bool isActive)
{
_isActive = isActive;
return this;
}
public BranchBuilder WithUpdatedAt(DateTime? updatedAt)
{
_updatedAt = updatedAt;
return this;
}
public Branch Build()
{
if (InstanceCache.TryGetValue((_id, _sponsorId), out var existingBranch))
return existingBranch;
var branch = new Branch(
_id,
_sponsorId,
_name,
_description,
_alias,
_isActive,
_updatedAt
);
InstanceCache.Add((branch.Id, branch.SponsorId), branch);
return branch;
}
}
- And my unit Test
[Fact]
public void Should_Be_Detected_Memory_Leak_After_Parse_Completed_With_Success()
{
// Arrange
var source = GetQueryResults();
WeakReference reference = default;
// Act
foreach (var queryResult in source)
{
_entityBuilder
.WithId(queryResult.EntityId)
.WithName(queryResult.EntityName)
.IsMultiSponsored(queryResult.IsMultiSponsored)
.HavingPlan(planBuilder =>
{
planBuilder
.WithId(queryResult.PlanId, queryResult.EntityId)
.WithName(queryResult.PlanName)
.HavingSponsor(sponsorBuilder =>
{
sponsorBuilder
.WithId(queryResult.SponsorId, queryResult.PlanId)
.WithAlias(queryResult.SponsorAlias)
.WithName(queryResult.SponsorName)
.WithEffectiveDate(queryResult.EffectiveDate)
.IsActive(queryResult.SponsorIsActive)
.HavingBranch(branchBuilder =>
{
branchBuilder
.WithId(queryResult.BranchId, queryResult.SponsorId)
.WithAlias(queryResult.BranchAlias)
.IsActive(queryResult.BranchIsActive);
});
});
});
}
var entity = _entityBuilder.Build();
reference = new WeakReference(entity, true);
GC.Collect();
GC.WaitForPendingFinalizers();
// Assert
Assert.True(reference.IsAlive);
}
What could be causing a memory leak in this implementation? Thanks in advance for everyone’s help!
To identify and resolve the memory leak, you can follow these steps
New contributor
Gabriel Ribeiro is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.