Skip to content

Commit 3bf2b4f

Browse files
authored
support the descriptor (component-model) API (#1216)
1 parent f0da900 commit 3bf2b4f

15 files changed

+696
-9
lines changed

Dapper.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.Stro
4444
EndProject
4545
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}"
4646
EndProject
47+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIBindingTest", "UIBindingTest\UIBindingTest.csproj", "{FC8502EB-DF49-469B-B752-466EE61CC5C4}"
48+
EndProject
4749
Global
4850
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4951
Debug|Any CPU = Debug|Any CPU
@@ -90,6 +92,10 @@ Global
9092
{F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU
9193
{F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU
9294
{F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU
95+
{FC8502EB-DF49-469B-B752-466EE61CC5C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
96+
{FC8502EB-DF49-469B-B752-466EE61CC5C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
97+
{FC8502EB-DF49-469B-B752-466EE61CC5C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
98+
{FC8502EB-DF49-469B-B752-466EE61CC5C4}.Release|Any CPU.Build.0 = Release|Any CPU
9399
EndGlobalSection
94100
GlobalSection(SolutionProperties) = preSolution
95101
HideSolutionNode = FALSE
@@ -105,6 +111,7 @@ Global
105111
{8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}
106112
{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}
107113
{F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262}
114+
{FC8502EB-DF49-469B-B752-466EE61CC5C4} = {568BD46C-1C65-4D44-870C-12CD72563262}
108115
EndGlobalSection
109116
GlobalSection(ExtensibilityGlobals) = postSolution
110117
SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#if !NETSTANDARD1_3 // needs the component-model API
2+
using System;
3+
using System.Collections.Generic;
4+
using System.ComponentModel;
5+
6+
namespace Dapper
7+
{
8+
public static partial class SqlMapper
9+
{
10+
[TypeDescriptionProvider(typeof(DapperRowTypeDescriptionProvider))]
11+
private sealed partial class DapperRow
12+
{
13+
private sealed class DapperRowTypeDescriptionProvider : TypeDescriptionProvider
14+
{
15+
public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance)
16+
=> new DapperRowTypeDescriptor(instance);
17+
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
18+
=> new DapperRowTypeDescriptor(instance);
19+
}
20+
21+
//// in theory we could implement this for zero-length results to bind; would require
22+
//// additional changes, though, to capture a table even when no rows - so not currently provided
23+
//internal sealed class DapperRowList : List<DapperRow>, ITypedList
24+
//{
25+
// private readonly DapperTable _table;
26+
// public DapperRowList(DapperTable table) { _table = table; }
27+
// PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
28+
// {
29+
// if (listAccessors != null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty;
30+
31+
// return DapperRowTypeDescriptor.GetProperties(_table);
32+
// }
33+
34+
// string ITypedList.GetListName(PropertyDescriptor[] listAccessors) => null;
35+
//}
36+
37+
private sealed class DapperRowTypeDescriptor : ICustomTypeDescriptor
38+
{
39+
private readonly DapperRow _row;
40+
public DapperRowTypeDescriptor(object instance)
41+
=> _row = (DapperRow)instance;
42+
43+
AttributeCollection ICustomTypeDescriptor.GetAttributes()
44+
=> AttributeCollection.Empty;
45+
46+
string ICustomTypeDescriptor.GetClassName() => typeof(DapperRow).FullName;
47+
48+
string ICustomTypeDescriptor.GetComponentName() => null;
49+
50+
private static readonly TypeConverter s_converter = new ExpandableObjectConverter();
51+
TypeConverter ICustomTypeDescriptor.GetConverter() => s_converter;
52+
53+
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => null;
54+
55+
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => null;
56+
57+
object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => null;
58+
59+
EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty;
60+
61+
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty;
62+
63+
internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row);
64+
internal static PropertyDescriptorCollection GetProperties(DapperTable table, IDictionary<string,object> row = null)
65+
{
66+
string[] names = table?.FieldNames;
67+
if (names == null || names.Length == 0) return PropertyDescriptorCollection.Empty;
68+
var arr = new PropertyDescriptor[names.Length];
69+
for (int i = 0; i < arr.Length; i++)
70+
{
71+
var type = row != null && row.TryGetValue(names[i], out var value) && value != null
72+
? value.GetType() : typeof(object);
73+
arr[i] = new RowBoundPropertyDescriptor(type, names[i]);
74+
}
75+
return new PropertyDescriptorCollection(arr, true);
76+
}
77+
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => GetProperties(_row);
78+
79+
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => GetProperties(_row);
80+
81+
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => _row;
82+
}
83+
84+
private sealed class RowBoundPropertyDescriptor : PropertyDescriptor
85+
{
86+
private readonly Type _type;
87+
public RowBoundPropertyDescriptor(Type type, string name) : base(name, null)
88+
=> _type = type;
89+
90+
public override bool CanResetValue(object component) => false;
91+
public override void ResetValue(object component) => throw new NotSupportedException();
92+
93+
public override bool IsReadOnly => false;
94+
public override bool ShouldSerializeValue(object component) => true;
95+
public override Type ComponentType => typeof(DapperRow);
96+
public override Type PropertyType => _type;
97+
public override object GetValue(object component)
98+
=> ((IDictionary<string, object>)component).TryGetValue(Name, out var val) ? val : null;
99+
public override void SetValue(object component, object value)
100+
=> ((IDictionary<string, object>)component)[Name] = value;
101+
}
102+
}
103+
}
104+
}
105+
#endif

Dapper/SqlMapper.DapperRow.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ namespace Dapper
77
{
88
public static partial class SqlMapper
99
{
10-
private sealed class DapperRow
11-
: System.Dynamic.IDynamicMetaObjectProvider
12-
, IDictionary<string, object>
10+
private sealed partial class DapperRow
11+
: IDictionary<string, object>
1312
, IReadOnlyDictionary<string, object>
1413
{
1514
private readonly DapperTable table;
@@ -78,12 +77,6 @@ public override string ToString()
7877
return sb.Append('}').__ToStringRecycle();
7978
}
8079

81-
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
82-
System.Linq.Expressions.Expression parameter)
83-
{
84-
return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
85-
}
86-
8780
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
8881
{
8982
var names = table.FieldNames;

Dapper/SqlMapper.DapperRowMetaObject.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ namespace Dapper
55
{
66
public static partial class SqlMapper
77
{
8+
private sealed partial class DapperRow : System.Dynamic.IDynamicMetaObjectProvider
9+
{
10+
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
11+
System.Linq.Expressions.Expression parameter)
12+
{
13+
return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
14+
}
15+
}
16+
817
private sealed class DapperRowMetaObject : System.Dynamic.DynamicMetaObject
918
{
1019
private static readonly MethodInfo getValueMethod = typeof(IDictionary<string, object>).GetProperty("Item").GetGetMethod();
@@ -79,6 +88,13 @@ public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.Se
7988

8089
return callMethod;
8190
}
91+
92+
static readonly string[] s_nixKeys = new string[0];
93+
public override IEnumerable<string> GetDynamicMemberNames()
94+
{
95+
if(HasValue && Value is IDictionary<string, object> lookup) return lookup.Keys;
96+
return s_nixKeys;
97+
}
8298
}
8399
}
84100
}

UIBindingTest/App.config

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
5+
</startup>
6+
</configuration>

UIBindingTest/BindingForm.Designer.cs

Lines changed: 63 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UIBindingTest/BindingForm.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Data.SqlClient;
2+
using System.Windows.Forms;
3+
using Dapper;
4+
5+
namespace UIBindingTest
6+
{
7+
public partial class BindingForm : Form
8+
{
9+
public BindingForm()
10+
{
11+
InitializeComponent();
12+
13+
using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated Security=SSPI"))
14+
{
15+
mainGrid.DataSource = conn.Query("select * from sys.objects").AsList();
16+
}
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)