Mapping a listing to a "sub-listing"
I have a database containing Products . These products are classified using Category and Subcategory .
For example:
Product p1 = new Product()
{
Category = Category.Fruit,
Subcategory = Subcategory.Apple
};
My problem is that I want to restrict the subcategory based on the category.
The example below is not possible:
Product p2 = new Product()
{
Category = Category.Fruit,
Subcategory = Subcategory.Cheese
};
Also, I would like to be able to return an array of strings (corresponding to each category enum), each with an array of corresponding subcategories.
I pondered for a while but came up with nothing and I didn't find any solutions on the internet.
What do you advise?
source to share
I like the card 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 writing a class SubcategoryOfAttribute
(see here for the MS manual). Then you can write a verifier that can look at any subcategory and get the legal parent category out of it.
The advantage of this over a map is that the relationship is well documented in the declaration.
The disadvantage is that each subcategory can have at most one parent category.
I found it intriguing, so I cut it off. Attribute first:
[AttributeUsage(AttributeTargets.Field)]
public class SubcategoryOf : Attribute {
public SubcategoryOf(Category cat) {
Category = cat;
}
public Category Category { get; private set; }
}
Then we do 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 change the attribute and this predicate to get all the attributes and check them.
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 enter your product type to test it:
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
source to share
What you are trying to do is represent first order logic ( http://en.wikipedia.org/wiki/First-order_logic ) using enums. And synchronization with the database. This is not an easy task when hardcoded in code. Many good solutions have already been suggested.
For my part, I would just use strings (or unique IDs) for the category and subcategory, and enforce integrity using the rules defined in the database. But if you end up using it in your code, it won't be compile time.
The problem with Enum is that it has to match your external source and your code. In addition, it becomes difficult to attach more information to it, such as price or country, or even if you have different types of apples.
source to share
My suggestion would be to have Dictionary<SubCategory, Category>
one that matches yours SubCategory
to yours Category
.
After that you can just get rid of Category
your product together, or just use the 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]; }
}
}
source to share