comparison Tools/DocGen/Content/Doc/DataAccess/Introduction.htm @ 0:f990fcb411a9

Копия текущей версии из github
author cin
date Thu, 27 Mar 2014 21:46:09 +0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:f990fcb411a9
1 <% title # Introduction to abstract data accessors %>
2 <% order # 1 %>
3 <h4>Introduction</h4>
4
5 <p class='j'>
6 Before we start talking about the data accessors, let us create a few examples of
7 typical data accessor methods.
8 </p>
9 <p class='j'>
10 The following table contains three stored procedures and
11 three data access methods implementing the stored procedure calls.
12 </p>
13
14 <table width='100%'>
15 <tr><th>Stored procedure</th><th>Data access method</th></tr>
16 <tr><td colspan=2 style='padding-top:7px'>
17 The first stored procedure takes filter and page parameters and returns recordset from the Person table.
18 </td></tr>
19 <tr>
20 <td width=50% height=100%><% sql #
21 CREATE Procedure GetPersonListByName(
22 @firstName varchar(50),
23 @lastName varchar(50),
24 @pageNumber int,
25 @pageSize int)
26 AS
27 -- stored procedure implementation
28 --
29 %></td>
30 <td width=50%><% cs #
31 public List<Person> GetPersonListByName(
32 string firstName,
33 string lastName,
34 int pageNumber,
35 int pageSize)
36 {
37 // method implementation.
38 }
39 %></td>
40 </tr>
41 <tr><td colspan=2>
42 Second example will return single <b>Person</b> record by <b>id</b>.
43 </td></tr>
44 <tr>
45 <td width=50% height=100%><% sql #
46 CREATE Procedure GetPersonByID(@id int)
47 AS
48 -- stored procedure implementation
49 --
50 %>
51 </td>
52 <td width=50%><% cs #
53 public Person GetPersonByID(int id)
54 {
55 // method implementation.
56 }
57 %></td>
58 </tr>
59 <tr><td colspan=2>
60 The last example will delete a record from the database by <b>id</b>.
61 </td></tr>
62 <tr>
63 <td width=50% height=100%><% sql #
64 CREATE Procedure DeletePersonByID(@id int)
65 AS
66 -- stored procedure implementation
67 --
68 %></td>
69 <td width=50%><% cs #
70 public void DeletePersonByID(int id)
71 {
72 // method implementation.
73 }
74 %></td>
75 </tr>
76 </table>
77
78 <p class='j'>Now lets see what we can say if we compare the stored procedure and C# method signatures.
79 <ol>
80 <li>Stored procedure and method names match up.</li>
81 <li>Sequential order, method parameter types and names correspond to stored procedure parameters.</li>
82 <li>Methods' return values can give us an idea what <b>Execute</b> method we
83 should utilize and what object type has to be used to map data from recordset if needed.</li>
84 </ol>
85 </p>
86
87 <p class='j'>
88 As demonstrated above method definition contains all the information we need
89 to implement the method body. Actually, by defining the method signatures, we
90 completed the most intelligent part of data accessor development. The rest of
91 work is definitely a monkey's job. Honestly, I got bored of being just a coding
92 machine writing the same data access code over and over again, especially
93 understanding that this process can be automated.
94 </p>
95 <p class='j'>
96 This introduction shows how to avoid the implementation step of data access
97 development and how to reduce this routine process to the method declaration.
98 </p>
99
100 <h4>Abstract classes</h4>
101
102 <p class='j'>
103 Unfortunately, mainstream .NET languages still do not have a compile-time
104 transformation system like some functional or <a href="http://nemerle.org/Macros">hybrid</a> languages do.
105 All we have today is pre-compile- and run-time code generation.
106 </p>
107 <p class='j'>
108 This introduction concentrates on run-time code generation and its support by
109 <a href="http://www.bltoolkit.net/">Business Logic Toolkit for .NET</a>.
110 </p>
111 <p class='j'>
112 Let us step back and bring the methods from the previous examples together in one class.
113 Ideally, this data accessor class could look like the following:
114 </p>
115 <% cs #
116 using System;
117 using System.Collections.Generic;
118
119 public class PersonAccessor
120 {
121 public List<Person> GetPersonListByName(
122 string firstName, string lastName, int pageNumber, int pageSize);
123
124 public Person GetPersonByID (int id);
125 public void DeletePersonByID(int id);
126 }
127 %>
128
129 <p class='j'>
130 The bad news about this sample is that we cannot use such syntax as the
131 compiler expects the method's body implementation.
132 </p>
133 <p class='j'>
134 The good news is we can use abstract classes and methods that give us quite
135 similar, compilable source code.
136 </p>
137 <% cs #
138 using System;
139 using System.Collections.Generic;
140
141 public /*[a]*/abstract/*[/a]*/ class PersonAccessor
142 {
143 public /*[a]*/abstract/*[/a]*/ List<Person> GetPersonListByName(
144 string firstName, string lastName, int pageNumber, int pageSize);
145
146 public /*[a]*/abstract/*[/a]*/ Person GetPersonByID (int id);
147 public /*[a]*/abstract/*[/a]*/ void DeletePersonByID(int id);
148 }
149 %>
150
151 <p class='j'>This code is 100% valid and our next step is to make it workable.</p>
152
153 <H4>Abstract DataAccessor</H4>
154
155 <p class='j'>
156 Business Logic Toolkit provides the <b>DataAccessor</b> class, which is
157 used as a base class to develop data accessor classes. If we add
158 <b>DataAccessor</b> to our previous example, it will look like the following:
159 </p>
160 <% cs #
161 using System;
162 using System.Collections.Generic;
163
164 public abstract class PersonAccessor : /*[a]*/DataAccessor/*[/a]*/
165 {
166 public abstract List<Person> GetPersonListByName(
167 string firstName, string lastName, int pageNumber, int pageSize);
168
169 public abstract Person GetPersonByID (int id);
170 public abstract void DeletePersonByID(int id);
171 }
172 %>
173
174 <p class='j'>That's it! Now this class is complete and fully functional. The code below shows how to use it:</p>
175
176 <% cs #
177 using System;
178 using System.Collections.Generic;
179
180 using BLToolkit.Reflection;
181
182 namespace DataAccess
183 {
184 class Program
185 {
186 static void Main(string[] args)
187 {
188 PersonAccessor pa = /*[a]*/TypeAccessor/*[/a]*/<PersonAccessor>./*[a]*/CreateInstance/*[/a]*/();
189
190 List<Person> list = pa.GetPersonListByName("Crazy", "Frog", 0, 20);
191
192 foreach (Person p in list)
193 Console.Write("{0} {1}", p.FirstName, p.LastName);
194 }
195 }
196 }
197 %>
198
199 <p class='j'>
200 The only magic here is the <b>TypeAccessor.CreateInstance</b> method. First of all this
201 method creates a new class inherited from the <b>PersonAccessor</b> class and
202 then generates abstract method bodies depending on each method declaration.
203 If we wrote those methods manually, we could get something like the following:
204 </p>
205 <% cs #
206 using System;
207 using System.Collections.Generic;
208
209 using BLToolkit.Data;
210
211 namespace Example.BLToolkitExtension
212 {
213 public sealed class PersonAccessor : Example.PersonAccessor
214 {
215 public override List<Person> GetPersonListByName(
216 string firstName,
217 string lastName,
218 int pageNumber,
219 int pageSize)
220 {
221 using (DbManager db = GetDbManager())
222 {
223 return db
224 .SetSpCommand("GetPersonListByName",
225 db.Parameter("@firstName", firstName),
226 db.Parameter("@lastName", lastName),
227 db.Parameter("@pageNumber", pageNumber),
228 db.Parameter("@pageSize", pageSize))
229 .ExecuteList<Person>();
230 }
231 }
232
233 public override Person GetPersonByID(int id)
234 {
235 using (DbManager db = GetDbManager())
236 {
237 return db
238 .SetSpCommand("GetPersonByID", db.Parameter("@id", id))
239 .ExecuteObject<Person>();
240 }
241 }
242
243 public override void DeletePersonByID(int id)
244 {
245 using (DbManager db = GetDbManager())
246 {
247 db
248 .SetSpCommand("DeletePersonByID", db.Parameter("@id", id))
249 .ExecuteNonQuery();
250 }
251 }
252 }
253 }
254 %>
255
256 <p class='j'>
257 (The <a href='..\Data\index.htm'>DbManager</a> class is another BLToolkit class used for 'low-level' database access).
258 </p>
259
260 <p class="j">
261 Every part of the method declaration is important.
262 Method's return value specifies one of the Execute methods in the following way:
263 <table class='data'>
264 <tr><th>Return Type</th><th>Execute Method</th></tr>
265 <tr><td><i>IDataReader</i> interface</td><td>ExecuteReader</td></tr>
266 <tr><td>Subclass of <i>DataSet</i></td><td>ExecuteDataSet</td></tr>
267 <tr><td>Subclass of <i>DataTable</i></td><td>ExecuteDataTable</td></tr>
268 <tr><td>Class implementing the <i>IList</i> interface</td><td><a href="ExecuteList.htm">ExecuteList</a> or <a href="ExecuteList.htm">ExecuteScalarList</a></td></tr>
269 <tr><td>Class implementing the <i>IDictionary</i> interface</td><td><a href="ExecuteDictionary.htm">ExecuteDictionary</a> or <a href="ExecuteDictionary.htm">ExecuteScalarDictionary</a></td></tr>
270 <tr><td><i>void</i></td><td>ExecuteNonQuery</td></tr>
271 <tr><td><i>string</i>, <i>byte[]</i> or value type</td><td><a href="ExecuteScalar.htm">ExecuteScalar</a></td></tr>
272 <tr><td>In any other case</td><td><a href="ExecuteObject.htm">ExecuteObject</a></td></tr>
273 </table>
274 The method name explicitly defines the action name, which is converted to the stored procedure name.
275 Type, sequential order, and name of the method parameters are mapped to the command parameters.
276 Exceptions from this rule are:
277 <br>
278 <div style='margin:-10px 0px -0px -10px'><ul compact="compact">
279 <li>a parameter of <a href="../Data/index.htm"><i>DbManager</i></a> type. In this case generator uses provided <a href="../Data/index.htm"><i>DbManager</i></a> to call the command.</li>
280 <li>parameters decorated with attribute <a href='#FormatAttribute'>FormatAttribute</a>, <a href='#DestinationAttribute'>DestinationAttribute</a>.</li>
281 </ul></div>
282 </p>
283
284 <h4>Generating process control</h4>
285
286 <p class='j'>
287 The <b>PersonAccessor</b> class above is a very simple example and, of
288 course, it seems too ideal to be real. In real life, we need more flexibility
289 and more control over the generated code. BLToolkit contains a bunch of
290 attributes to control DataAccessor generation in addition to <b>DataAccessor</b> virtual members.
291 </p>
292
293 <h5>Method CreateDbManager</h5>
294
295 <% cs #
296 protected virtual DbManager CreateDbManager()
297 {
298 return new DbManager();
299 }
300 %>
301
302 <p class='j'>
303 By default, this method creates a new instance of <b>DbManager</b> that uses default database configuration.
304 You can change this behavior by overriding this method. For example:
305 </p>
306 <% cs #
307 public abstract class OracleDataAccessor : DataAccessor
308 {
309 protected override DbManager CreateDbManager()
310 {
311 return new DbManager("Oracle", "Production");
312 }
313 }
314 %>
315
316 <p class='j'>
317 This code will use the <i>Oracle</i> data provider and <i>Production</i> configuration.
318 </p>
319
320 <a name='GetDefaultSpName'></a><h5>Method GetDefaultSpName</h5>
321
322 <% cs #
323 protected virtual string GetDefaultSpName(string typeName, string actionName)
324 {
325 return typeName == null?
326 actionName:
327 string.Format("{0}_{1}", typeName, actionName);
328 }
329 %>
330
331 <p class='j'>
332 As I mentioned, the method name explicitly defines the so-called action name.
333 The final stored procedure name is created by the <b>GetDefaultSpName</b>
334 method. The default implementation uses the following naming convention:
335 </p>
336
337 <ul>
338 <li>
339 If type name is provided, the method constructs the stored proc name by
340 concatenating the type and action names. Thus, if the type name is "Person" and
341 the action name is "GetAll", the resulting sproc name will be "Person_GetAll".
342 </li>
343 <li>
344 If the type name is NOT provided, the stored procedure name will equal the
345 action name.</li>
346 </ul>
347
348 <p class='j'>
349 You can easily change this behavior. For example, for the naming convention "p_Person_GetAll",
350 the method implementation can be the following:
351 </p>
352 <% cs #
353 public abstract class MyBaseDataAccessor<T,A> : DataAccessor<T,A>
354 where A : DataAccessor<T,A>
355 {
356 protected override string GetDefaultSpName(string typeName, string actionName)
357 {
358 return string.Format("p_{0}_{1}", typeName, actionName);
359 }
360 }
361 %>
362
363 <h5>Method GetTableName</h5>
364
365 <% cs #
366 protected virtual string GetTableName(Type type)
367 {
368 // ...
369 return type.Name;
370 }
371 %>
372
373 <p class='j'>
374 By default, the table name is the associated object type name (<i>Person</i> in our examples).
375 There are two ways to associate an object type with an accessor. By providing generic parameter:
376 </p>
377 <% cs #
378 public abstract class PersonAccessor : DataAccessor<Person>
379 {
380 }
381 %>
382
383 <p class='j'>
384 And by the <b>ObjectType</b> attribute:
385 </p>
386 <% cs #
387 [ObjectType(typeof(Person))]
388 public abstract class PersonAccessor : DataAccessor
389 {
390 }
391 %>
392
393 <p class='j'>
394 If you want to have different table and type names in your application, you may override the <b>GetTableName</b> method:
395 </p>
396 <% cs #
397 public abstract class OracleDataAccessor<T,A> : DataAccessor<T,A>
398 where A : DataAccessor<T,A>
399 {
400 protected override string GetTableName(Type type)
401 {
402 return base.GetTableName(type).ToUpper();
403 }
404 }
405 %>
406
407 <h5>TableNameAttribute</h5>
408
409 <p class='j'>
410 Also, you can change the table name for a particular object type by decorating this object
411 with the <b>TableNameAttribute</b> attribute:
412 </p>
413 <% cs #
414 [TableName("PERSON")]
415 public class Person
416 {
417 public int ID;
418 public string FirstName;
419 public string LastName;
420 }
421 %>
422
423 <h5>ActionNameAttribute</h5>
424
425 <p class='j'>
426 This attribute allows changing the action name.
427 </p>
428 <% cs #
429 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
430 {
431 [ActionName("GetByID")]
432 protected abstract IDataReader GetByIDInternal(DbManager db, int id);
433
434 public Person GetByID(int id)
435 {
436 using (DbManager db = GetDbManager())
437 using (IDataReader rd = GetByIDInternal(db, id))
438 {
439 Person p = new Person();
440
441 // do something complicated.
442
443 return p;
444 }
445 }
446 }
447 %>
448
449 <h5>ActionSprocNameAttribute</h5>
450
451 <p class='j'>
452 This attribute associates the action name with a stored procedure name:
453 </p>
454 <% cs#
455 [ActionSprocName("Insert", sp_Person_Insert")]
456 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
457 {
458 public abstract void Insert(Person p);
459 }
460 %>
461
462 <p class='j'>
463 This attribute can be useful when you need to reassign a stored procedure name for a method defined in your base class.
464 </p>
465
466 <h5>SprocNameAttribute</h5>
467
468 <p class='j'>
469 The regular way to assign deferent from default sproc name for a method is the <b>SprocName</b> attribute.
470 </p>
471 <% cs #
472 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
473 {
474 [SprocName("sp_Person_Insert")]
475 public abstract void Insert(Person p);
476 }
477 %>
478
479 <a name='DestinationAttribute'></a><h5>DestinationAttribute</h5>
480
481 <p class='j'>
482 By default, the DataAccessor generator uses method's return value to determine which <i>Execute</i> method
483 should be used to perform the current operation.
484 The <b>DestinationAttribute</b> indicates that target object is a parameter decorated with this attribute:
485 </p>
486 <% cs #
487 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
488 {
489 public abstract void GetAll([Destination] List<Person> list);
490 }
491 %>
492
493 <h5>DirectionAttributes</h5>
494
495 <p class='j'>
496 <i>DataAccessor</i> generator can map provided business object to stored
497 procedure parameters. <b>Direction</b> attributes allow controlling this process more precisely.
498 </p>
499 <% cs #
500 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
501 {
502 public abstract void Insert(
503 [Direction.Output("ID"), Direction.Ignore("LastName")] Person person);
504 }
505 %>
506
507 <p class='j'>
508 In addition, BLToolkit provides two more direction attributes:
509 <b>Direction.InputOutputAttribute</b> and <b>Direction.ReturnValueAttribute</b>.
510 </p>
511
512 <h5>DiscoverParametersAttribute</h5>
513 <p class='j'>
514 By default, BLToolkit expects method parameter names to match stored procedure
515 parameter names. The sequential order of parameters is not important in this
516 case. This attribute enforces BLToolkit to retrieve parameter information from
517 the sproc and to assign method parameters in the order they go. Parameter names
518 are ignored.
519 </p>
520
521 <a name='FormatAttribute'></a><h5>FormatAttribute</h5>
522
523 <p class='j'>
524 This attribute indicates that the specified parameter is used to construct the stored procedure name or SQL statement:
525 </p>
526 <% cs #
527 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
528 {
529 [SqlQuery("SELECT {0} FROM {1} WHERE {2}")]
530 public abstract List<string> GetStrings(
531 [Format(0)] string fieldName,
532 [Format(1)] string tableName,
533 [Format(2)] string whereClause);
534 }
535 %>
536
537 <h5>IndexAttribute</h5>
538
539 <p class='j'>
540 If you want your method to return a dictionary, you will have to specify fields to build the dictionary key.
541 The Index attribute allows you to do that:
542 </p>
543 <% cs #
544 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
545 {
546 [SqlQuery("SELECT * FROM Person")]
547 [Index("ID")]
548 public abstract Dictionary<int, Person> SelectAll1();
549
550 [SqlQuery("SELECT * FROM Person")]
551 [Index("@PersonID", "LastName")]
552 public abstract Dictionary<CompoundValue, Person> SelectAll2();
553 }
554 %>
555 <p class='j'>Note: if your key has more than one field, the type of this key should be <b>CompoundValue</b>.</p>
556 <p class='j'>If the field name starts from '@' symbol, BLToolkit reads the field value from data source,
557 otherwise from an object property/field.</p>
558
559 <h5>ParamNameAttribute</h5>
560
561 <p class='j'>
562 By default, the method parameter name should match the stored procedure parameter name.
563 This attribute specifies the sproc parameter name explicitly.
564 </p>
565 <% cs #
566 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
567 {
568 public abstract Person SelectByName(
569 [ParamName("FirstName")] string name1,
570 [ParamName("@LastName")] string name2);
571 }
572 %>
573
574 <h5>ScalarFieldNameAttribute</h5>
575
576 <p class='j'>
577 If your method returns a dictionary of scalar values, you will have to specify the name or index of the field
578 used to populate the scalar list. The <b>ScalarFieldName</b> attribute allows you to do that:
579 </p>
580 <% cs #
581 public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
582 {
583 [SqlQuery("SELECT * FROM Person")]
584 [Index("@PersonID")]
585 [ScalarFieldName("FirstName")]
586 public abstract Dictionary<int, string> SelectAll1();
587
588 [SqlQuery("SELECT * FROM Person")]
589 [Index("PersonID", "LastName")]
590 [ScalarFieldName("FirstName")]
591 public abstract Dictionary<CompoundValue, string> SelectAll2();
592 }
593 %>
594
595 <h5>ScalarSourceAttribute</h5>
596
597 <p class='j'>
598 If a method returns a scalar value, this attribute can be used to specify how database returns this value.
599 The <b>ScalarSource</b> attribute take a parameter of the <b>ScalarSourceType</b> type:
600 </p>
601
602 <table class='data'>
603 <tr><th>ScalarSourceType</th><th>Description</th></tr>
604 <tr><td>DataReader</td><td>Calls the <b>DbManager.ExecuteReader</b> method, and then calls <b>IDataReader.GetValue</b> method to read the value.</td></tr>
605 <tr><td>OutputParameter</td><td>Calls the <b>DbManager.ExecuteNonQuery</b> method, and then reads value from the <b>IDbDataParameter.Value</b> property.</td></tr>
606 <tr><td>ReturnValue</td><td>Calls the <b>DbManager.ExecuteNonQuery</b> method, and then reads return value from command parameter collection.</td></tr>
607 <tr><td>AffectedRows</td><td>Calls the <b>DbManager.ExecuteNonQuery</b> method, and then returns its return value.</td></tr>
608 </table>
609
610 <h5>SqlQueryAttribute</h5>
611
612 <p class='j'>
613 This attribute allows specifying SQL statement.
614 </p>
615 <% cs # public abstract class PersonAccessor : DataAccessor<Person, PersonAccessor>
616 {
617 [SqlQuery("SELECT * FROM Person WHERE PersonID = @id")]
618 public abstract Person GetByID(int @id);
619 }
620 %>
621
622 <h4>Conclusion</h4>
623
624 <p class='j'>
625 I hope this brief tutorial demonstrates one of the simplest, quickest and
626 most low-maintenance ways to develop your data access layer. In addition, you
627 will get one more benefit, which is incredible object mapping performance. But
628 that is a topic we will discuss later.
629 </p>