I am developing an inventory management system and I want to add a product with variants. I can add a product with three variants(color, size, material) and the options for each as below:
color - Black, Blue, Grey size - S,M,L,XL material - Cotton, Wool
If specify only 2 variants(e.g. color and size) my code is generating all the options correctly but if I add a 3rd variant then its not giving me the expected output.
Suppose I have a product called Jean my expected output would be as below:
===========================================
===========================================
My model is as below:
public class CreateModel : PageModel
{
[Required]
[BindProperty]
public string? Name { get; set; }
[BindProperty]
public List<ProductVariantModel> Variants { get; set; }
}
ProductVariantModel
public class ProductVariantModel
{
public string? Name { get; set; }
public string? Options { get; set; }
}
I'm creating the combinations as below:
List<ProductVariantOption> productOptions = new();
try
{
int variantsTotal = model.Variants.Count;
for (int a = 0; a < variantsTotal; a++)
{
string[] options = model.Variants[a].Options.Split(',');
for (int i = 0; i < options.Length; i++)
{
string? option = $"{model.Name}-{options[i]}";
if (variantsTotal > 1)
{
int index = a + 1;
if (index < variantsTotal)
{
var levelBelowOptions = model.Variants[index].Options.Split(',');
var ops = GetOptions(option, levelBelowOptions);
productOptions.AddRange(ops);
}
}
}
a += 1;
}
}
GetOptions method
private List<ProductVariantOption> GetOptions(string option, string[] options)
{
List<ProductVariantOption> variantOptions = new();
for (int i = 0; i < options.Length; i++)
{
string sku = $"{option}/{options[i]}";
string opt = $"{option}/{options[i]}";
variantOptions.Add(new ProductVariantOption(opt, sku));
}
return variantOptions;
}
ProductVariantOption
public class ProductVariantOption
{
public string Name { get; private set; }
public string SKU { get; private set; }
public Guid ProductVariantId { get; private set; }
public ProductVariant ProductVariant { get; private set; }
public ProductVariantOption(string name, string sku)
{
Guard.AgainstNullOrEmpty(name, nameof(name));
Name = name;
SKU = sku;
}
}
Where am I getting it wrong?
If you generalize your problem, you can describe it as follows:
So you might want to generate cross products of all lists of variables. This could be achieved with an .Aggregate
which does .SelectMany
inside (note that I have simplified the definition of the final output, but you can construct it as you want inside the .BuildModel
method:
private static ProductVariantOption BuildModel(string[] modelParts) {
if (modelParts.Length == 1) {
return new ProductVariantOption {
Name = modelParts.Single()
};
}
var baseName = modelParts.First();
var variantParts = string.Join('/', modelParts.Skip(1));
return new ProductVariantOption {
Name = $"{baseName}-{variantParts}"
};
}
public static IList<ProductVariantOption> GetVariants(CreateModel model) {
// Prepare all possible variables from the model in advance
var allVariables = model.Variants.Select(v => v.Options.Split(",")).ToArray();
var initialParts = new List<string[]> { new[] { model.Name } };
// Generate cross product for every subsequent variant with initial list as a seed
// Every iteration of aggregate produces all possible combination of models with the new variant
var allModels = allVariables.Aggregate(initialParts, (variantsSoFar, variableValues) =>
variantsSoFar
.SelectMany(variant => variableValues.Select(variableValue => variant.Append(variableValue).ToArray()))
.ToList()
);
// Map all lists of model parts into ProductVariantOption
return allModels.Select(BuildModel).ToList();
}
This approach has the benefit of being able to handle any amount of potential variables including cases where there are no variables (in this case only a single variant is produced - In your example it would be just "Jean")
Complete running example: https://dotnetfiddle.net/XvkPZQ