Sven
Sven

Reputation: 2889

Adding dynamic attributes to a model

I can't wrap my mind around this issue and haven't found the correct search keys yet:

I would like to have several categories of items in which all items have specific attributes. Those attributes (text fields, dropdowns, or checkboxes) should be added to a category and I want to edit and save those attributes for each item.

I'm working with MVC 4 and code-first EF5. How can I implement this?

My first approach were several classes like Text, Dropdown that were inherited from an abstract Attribute class and a Category class like this:

public class Category
{
    [Key]
    public int CategoryId { get; set; }

    public string Name { get; set; }
    public string Description { get; set; }

    public virtual ICollection<Item> Items { get; set; }
    public virtual ICollection<Attribute> Attributes { get; set; } 
}

But then I had no idea to proceed. Am I on the right way or completely wrong? Can someone give me a few hints I can search for?

Edit

Ultimately I'm trying to build a list of hifi devices. Speakers have different attributes than amplifier and those have different attributes to tape recorders. I would like to give a unified look for the details of each device and pre-define specific attributes to that category with an additional free-for-all text area. Speaker XYZ is my item, Speaker my category and dB an attribute.

Upvotes: 0

Views: 487

Answers (3)

umair.ali
umair.ali

Reputation: 2714

I actually never worked with code first approach, but I can give you some idea about how this scenario can be handled...To me, it looks that Item is the major one instead of Category. So you can have this structure...

public class Category
{
    [Key]
    public int CategoryId { get; set; }
    public string CategoryName { get; set; }
    public string CategoryDescription { get; set; }
    // use attributes here if you want them for Category
    //public Dictionary<string, string> ItemnAttributes { get; set; }
}

public class MyItem
{
    [Key]
    public int ItemId { get; set; }
    public string ItemName { get; set; }
    public string ItemDescription { get; set; }
    public Category ItemnCatagory { get; set; }
    public Dictionary<string, string> ItemnAttributes { get; set; }
}

Hope this helps..

Upvotes: 1

NSGaga
NSGaga

Reputation: 14312

I think something like this may work for you...

public class Category
{
    public int CategoryId { get; set; }

    public string Name { get; set; }
    public string Description { get; set; }

    public virtual ICollection<Item> Items { get; set; }
    public virtual ICollection<Attribute> Attributes { get; set; }
}

public class Item
{
    public int ItemId { get; set; }

    public string Name { get; set; }
    public string Description { get; set; }

    public int CategoryId { get; set; }
    public Category Category { get; set; }
    public virtual ICollection<ItemAttribute> ItemAttributes { get; set; }
}

public class Attribute
{
    public int AttributeId { get; set; }

    public string Name { get; set; }
    public string Description { get; set; }

    public virtual ICollection<Category> Categories { get; set; }
    public virtual ICollection<ItemAttribute> ItemAttributes { get; set; }
}

public class ItemAttribute
{
    public int ItemId { get; set; }
    public int AttributeId { get; set; }

    public Item Item { get; set; }
    public Attribute Attribute { get; set; }

    public string Value { get; set; }
    public int ValueInt{ get; set; }
    // etc. 
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<ItemAttribute>()
        .HasKey(x => new { x.ItemId, x.AttributeId });
    modelBuilder.Entity<ItemAttribute>()
        .HasRequired(x => x.Item)
        .WithMany(x => x.ItemAttributes)
        .HasForeignKey(x => x.ItemId)
        .WillCascadeOnDelete(false);
    modelBuilder.Entity<ItemAttribute>()
        .HasRequired(x => x.Attribute)
        .WithMany(x => x.ItemAttributes)
        .HasForeignKey(x => x.AttributeId)
        .WillCascadeOnDelete(false);
    // AttributeCategories is created for you - but you can do the same as ^ above to customize
    // just change 'ICollection<Category> Categories' to collection of 'ItemAttribute'
}

// use it like e.g.
var item = new Item { Name = "ItemTest", };
var attribute = new Attribute { Name = "attributeTest", };
item.ItemAttributes = new List<ItemAttribute> 
{
    new ItemAttribute { Item = item, Attribute = attribute, Value = "test", },
};
var category = new Category
{
    Name = "cat1",
    Items = new[]
    {
        item,
        new Item{ Name = "Item1", },
        new Item{ Name = "Item2", },
        new Item{ Name = "Item3", },
        new Item{ Name = "Item4", },
        new Item{ Name = "Item5", },
    },
    Attributes = new[]
    {
        attribute,
        new Attribute{ Name = "att1", },
        new Attribute{ Name = "att2", },
    }
};
db.Categories.Add(category);
db.SaveChanges();
var categories = db.Categories.ToList();

ItemAttribute is used to connect and store values.

And you're going to need to further adjust as per your requirements.

Upvotes: 1

Fendy
Fendy

Reputation: 4643

Ok so this question is basically about the data design.

First, I assume that the rule is:

  1. One item has one category
  2. One category has many attributes
  3. One item has many attributes associated with the category

For rule no.1, it is good enough in your design. (simplified example)

public class Category{
  public IEnumerable<Item> Items{get;set;}
}
public class Item{
  public Category Category{get;set;}
}

Its clear enough.

For rule no.2, I think you should make a CategoryAttribute class. It holds the relation between one to many Category and Attribute. Basically, CategoryAttribute is a master, whereas the children will be ItemAttribute.

public class Category{
  public IEnumerable<CategoryAttribute> CategoryAttributes{get;set;}
}
public class CategoryAttribute{
  public Category Category{get;set;}
  public string CategoryName{get;set;}
  public string DefaultValue{get;set;} // maybe a default value for specific 
                                       // attribute, but it's up to you

  public IEnumerable<ItemAttribute> ItemAttributes{get;set;}
}

The IEnumerable<ItemAttribute> is the one to many relation between category attribute and item attribute.

For rule no.3, the the ItemAttribute described in rule no.2 will be represented attribute owned by each item.

public class Item{
  public IEnumerable<ItemAttribute> ItemAttributes{get;set;}
}
public class ItemAttribute{
  public Item Item {get;set;} // it owned by one attribute
  public CategoryAttribute{get;set;} // it owned by one category attribute
}

I don't quite sure about how to represent relation or primary and foreign key in code first. Hopefully I can enhance my answer if needed (and if I able). But hopefully my illustration about relations and the class designs for each objects.

Upvotes: 1

Related Questions