Click here to Skip to main content
15,885,278 members
Articles / Web Development / ASP.NET
Article

DynamicData Many to Many FieldTemplate

Rate me:
Please Sign up or sign in to vote.
4.00/5 (8 votes)
15 Oct 2008CPOL2 min read 37.2K   316   24   9
Edit control for 'n to n' or 'many to many' table relations for DynamicData

Introduction

In Dynamic Data, when you have a database with an n-to-n or many-to-many relationship like this...

tables.jpg

... 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:
editscreen2.JPG

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...

C#
[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.

C#
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.

C#
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. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Netherlands Netherlands
Developer since 1998 and mainly focused on web development. Currently employed as technical architect.

Comments and Discussions

 
QuestionMultipleSelect in insert mode and View mode Pin
Member 44640198-Dec-11 22:20
Member 44640198-Dec-11 22:20 
GeneralGreat Article. Very informative. Pin
SuperYeti42025-Feb-11 9:57
SuperYeti42025-Feb-11 9:57 
GeneralMy vote of 5 Pin
SuperYeti42025-Feb-11 9:51
SuperYeti42025-Feb-11 9:51 
AnswerNot working for me Pin
Member 361610713-May-09 2:31
Member 361610713-May-09 2:31 
AnswerRe: Not working for me Pin
Jorrit NL23-Jun-09 23:47
Jorrit NL23-Jun-09 23:47 
GeneralUsing field template Pin
renzo.pennetta16-Dec-08 3:36
renzo.pennetta16-Dec-08 3:36 
I use your great template but i have a problem in ExtraValues on row

Type t = Type.GetType(Table.DataContextType.FullName);

My problem is that after execution, t variable is null.

Thanks
Renzo
AnswerRe: Using field template Pin
Jorrit NL23-Jun-09 23:01
Jorrit NL23-Jun-09 23:01 
GeneralMany to many field template Pin
rajeshshaw24-Oct-08 18:35
rajeshshaw24-Oct-08 18:35 
GeneralRe: Many to many field template Pin
kapros30-Nov-08 3:39
kapros30-Nov-08 3:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.