![]() |
Web Development »
ASP.NET Controls »
General
Intermediate
License: The Code Project Open License (CPOL)
DynamicData Many to Many FieldTemplateBy RoocEdit control for 'n to n' or 'many to many' table relations for DynamicData |
C# 3.0, ASP.NET
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
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);
//find the foreignkeycolumn not pointing to the current table
//so the foreignforeign table
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();
//when we set the ListItem.Selected to true here it doesn't work,
//so it is deferred to the
//prerender and temporarily stored in a string list
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)
{
//find the foreignkey pointing to this table
MetaForeignKeyColumn mfkThis = (MetaForeignKeyColumn)
ChildrenColumn.ChildTable.Columns.Single<metacolumn />
(c => c is MetaForeignKeyColumn && c.Name ==
this.Column.Table.EntityType.Name);
//find the foreignkey pointing to the other table
MetaForeignKeyColumn mfk = (MetaForeignKeyColumn)
ChildrenColumn.ChildTable.Columns.Single<metacolumn />
(c => c is MetaForeignKeyColumn && c.Name !=
this.Column.Table.EntityType.Name);
if (mfk != null)
{
//get a new context for the updates of the many to many table
Type t = Type.GetType(Table.DataContextType.FullName);
System.Data.Linq.DataContext c =
Activator.CreateInstance(t) as System.Data.Linq.DataContext;
//get the manytomany table object
ITable o = c.GetType().GetProperty
(ChildrenColumn.ChildTable.Name).GetValue
(c, null) as ITable;
if (o != null)
{
//the current edited id
int currentID = Convert.ToInt32
(Request.QueryString
[Table.PrimaryKeyColumns[0].Name]);
//first delete all items
foreach (ListItem li in _ddlItems.Items)
{
//create the entity to delete
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);
//has to be attached to be deleted
o.Attach(entity);
o.DeleteOnSubmit(entity);
}
c.SubmitChanges();
//then add selected items
foreach(ListItem li in _ddlItems.Items)
{
if (li.Selected)
{
//create entity to add
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.
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 15 Oct 2008 Editor: Deeksha Shenoy |
Copyright 2008 by Rooc Everything else Copyright © CodeProject, 1999-2009 Web22 | Advertise on the Code Project |