I have a database containing Products. These products are categorized, having a Category and a Subcategory.
For example:
Product p1 = new Product()
{
Category = Category.Fruit,
Subcategory = Subcategory.Apple
};
My issue is the fact that I want to restrict the subcategory, depending on the category.
The below example should not be possible:
Product p2 = new Product()
{
Category = Category.Fruit,
Subcategory = Subcategory.Cheese
};
Furthermore I'd like to be able to return an array of strings (matching each Category enum) which each has an array of the corresponding subcategories.
I've been thinking for a while but have come up with nothing, nor have I found any solutions online.
What would be advised?
I like the map rule. You can also put a custom attribute on your enum values.
For example:
public enum Subcategory {
[SubcategoryOf(Category.Fruit)]
Apple,
[SubcategoryOf(Category.Dairy)]
Emmenthaler
}
This requires that you write a SubcategoryOfAttribute
class (see here for the MS guide). Then you can write a verifier that can look at any subcategory and get the legal parent category from it.
The advantage to this over the map is that the relationship is spelled out in the declaration nicely.
The disadvantage is that each subcategory can have a maximum of one parent category.
I found this in intriguing, so I stubbed it out. First the attribute:
[AttributeUsage(AttributeTargets.Field)]
public class SubcategoryOf : Attribute {
public SubcategoryOf(Category cat) {
Category = cat;
}
public Category Category { get; private set; }
}
Then we make some mock enums
public enum Category {
Fruit,
Dairy,
Vegetable,
Electronics
}
public enum Subcategory {
[SubcategoryOf(Category.Fruit)]
Apple,
[SubcategoryOf(Category.Dairy)]
Buttermilk,
[SubcategoryOf(Category.Dairy)]
Emmenthaler,
[SubcategoryOf(Category.Fruit)]
Orange,
[SubcategoryOf(Category.Electronics)]
Mp3Player
}
Now we need a predicate to determine if a subcategory matches a category (note: you can have multiple parent categories if you want - you need to modify the attribute and this predicate to get all attributes and check each one.
public static class Extensions {
public static bool IsSubcategoryOf(this Subcategory sub, Category cat) {
Type t = typeof(Subcategory);
MemberInfo mi = t.GetMember(sub.ToString()).FirstOrDefault(m => m.GetCustomAttribute(typeof(SubcategoryOf)) != null);
if (mi == null) throw new ArgumentException("Subcategory " + sub + " has no category.");
SubcategoryOf subAttr = (SubcategoryOf)mi.GetCustomAttribute(typeof(SubcategoryOf));
return subAttr.Category == cat;
}
}
Then you put in your product type to test it out:
public class Product {
public Product(Category cat, Subcategory sub) {
if (!sub.IsSubcategoryOf(cat)) throw new ArgumentException(
String.Format("{0} is not a sub category of {1}.", sub, cat), "sub");
Category = cat;
Subcategory = sub;
}
public Category Category { get; private set; }
public Subcategory Subcategory { get; private set; }
}
Test code:
Product p = new Product(Category.Electronics, Subcategory.Mp3Player); // succeeds
Product q = new Product(Category.Dairy, Subcategory.Apple); // throws an exception
What you are trying to do is to represent first order logic (http://en.wikipedia.org/wiki/First-order_logic) using enums. And syncing with a database. It's not an easy task when hard-coding it in code. Many good solutions have already been suggested.
For my part, I would just use strings (or unique ids) for Category and SubCategory and enforce the integrity using the rules defined in the database. But if you end up using it in code, it won't be compile-time.
The problem with Enum is that it must match with your external source and your code. Also, it becomes difficult to attach more information to it, like the price or country or even if you have different kind of apples.
My suggestion would be to have a Dictionary<SubCategory, Category>
, that maps your SubCategory
to your Category
.
After that, you can just get rid of the Category
on your product all together, or you can just use a helper method
public class Product
{
static Dictionary<SubCategory, Category> _categoriesMap;
public static Product()
{
_categoriesMap = new Dictionary<SubCategory, Category>();
_categoriesMap.Add(SubCategory.Apple, Category.Fruit);
}
public SubCategory SubCategory { get; set; }
public Category Category
{
get { return _categoriesMap[this.SubCategory]; }
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With