1+ using System ;
2+ using System . Collections . Concurrent ;
3+ using System . Collections . Generic ;
4+ using System . ComponentModel . Composition ;
5+ using System . Data ;
6+ using System . Data . Common ;
7+ using System . Data . SqlClient ;
8+ using System . Linq ;
9+ using System . Text ;
10+ using System . Threading ;
11+ using Simple . Data . Ado . Schema ;
12+
13+ namespace Simple . Data . Ado
14+ {
15+ [ Export ( "Ado" , typeof ( IAdapter ) ) ]
16+ internal class AdoAdapter : IAdapter , IAdapterWithRelation
17+ {
18+ private readonly IConnectionProvider _connectionProvider ;
19+ private readonly DatabaseSchema _schema ;
20+
21+ private readonly Lazy < ConcurrentDictionary < Tuple < string , string > , TableJoin > > _tableJoins =
22+ new Lazy < ConcurrentDictionary < Tuple < string , string > , TableJoin > > (
23+ ( ) => new ConcurrentDictionary < Tuple < string , string > , TableJoin > ( ) , LazyThreadSafetyMode . ExecutionAndPublication
24+ ) ;
25+
26+ public AdoAdapter ( IConnectionProvider connectionProvider )
27+ {
28+ _connectionProvider = connectionProvider ;
29+ _schema = DatabaseSchema . Get ( _connectionProvider ) ;
30+ }
31+
32+ public IEnumerable < IDictionary < string , object > > Find ( string tableName , SimpleExpression criteria )
33+ {
34+ if ( criteria == null ) return FindAll ( tableName ) ;
35+
36+ var commandBuilder = new FindHelper ( _schema ) . GetFindByCommand ( tableName , criteria ) ;
37+ return ExecuteQuery ( commandBuilder ) ;
38+ }
39+
40+ public IDictionary < string , object > Insert ( string tableName , IDictionary < string , object > data )
41+ {
42+ var table = _schema . FindTable ( tableName ) ;
43+
44+ string columnList =
45+ data . Keys . Select ( s => table . FindColumn ( s ) . QuotedName ) . Aggregate ( ( agg , next ) => agg + "," + next ) ;
46+ string valueList = data . Keys . Select ( s => "?" ) . Aggregate ( ( agg , next ) => agg + "," + next ) ;
47+
48+ string insertSql = "insert into " + table . QuotedName + " (" + columnList + ") values (" + valueList + ")" ;
49+
50+ var identityColumn = table . Columns . FirstOrDefault ( col => col . IsIdentity ) ;
51+
52+ if ( identityColumn != null )
53+ {
54+ insertSql += "; select * from " + table . QuotedName + " where " + identityColumn . QuotedName +
55+ " = scope_identity()" ;
56+ return ExecuteSingletonQuery ( insertSql , data . Values . ToArray ( ) ) ;
57+ }
58+
59+ Execute ( insertSql , data . Values . ToArray ( ) ) ;
60+ return null ;
61+ }
62+
63+ public int Update ( string tableName , IDictionary < string , object > data , SimpleExpression criteria )
64+ {
65+ var commandBuilder = new UpdateHelper ( _schema ) . GetUpdateCommand ( tableName , data , criteria ) ;
66+ return Execute ( commandBuilder ) ;
67+ }
68+
69+ /// <summary>
70+ /// Deletes from the specified table.
71+ /// </summary>
72+ /// <param name="tableName">Name of the table.</param>
73+ /// <param name="criteria">The expression to use as criteria for the delete operation.</param>
74+ /// <returns>The number of records which were deleted.</returns>
75+ public int Delete ( string tableName , SimpleExpression criteria )
76+ {
77+ var commandBuilder = new DeleteHelper ( _schema ) . GetDeleteCommand ( tableName , criteria ) ;
78+ return Execute ( commandBuilder ) ;
79+ }
80+
81+ /// <summary>
82+ /// Gets the names of the fields which comprise the unique identifier for the specified table.
83+ /// </summary>
84+ /// <param name="tableName">Name of the table.</param>
85+ /// <returns>A list of field names; an empty list if no key is defined.</returns>
86+ public IEnumerable < string > GetKeyFieldNames ( string tableName )
87+ {
88+ return _schema . FindTable ( tableName ) . PrimaryKey . AsEnumerable ( ) ;
89+ }
90+
91+ private IEnumerable < IDictionary < string , object > > FindAll ( string tableName )
92+ {
93+ return ExecuteQuery ( "select * from " + _schema . FindTable ( tableName ) . ActualName ) ;
94+ }
95+
96+ private IEnumerable < IDictionary < string , object > > ExecuteQuery ( ICommandBuilder commandBuilder )
97+ {
98+ using ( var connection = CreateConnection ( ) )
99+ {
100+ using ( var command = commandBuilder . GetCommand ( connection ) )
101+ {
102+ return TryExecuteQuery ( connection , command ) ;
103+ }
104+ }
105+ }
106+
107+ private IEnumerable < IDictionary < string , object > > ExecuteQuery ( string sql , params object [ ] values )
108+ {
109+ using ( var connection = CreateConnection ( ) )
110+ {
111+ using ( var command = CommandHelper . Create ( connection , sql , values ) )
112+ {
113+ return TryExecuteQuery ( connection , command ) ;
114+ }
115+ }
116+ }
117+
118+ private static IEnumerable < IDictionary < string , object > > TryExecuteQuery ( DbConnection connection , IDbCommand command )
119+ {
120+ try
121+ {
122+ connection . Open ( ) ;
123+
124+ return command . ExecuteReader ( ) . ToDictionaries ( ) ;
125+ }
126+ catch ( DbException ex )
127+ {
128+ throw new AdoAdapterException ( ex . Message , command ) ;
129+ }
130+ }
131+
132+ internal IDictionary < string , object > ExecuteSingletonQuery ( string sql , params object [ ] values )
133+ {
134+ using ( var connection = CreateConnection ( ) )
135+ {
136+ using ( var command = CommandHelper . Create ( connection , sql , values . ToArray ( ) ) )
137+ {
138+ try
139+ {
140+ connection . Open ( ) ;
141+ using ( var reader = command . ExecuteReader ( ) )
142+ {
143+ if ( reader . Read ( ) )
144+ {
145+ return reader . ToDictionary ( ) ;
146+ }
147+ }
148+ }
149+ catch ( DbException ex )
150+ {
151+ throw new AdoAdapterException ( ex . Message , command ) ;
152+ }
153+ }
154+ }
155+
156+ return null ;
157+ }
158+
159+ internal int Execute ( string sql , params object [ ] values )
160+ {
161+ using ( var connection = CreateConnection ( ) )
162+ {
163+ using ( var command = CommandHelper . Create ( connection , sql , values . ToArray ( ) ) )
164+ {
165+ return TryExecute ( connection , command ) ;
166+ }
167+ }
168+ }
169+
170+ private int Execute ( ICommandBuilder commandBuilder )
171+ {
172+ using ( var connection = CreateConnection ( ) )
173+ {
174+ using ( var command = commandBuilder . GetCommand ( connection ) )
175+ {
176+ return TryExecute ( connection , command ) ;
177+ }
178+ }
179+ }
180+
181+ private static int TryExecute ( DbConnection connection , IDbCommand command )
182+ {
183+ try
184+ {
185+ connection . Open ( ) ;
186+ return command . ExecuteNonQuery ( ) ;
187+ }
188+ catch ( DbException ex )
189+ {
190+ throw new AdoAdapterException ( ex . Message , command ) ;
191+ }
192+ }
193+
194+ internal DbConnection CreateConnection ( )
195+ {
196+ return _connectionProvider . CreateConnection ( ) ;
197+ }
198+
199+ internal DatabaseSchema GetSchema ( )
200+ {
201+ return DatabaseSchema . Get ( _connectionProvider ) ;
202+ }
203+
204+ /// <summary>
205+ /// Determines whether a relation is valid.
206+ /// </summary>
207+ /// <param name="tableName">Name of the known table.</param>
208+ /// <param name="relatedTableName">Name of the table to test.</param>
209+ /// <returns>
210+ /// <c>true</c> if there is a valid relation; otherwise, <c>false</c>.
211+ /// </returns>
212+ public bool IsValidRelation ( string tableName , string relatedTableName )
213+ {
214+ return TryJoin ( tableName , relatedTableName ) != null ;
215+ }
216+
217+ /// <summary>
218+ /// Finds data from a "table" related to the specified "table".
219+ /// </summary>
220+ /// <param name="tableName">Name of the table.</param>
221+ /// <param name="row"></param>
222+ /// <param name="relatedTableName"></param>
223+ /// <returns>The list of records matching the criteria. If no records are found, return an empty list.</returns>
224+ /// <remarks>When implementing the <see cref="IAdapter"/> interface, if relationships are not possible, throw a <see cref="NotSupportedException"/>.</remarks>
225+ public IEnumerable < IDictionary < string , object > > FindRelated ( string tableName , IDictionary < string , object > row , string relatedTableName )
226+ {
227+ var join = TryJoin ( tableName , relatedTableName ) ;
228+ if ( join == null ) throw new AdoAdapterException ( "Could not resolve relationship." ) ;
229+
230+ return join . Master == _schema . FindTable ( tableName ) ? GetDetail ( row , join ) : GetMaster ( row , join ) ;
231+ }
232+
233+ private TableJoin TryJoin ( string tableName , string relatedTableName )
234+ {
235+ return _tableJoins . Value . GetOrAdd ( Tuple . Create ( tableName , relatedTableName ) ,
236+ t => TryCreateJoin ( t . Item1 , t . Item2 ) ) ;
237+ }
238+
239+ private TableJoin TryCreateJoin ( string tableName , string relatedTableName )
240+ {
241+ return TryMasterJoin ( tableName , relatedTableName ) ?? TryDetailJoin ( tableName , relatedTableName ) ;
242+ }
243+
244+ private TableJoin TryMasterJoin ( string tableName , string relatedTableName )
245+ {
246+ return _schema . FindTable ( tableName ) . GetMaster ( relatedTableName ) ;
247+ }
248+
249+ private TableJoin TryDetailJoin ( string tableName , string relatedTableName )
250+ {
251+ return _schema . FindTable ( tableName ) . GetDetail ( relatedTableName ) ;
252+ }
253+
254+ private IEnumerable < IDictionary < string , object > > GetMaster ( IDictionary < string , object > row , TableJoin masterJoin )
255+ {
256+ var criteria = new Dictionary < string , object > { { masterJoin . MasterColumn . ActualName , row [ masterJoin . DetailColumn . HomogenizedName ] } } ;
257+ yield return Find ( masterJoin . Master . ActualName ,
258+ ExpressionHelper . CriteriaDictionaryToExpression ( masterJoin . Master . ActualName ,
259+ criteria ) ) . FirstOrDefault ( ) ;
260+ }
261+
262+ private IEnumerable < IDictionary < string , object > > GetDetail ( IDictionary < string , object > row , TableJoin join )
263+ {
264+ var criteria = new Dictionary < string , object > { { join . DetailColumn . ActualName , row [ join . MasterColumn . HomogenizedName ] } } ;
265+ return Find ( join . Detail . ActualName ,
266+ ExpressionHelper . CriteriaDictionaryToExpression ( join . Detail . ActualName ,
267+ criteria ) ) ;
268+ }
269+ }
270+ }
0 commit comments