Introduction
In Dynamic Data, when you have a database with an n-to-n or many-to-many relationship like this...
... the default DynamicData
setup will be three table edits, but wouldn't it be easier if in the Table1
edit, you get a multiple select input like so:

The attached zip file gives you that multiple select option for DynamicData
items.
Using the Code
I've only implemented it for the manytomany table without extra fields (so only the two foreign keys themselves) and these have to be of type int in my example.
The dynamic data setup does a lot for you but the setup for many to many relations isn't really useful (the extra table instead of editing it in the correct table). So if you add the following UIHint
attribute to your manytomany column...
[UIHint("MultipleSelect")]
... and put the multipleselect_edit
source in the FieldTemplates folder, you get the MultipleSelect
listbox for your many to many relations.
What I did was rely a bit heavily on reflection. To populate the listbox, I use the OnDataBinding
and OnPreRender
events.
protected override void OnDataBinding( EventArgs e )
{
base.OnDataBinding(e);
MetaForeignKeyColumn mfk = (MetaForeignKeyColumn)
ChildrenColumn.ChildTable.Columns.Single<MetaColumn>
(c => c is MetaForeignKeyColumn && c.Name !=
this.Column.Table.EntityType.Name);
if (mfk != null)
{
LinqDataSource lds = new LinqDataSource();
lds.ContextTypeName =
ChildrenColumn.ChildTable.DataContextType.FullName;
lds.TableName = mfk.ParentTable.Name;
_ddlItems.DataSource = lds;
_ddlItems.DataTextField = mfk.ParentTable.DisplayColumn.Name;
_ddlItems.DataValueField = mfk.ParentTable.PrimaryKeyColumns[0].Name;
_ddlItems.DataBind();
selectedValues.Clear();
if (!IsPostBack)
{
IList a = FieldValue as IList;
foreach (var b in a)
{
object val = ( (PropertyInfo)b.GetType().GetProperty
(mfk.ForeignKeyNames[0]) ).GetValue(b, null);
selectedValues.Add(val.ToString());
}
}
}
}
protected override void OnPreRender( EventArgs e )
{
foreach (string s in selectedValues)
{
if (_ddlItems.Items.FindByValue(s) != null)
{
_ddlItems.Items.FindByValue(s).Selected = true;
}
}
base.OnPreRender(e);
}
For some reason, setting the Selected = true
in the OnDataBinding
will not work, so I store the selectedValues
in a temp list and set the selected property in the PreRender
method.
To save all this, I use the ExtractValues
method with an IsPostback
check. Don't know if this is the correct place but it seems to work. What the save
method does is remove all entities for the ID that is being edited, and then add the items that are selected in the listbox.
protected override void ExtractValues( IOrderedDictionary dictionary )
{
if (IsPostBack)
{
MetaForeignKeyColumn mfkThis = (MetaForeignKeyColumn)
ChildrenColumn.ChildTable.Columns.Single<metacolumn />
(c => c is MetaForeignKeyColumn && c.Name ==
this.Column.Table.EntityType.Name);
MetaForeignKeyColumn mfk = (MetaForeignKeyColumn)
ChildrenColumn.ChildTable.Columns.Single<metacolumn />
(c => c is MetaForeignKeyColumn && c.Name !=
this.Column.Table.EntityType.Name);
if (mfk != null)
{
Type t = Type.GetType(Table.DataContextType.FullName);
System.Data.Linq.DataContext c =
Activator.CreateInstance(t) as System.Data.Linq.DataContext;
ITable o = c.GetType().GetProperty
(ChildrenColumn.ChildTable.Name).GetValue
(c, null) as ITable;
if (o != null)
{
int currentID = Convert.ToInt32
(Request.QueryString
[Table.PrimaryKeyColumns[0].Name]);
foreach (ListItem li in _ddlItems.Items)
{
object entity = Activator.CreateInstance
(o.ElementType);
o.ElementType.GetProperty
(mfkThis.ForeignKeyNames[0]).
SetValue(entity, currentID, null);
o.ElementType.GetProperty
(mfk.ForeignKeyNames[0]).
SetValue(entity, Convert.ToInt32
(li.Value), null);
o.Attach(entity);
o.DeleteOnSubmit(entity);
}
c.SubmitChanges();
foreach(ListItem li in _ddlItems.Items)
{
if (li.Selected)
{
object entity =
Activator.CreateInstance
(o.ElementType);
o.ElementType.GetProperty
(mfkThis.ForeignKeyNames[0]).
SetValue(entity, currentID,
null);
o.ElementType.GetProperty
(mfk.ForeignKeyNames[0]).
SetValue(entity,
Convert.ToInt32(li.Value),
null);
o.InsertOnSubmit(entity);
}
}
c.SubmitChanges();
}
}
}
}
As you can see, lots of reflection here but that makes it reusable for other types. I also submit the deletes and inserts separately as it didn't work in one go. The ID's of the primarykeys are converted to Int32
so for now only those work. But if you have other types, you can easily make a copy for that specific type if you want. Just change the conversion.