Michael Minton
Michael Minton

Reputation: 4495

Proper Design for an Entity with a Variable Number of Properties

I am implementing a custom form designer. I'm now to the point where I want to persist the layout to the database.

Technology Limitations

Here is a design I have come up with:

Controls Table

ControlProperties Table

Control Class

public class Control
{
  public virtual int Id { get; set; }
  public virtual string Type { get; set; }
  public virtual Dictionary<string, string> Properties { get; set; }
}

I would then map this so I could do something like this to reload the layout:

var controls = GetListOfControlsUsingNHibernate();
foreach (var control in controls){
  var newControl = CreateControlFromType(control.Type);
  SetPropertiesViaTypeDescriptor(newControl, control.Properties);
  this.Controls.Add(newControl);
}

I have two questions.

  1. How would one map this using Fluent NHibernate?
  2. Is there a better approach that doesn't involve EAV and storing all values as strings?

Upvotes: 1

Views: 883

Answers (2)

Michael Minton
Michael Minton

Reputation: 4495

A bud on twitter pointed me in the right direction for mapping this using Fluent NHibernate:

First create a table named Properties to hold our properties:

╔═══════════╦══════════════╦═══════════════╗
║ ControlId ║ PropertyName ║ PropertyValue ║
╚═══════════╩══════════════╩═══════════════╝

Now change our mapping like this:

public class ControlMap : ClassMap<Control>
{
    public ControlMap()
    {
        Table("Controls");
        Id(x => x.Id);
        Map(x => x.Type);
        HasMany(x => x.Properties).Table("ControlProperties")
            .AsMap<string>(index => index.Column("PropertyName").Type<string>(),
                           element => element.Column("PropertyValue").Type<string>())
            .Cascade.All(); 
    }
}

For some background on the hbm xml behind this see Ayende's NHibernate Mapping Map.

Upvotes: 0

Mike Koder
Mike Koder

Reputation: 1928

Some alternative ways to do that.

One solution is to use inheritance

public abstract class Control
{
    // ...
}
public class TextBox : Control
{
    public virtual string Text { get; set; }
}
public class MaskedTextBox : TextBox
{
    public virtual string Mask { get; set; }
}

Another is to use different kinds of properties

public class Control
{
  public virtual ISet<Property> Properties { get; set; }
} 
public abstract class Property
{
  public virtual string Name { get; set; }
}
public class IntProperty : Property
{
  public virtual int Value { get; set; }
}
public class DecimalProperty : Property
{
  public virtual decimal Value { get; set; }
}
// ...

Yet another is to use interfaces

public abstract class Control
{
}
public class TextBox : Control, ITextControl
{
  public virtual string Text { get; set; }
}


public class ConcreteControlMap<T> : SubclassMap<T> where T : Control
{
  public ConcreteControlMap()
  {
    if(typeof(ITextControl).IsAssignableFrom(typeof(T)))
    {
      Map(c => ((ITextControl)c).Text);
    }
  }
}

Upvotes: 1

Related Questions