Chapter 9
Object-Relational Structures Design Patterns
Introduction
To organize the object-relational structures, the design patterns of this category can be divided into the following ten main sections:
Identity field: Tries to maintain the data identity between the object and the database record by storing the value of the database identity column(s) inside the object.
Foreign key mapping: Tries to map the foreign key relationship between tables in the database as a relationship between objects.
Association table mapping: Tries to map the many-to-many relationship between tables in the database as relationships between objects.
Dependent mapping: Attempts to make a class responsible for managing child class communication with the database.
Embedded value: It can map an object to several table columns related to another object.
Serialized LOB: It can convert a graph of objects into a LOB (Large Object) and store it in a database field.
Single table inheritance: It mapped the classes that participated in the inheritance structure to a table.
Class table inheritance: Tries to map each class to one table in the database in an inheritance structure.
Concrete table inheritance: Tries to map each non-abstract class to one table in the database.
Inheritance mappers: Tries to organize the various Mappers to manage the inheritance structure.
Structure
In this chapter, we will cover the following topics:
Object-relational structures design patterns
Identity field
Foreign key mapping
Association table mapping
Dependent mapping
Embedded value
Serialized LOB
Single table inheritance
Class table inheritance
Concrete table inheritance
Inheritance mappers
Objectives
In this chapter, you will learn how to map different relationships between database tables (such as one-to-many and many-to-many relationships) to classes. In this chapter, you will learn how to map an inheritance structure to tables in a database. Also, you will learn how to work with dependent data and map them with the data in the database tables.
Object-relational structures design patterns
There are important points to pay attention to when mapping relationships. First, to associate an object with a record in a table, it is often necessary to insert a primary key value into the object. For this, using the identity field design pattern will be useful. Now, one table may be related to another through a foreign key relationship, and the foreign key mapping design pattern can be used to implement this relationship between objects.
The connection between objects may be very long or circular. Therefore, when loading one of the objects, it is necessary to fetch the data related to the dependent object and repeat this process continuously. As mentioned earlier, a lazy load design pattern can be useful in this section. But there is a situation where there is a one-to-many relationship between A and B, and B's data can only be used through A, and we have nothing to do with them outside of A. In this case, A can be considered the owner of B and benefit from the dependent mapping design pattern. This type of communication becomes complicated when the one-to-many relationship becomes a many-to-many relationship. In this case, you can use the association table mapping design pattern.
As mentioned earlier, identity field design patterns can be used for communication between objects. But some data should not be mapped to specific tables, and value objects are among these data. The embedded value design pattern is a better way to store value objects. Sometimes the amount of data that must be stored in this method can be very large. To solve this problem, you can use the serialized LOB design pattern.
The preceding points were related to composition-type relationships. Sometimes there may be an inheritance relationship between classes. This type of relationship does not exist in relational databases, so there are three options for implementing this type of relationship: one table for the entire inheritance structure (Single table inheritance design pattern), one table for each non-abstract class (Concrete table inheritance design pattern), and finally one table for each class (Class table inheritance design pattern).
Identity field
Name:
Identity field
Classification:
Object-relational structures design patterns
Also known as:
---
Intent:
This design pattern tries to maintain the data identity between the object and the database record by storing the value of the database identity column(s) inside the object.
Motivation, Structure, Implementation, and Sample code:
Suppose a requirement is raised and it is necessary to retrieve the data in the books table or to define a new book. Suppose the following structure exists for the books table in the database:
BookId
Title
PublishDate
1
Design Patterns in .NET
2021-10-01
2
Programming in C#.NET
2021-11-01
Table 9.1: Book table in the database
When using the identity field design pattern, we store the identity value of the books table in the object of the Book class. This identifier value must have an important feature, and that is that its value must be unique. The reason for this requirement is that we must be able to uniquely identify each record and each object with the help of this value. The best candidate for an identifier will often be the primary key value since the primary key always has a unique value. With this assumption, the code for the Book class will be as follows:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public DateOnly PublishDate { get; set; }
}
As it is clear from the code related to the Book class, the BookId property will be used to store the value of the BookId column of the database. Based on this structure, the following objects can be considered:
Book book1 = new()
{
BookId = 1,
Title = "Design Patterns in .NET",
PublishDate = new DateOnly(2021, 10, 01)
};
In the preceding code, the value of the BookId property of the book1 object is equal to 1, so this object refers to the record with the value of the bookId=1 column in the Books table.
To insert a new book, you can do the following:
public bool Create(Book book)
{
book.BookId = GetNewBookId();
return Save(book);
}
private int GetNewBookId()
{
/*
* In this section, the desired method to generate the
* new value of the key must be implemented.
*/
…
}
According to the type of key (simple or composite) and the selected method to generate the new value of the key, the GetNewBookId method should be implemented.
Notes:
Key selection is very important in this design pattern. There are two main options for choosing a key. Using a meaningful key and using a meaningless key:
Meaningful key: It is also called natural key; its value is always unique and has a meaning for the user. For example, the ISBN is a unique value for a book and has a meaning for the end user.
Meaningless key: It is also called a generated key. It is a key whose value is unique but has no meaning for the user. For example, the primary key column, which is automatically and sometimes randomly set, is often a meaningless key for the user.
Choosing between these two types of keys is important. Although the meaningful key has a unique value, it has an important shortcoming. It is expected that the key value is immutable and unique. Immutability is not necessarily true for a meaningful key because a mistake may occur when entering its value, and the user wants to edit it. In this case, the immutability condition for the key will be violated. The value of the key can be changed until the changes are sent to the database, and the database has not checked the uniqueness of the value; the object can be used. This can affect reliability. For small scenarios or special situations, a meaningful key can be useful.
The keys can be defined as simple or composite according to their nature. For example, in the database design, the bookId column may be available and has the role of the primary key. In this case, the key is simple. In another case, for example, for the sales information table, storeId and bookId columns together maybe keys. In this case, it is a composite key. Managing uniqueness and immutability constraints for composite keys will be important and sensitive. To implement the composite key, using the key class can be helpful. In any case, regarding the key, two types of operations should be considered. The first operation compares the values, and the second is related to generating the next value. In the following code, you can see how to implement the key class:
public override bool Equals(object? obj)
{
if (obj is not CompoundKey)
return false;
CompoundKey other = (CompoundKey)obj;
if (keys.Length != other.keys.Length)
return false;
for (int i = 0; i < keys.Length; i++) if (!keys[i].Equals(other.keys[i])) return false; return true; } The preceding method shows the value comparison process for the composite key. As it is clear in the code, inside the for loop, the value of all the members of the composite key is compared one by one. According to the requirement, the preceding implementation may be varied and different to compare the values of the keys. Also, to make a composite key, you can proceed as follows: public class CompoundKey { private object[] keys; public CompoundKey(object[] keys) => this.keys = keys;
}
Different constructors with different input types can be defined to create composite keys with specified members or values, and the preceding code can be improved.
Keys can be on two levels in terms of uniqueness. The first level may require that the key value in a table is always unique, and the second level may require that the key value be unique in the entire database. It is usually very common to use the first level, while the advantage of the second level will be that an identity map will be needed.
Depending on the data type selected for the key, we may reach the maximum value when using it. In this case, to generate the next value, the value of the key used for the deleted records can be used. This method will be provided that the deletion has occurred physically. 64-bit data types or combination keys can reduce the possibility of reaching the maximum allowed value. Usually, the mistake is using data types such as GUID. Although the value of these data types is always unique, they occupy a lot of space. Therefore, before using them, the necessary checks must be done.
When creating a new object and storing its value in the database, generating a new value for the key will be necessary. Different ways to generate a new value for the key include setting the key column in the database to auto-generate, using the GUID for the key, or implementing your mechanism:
Using the auto-generate method is simpler. However, it may cause problems in the record and object mapping process. Because until the record is inserted in the database and then the same data is not retrieved from the database, the value assigned to the key will be unknown. An alternative method for auto generate in Microsoft SQL server database is to use sequence, which plays the role of database level counter.
Using the GUID method, as mentioned earlier, may cause performance problems.
To implement a custom mechanism, methods such as using the MAX function to find the last value of the key may also be used. This method may be useful for small tables with a lower simultaneous change rate. It will not be a good method for larger tables or tables with a high simultaneous change rate because, while having a negative effect on performance, it may be due to not having the appropriate isolation level for the transaction. Multiple objects get a common value for the key. Another way to implement this method is to use a key table. Using this table, you can store the next value for the key in this table and read the next value when you need to create a new object. It is better to access the key table through a separate transaction so that the rest of the transactions that need the key do not wait for the lock to be released. The problem with this method is that if the insert transaction is canceled for any reason, the generated key value will not be usable.
An alternative method for using the identity field design pattern is the identity map design pattern.
Consequences: Advantages
Using this design pattern, a common feature can be placed between the database records and the object created in the memory. The object and its corresponding record in the database can be identified through that feature. Without using this design pattern, in databases, the primary key value is usually used to identify the uniqueness of a record. In .NET, objects have their unique identifier, but there is no relationship between these two values.
Consequences: Disadvantages
If the data type of the key is a string, generating a new key or comparing the values of the keys can be time-consuming or heavy operations.
If the new value of the key is duplicated for any reason, if this value is not sent to the database, it cannot be noticed that it is duplicated. Obviously, in this case, performance will suffer.
Applicability:
When there is a need to map records in the database to objects, using this design pattern can be useful. This mapping usually uses domain models or row data gateway design patterns.
Related patterns:
Some of the following design patterns are not related to identity field design patterns, but to implement this design pattern, checking the following design patterns will be useful:
Identity map
Domain model
Row data gateway
Foreign key mapping
Name:
Foreign key mapping
Classification:
Object-relational structures design patterns
Also known as:
---
Intent:
Using this design pattern, the foreign key relationship between tables in the database is mapped as a relationship between objects.
Motivation, Structure, Implementation, and Sample code:
Continuing the example presented in the identity field design pattern, we assume we need additional information for each book, located in a separate table next to the book object. The relationship between the books and the bookDetail is one-to-one. One-to-one relationship means that each book has only one record in the bookDetail table, and each record in the bookDetail table corresponds to one book. In other words, the following relationship can be imagined between the books and bookDetail tables:
Figure%209.1.png
Figure 9.1: Relationship between books and bookDetail tables
As it is clear from Figure 9.1, ER diagram, the books table has a primary key column called bookId, and the bookDetail table also has a foreign key column called bookId. In the bookDetail table, the bookId column also plays the role of the primary key. To implement this relationship, you can do as follows:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public DateOnly PublishDate { get; set; }
private BookDetail detail;
public BookDetail Detail
{
get => detail;
set
{
detail = value;
if (detail.Book == null)
detail.Book = this;//’this’ refers to the current instance of the class }
}
}
public class BookDetail
{
public int BookId { get; set; }
public byte[] File { get; set; }
public Book Book { get; set; }
}
As it is clear from the preceding code, each Book and BookDetail class has designed the identity field design pattern. The BookId property in these two classes is mapped to the bookId column in the books and bookDetail tables. Also, the Book class has a property called Detail related to the BookDetail class. The BookDetail class also has a property called Book and is related to the Book class. This relationship maps the database's foreign key to the relationship between objects. In the preceding code, a one-to-one relationship is implemented in a bidirectional way, which means that the corresponding BookDetail object can be accessed through a Book object and vice versa. Removing one of the Book or Detail properties is enough to implement this relationship unidirectionally.
In this one-to-one implementation, inserting, editing, and deleting will be simple operations. To insert or edit a record in the supplementary information table of the book, create the BookDetail object and send it to the database. The only important point in constructing this object is to comply with the principles related to the foreign key constraint.
Next, suppose that we need to implement the comments that the readers have given to the book. For this purpose, the comments table is designed in the database. There is a one-to-many relationship between the books and comments tables, which means that there can be several comments for one book, and on the other hand, each comment is only specific to one book. With these explanations, the ER diagram can be imagined as follows:
Figure%209.2.png
Figure 9.2: Relationship between books and comments tables
As shown in the Figure 9.2 ER diagram, the comments table has a column called bookId, which plays the role of a foreign key and is connected to the bookId column in the books table. Also, as determined by the relationship type, several records may be in the comments table for each bookId. With these explanations, the following code can be considered to implement this relationship:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public DateOnly PublishDate { get; set; }
private ICollection
public ICollection
{
get => comments;
set
{
comments = value;
foreach (var item in comments.Where(x => x.Book == null))
item.Book = this; //’this’ refers to the current instance of the class
}
}
private BookDetail detail;
public BookDetail Detail
{
get => detail;
set
{
detail = value;
if (detail.Book == null)
detail.Book = this;
}
}
}
public class Comment
{
public int CommentId { get; set; }
public string Text { get; set; }
private Book book;
public Book Book
{
get => book;
set
{
book = value;
if (!book.Comments.Contains(this))
{
book.Comments.Add(this);
}
}
}
}
As it is clear in the preceding code, the Book class has an ICollection property called Comments, through which you can see all the comments related to that book. Also, the Comment class has a property called Book, which can connect the comment to its corresponding book. This method will also represent the implementation of one-to-many relationships. Remember that the preceding implementation is bidirectional, and it is possible to implement this relationship unidirectionally, as discussed in BookDetail.
In implementing the one-to-many relationship. There are three different ways to implement the editing process:
Delete and insert: This method is the easiest way to implement the editing operation. In this method, all the comments related to the book are removed from the comments table, and all the comments in the Book object are inserted into the comments table. This method has various problems, including that the comments related to the book may not have changed. In this method, we will have to delete and insert them again. Also, the bigger problem with this method is that this method can only be used if the comments have not been used elsewhere.
Reconciliation: Can be done in two ways:
Reconciliation with the data in the database: In this method, the comments related to the book should be read from the database and compared with the current comments. Any comment in the database but not in the list of current comments should be deleted. Any comment in the current list and not among the comments read from the database should be inserted.
Reconciliation with previously fetched data: In this method, storing the book comments fetched in the beginning will be necessary. This method will work better than the previous method because there is no need to refer to the database, but the data reliability will be low. The comparison method in this method will be the same as the previous method.
Using a bidirectional relationship instead of a unidirectional relationship: This method, also called the Back Pointer, allows us to access the related book through each comment instead of accessing the list of comments through the book. In this case, we will have a one-to-one relationship with the book for each comment, and as the one-to-one relationship explained earlier, the editing process can be implemented.
For the delete operation, the process will be almost similar. Deleting a comment often means that the comment is completely deleted, which naturally should also be deleted from the table. Or, it means that the comment has been transferred from one book to another, in which case, the comment record will also be edited when processing that book. In some cases, the comment may not be related to a specific book. In such cases, the value of the bookId column in the comments table will be equal to null (This method is only possible if the corresponding foreign key constraint allows storing null in the foreign key column table).
Notes:
If the relation is of immutable type, then the editing operation will be meaningless. For example, comments sent for a book cannot be edited or transferred to another book.
Using the UnitOfWork design pattern to implement editing, inserting, or deleting scenarios can be very helpful.
In implementing this design pattern, the occurrence of a loop is very likely. You can benefit from the lazy load or identity map design pattern to prevent stack overflow and properly manage the loop.
In the one-to-many relationship, you can use join to write a query to receive the list of book comments instead of sending several queries to the database. By sending one query to the database, all the information is required to be received. In this method, the number of remote calls or references to the database will be reduced, and the efficiency will be significantly improved.
Consequences: Advantages
Using this design pattern, the first principle of normalization can be observed.
Consequences: Disadvantages
Implementing a many-to-many relationship using this design pattern is impossible because, according to the first principle of normalization, the value of several foreign keys cannot be stored in one field.
Applicability:
To implement relationships between tables, this design pattern can be used. The association table mapping design pattern should be used to implement many-to-many relationships.
Related patterns:
Some of the following design patterns are not related to foreign key mapping design patterns, but to implement this design pattern, checking the following design patterns will be useful:
Identity field
Unit of work
Lazy load
Identity map
Association table mapping
Association table mapping
Name:
Association table mapping
Classification:
Object-relational structures design patterns
Also known as:
---
Intent:
Using this design pattern, the many-to-many relationship between tables in the database is mapped as relationships between objects.
Motivation, Structure, Implementation, and Sample code:
In the continuation of the example presented for the foreign key mapping design pattern, let us assume that we need to have the information of the authors of the book along with the book object. The book information is in the tblBooks table, and the author information is in the authors table. Each author may have written several books and may have several authors. With these explanations, we can conclude that the relationship between the book and the author is many-to-many, and the foreign key mapping design model cannot implement this model. The foreign key mapping model is useful when one side of the relationship is a single value (such as one-to-one or one-to-many relationships). Now we are facing a relationship where both sides are multi-valued.
In the database world, there is a famous solution to solve this problem, and that is to use an intermediate table. The task of this intermediate table is to convert the many-to-many relationship into two one-to-many relationships. This intermediate table is sometimes called a link table. The name of this intermediate table in the given example is bookAuthors. The relationship between the tblBooks and the authors can be imagined as follows:
Figure%209.3.png
Figure 9.3: Relationship between tblBooks and authors tables
As shown in Figure 9.3, ER diagram, the tblBooks table has a one-to-many relationship with the bookAuthors table. Also, the authors table has the same one-to-many relationship as the bookAuthors table. The primary key in the preceding link table is a composite key, and the primary key is defined from the combination of the bookId and authorId columns. Now that the many-to-many relationship problem has been solved in the database, you can use the association table mapping design pattern to solve this problem at the model level and also fix the .NET codes:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public DateOnly PublishDate { get; set; }
public ICollection
}
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection
}
As it is clear in the preceding code, the Book class is connected to the Author class through the Authors property, and the Author class is also connected to the Book class through the Books property. The important thing about this design pattern is that, as seen in the preceding code, the link table designed in the database has no related objects.
Suppose we need to get the list of authors of a specific book. There are two ways to read the information in this structure:
Refer to the link table and get different authorIds for the desired book and then refer to the authors table and get the information of each author. If the data is not in the memory and it is necessary to refer to the database for each step, we will face a performance problem, and otherwise, if the data is in the memory, this process can be simple.
Using Join in the database and receiving information at once. In this case, going back and forth to the database will be reduced, which will reduce the number of remote calls and improve efficiency; on the other hand, complexity will be created to map the results on the models.
To perform the editing operation, considering that the records of the link table are often not related to another table, therefore, the editing operation can be easily implemented in the form of deletion and insertion operations (Contrary to what we saw in the foreign key mapping design pattern). If the records of the link table are related to other tables, then the same tasks as mentioned in the foreign key mapping design pattern should be performed to perform editing operations.
Notes:
Sometimes the link table, apart from maintaining the values of the foreign keys, also carries additional information. In this case, the link table will have its class. For example, suppose we need to know how much each author contributed to writing a book, and the contribution value is the data that should be stored in the bookAuthors table.
Consequences: Advantages
Using this design pattern, you can connect two different tables without adding a new column to the table and simply by creating an intermediate table.
Consequences: Disadvantages
This design pattern can be used to implement other types of communication, but it is not recommended due to the complexity of this task.
Applicability:
To implement many-to-many relationships, this design pattern can be used.
Related patterns:
Some of the following design patterns are not related to the association table mapping design pattern, but to implement this design pattern, checking the following design patterns will be useful:
Foreign key mapping
Dependent mapping
Name:
Dependent mapping
Classification:
Object-relational structures design patterns
Also known as:
---
Intent:
Using this design pattern, a class becomes responsible for managing the communication of the child class with the database.
Motivation, Structure, Implementation, and Sample code:
Following the examples presented in the previous sections, suppose we need to have the contact information and address of the authors. The relationship between the contact information table or contactInfo and the authors table is one-to-many. In this way, each author can have several contact information. For example, one author can provide a phone number, mobile phone number, and work address in the form of contact information, and another author can provide a home address and work address.
To implement this scenario, it is very important to pay attention to one point: the identity and nature of the contact information, as the author is meaningful. In other words, the contact information cannot be accessed and worked by itself, and all transactions must be done through the author. This structure is exactly what the dependent mapping design pattern seeks to provide.
In this design pattern, we face a series of dependent elements and an owner for that dependent element. All transactions necessary for fetching, inserting, editing, and deleting the dependent element will happen only through its owner. Now, with the explanations provided, consider the following codes to implement the requirements:
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection
public void UpdateAuthor(Author author)
{
this.FirstName = author.FirstName;
this.LastName = author.LastName;
UpdateContactInfo(author);
}
// Editing contact information includes deleting and re-inserting it
public void UpdateContactInfo(Author author)
{
RemoveAllContactInfo();
foreach (var item in author.ContactInfo)
{
AddContactInfo(item);
}
}
/*
All communication of contact information with the database
happens through the owner
*/
public void AddContactInfo(ContactInfo contactInfo)
=> ContactInfo.Add(contactInfo);
public void RemoveContactInfo(ContactInfo contactInfo)
=> ContactInfo.Remove(contactInfo);
public void RemoveAllContactInfo() => ContactInfo.Clear();
}
public class ContactInfo
{
public ContactType ContactType { get; set; }
public string Value { get; set; }
}
public enum ContactType : short
{
HomePhone,
MobilePhone,
HomeAddress,
WorkAddress
}
The preceding code shows that the Author class has a property called ContactInfo. This property maintains a set of contact information for the author. This class provides the necessary methods to insert and delete the author's contact information (according to the requirement, different implementations may be provided for these methods, and the code sample is provided here for simplicity). On the other hand, the ContactInfo class only has ContactType for the type of contact information (mobile phone number, home number, residential address, and work address) and Value to store the amount of contact information. As it is clear in the preceding code, acting through the Author class is necessary to communicate with the data related to ContactInfo in the database. Therefore, the Author class has the same role as the owner, and the ContactInfo class has the role of the dependent element. In implementing the UpdateContactInfo method in the Author class, the editing operation can be deleted and reinserted by using this design pattern and assuming the dependent element to be immutable.
Notes:
Each dependent element must have only one owner.
When using active record or row data gateway design patterns, the class related to the dependent element will not contain the necessary code to communicate with the database. As mentioned before, these communications should be established only through the owner, and these codes will be placed in the owner's mapper class when using the data mapper design pattern. Finally, there will be no dependent element when using the table data gateway design pattern.
Usually, when reading the owner's information from the database, related elements are also retrieved. If reading the information on dependent elements is costly, or the information related to dependent elements is of little use, the lazy load design pattern can be used to improve efficiency.
The dependent element does not have an identity field, so it is not possible to place it in the identity map; so, like other models, the method that returns a specific record by receiving the key value is meaningless for this element. All these works will be in the scope of the owner's duties.
A dependent element can be the owner of another dependent element. In this case, the main owner has the same duty towards the second dependent element as the first. Therefore, all the communications of the second dependent element with the database must happen through the owner of the first dependent element.
The foreign key of any table should not be inside the dependent element table unless the owner of both tables is the same.
Using UML, the relationship between the owner and the dependent element can be displayed through the Composition relationship.
The use of this design pattern requires two necessary conditions:
A dependent element must have only one owner.
No reference should be from any object other than the owner to the dependent element.
As much as possible, the communication graph using this design pattern should not be too long. The longer and larger this graph is, the more complicated the operation of managing the dependent element through the owner will be.
If the UnitofWork design pattern is used, it is not recommended to use this design pattern. Because UnitofWork does not recognize dependent elements, which can damage the principles of the dependent mapper design pattern.
Consequences: Advantages
If we consider the dependent element immutable, then the change process in the dependent element and how to manage it will be very simple. Editing can be simulated by removing all dependent elements and inserting them again.
Consequences: Disadvantages
Using this design pattern, it will be difficult to track changes. For example, whenever the dependent element changes, this change should be notified to the owner so that the owner can prepare and send the changes to be applied to the database.
Applicability:
This design pattern can be used when an object is available through another object. Often, the type of this relationship is one-to-many.
Using this design pattern will be useful when there is a one-to-many relationship between two models, but their connection is implemented unidirectionally.
Related patterns:
Some of the following design patterns are not related to dependent mapping design patterns, but to implement this design pattern, checking the following design patterns will be useful:
Active record
Row data gateway
Data mapper
Table data gateway
Lazy load
Identity map
UnitofWork
Embedded value
Name:
Embedded value
Classification:
Object-relational structures design patterns
Also known as:
Aggregate mapping or composer
Intent:
Using this design pattern, one object can be mapped to several table columns related to another object.
Motivation, Structure, Implementation, and Sample code:
Following the previous examples, suppose we need to store the information of the publisher of the book. The book publisher has information such as name and address. The address of the book publisher has been designed and implemented as a value object in which the address consists of country, province, city, and street sections.
To implement this structure, one way is to consider two different classes for the publisher and their address, which have a one-to-one relationship with each other, and then map each of these classes to two different tables in the database and connect through the foreign key. If we do the design in this way, then the following structure can be considered for them:
Figure%209.4.png
Figure 9.4: Mapping Publisher and Address to separate tables
In Figure 9.4, two classes, Publisher and Address, are on the left side, each of which is mapped separately to the Publisher and PublisherAddress tables. The problem with this design is that we must use Join whenever we need to fetch and display the publisher's address next to their name. PublisherId also needs to be repeated in the Publisher and PublisherAddress classes, which leads to unnecessary duplicate data. The following code shows the sample query required to retrieve the name and address of the publisher with publisherId=1 using SQL:
SELECT p.Name, a.Country, a.Province, a.City, a.Street
FROM Publisher as p
INNER JOIN PublisherAddress as a
ON p.publisherId = a.publisherId
Where a.publisherId = 1
The preceding design and implementation have no terms of functionality, but the problem in the preceding design and implementation is performance. The embedded value design pattern helps to solve this performance problem by mapping one object to several columns of a table related to another object. In fact, by using this design pattern, the preceding model changes as follows:
Figure%209.5.png
Figure 9.5: Mapping Publisher and Address to one table
In Figure 9.5, two classes, Publisher and Address, are mapped to one table. Now, if we want to rewrite the previously presented query for this model, we will reach the following SQL code:
SELECT Name, Country, Province, City, Street
FROM Publisher WHERE PublisherId = 1
The preceding query will perform better than the previous query due to no need to use JOIN.
With these explanations, the proposed requirements can be implemented as follows:
public class Publisher
{
public int PublisherId { get; set; }
public string Name { get; set; }
public Address AddressInfo { get; set; }
public async Task
{
var reader = await new SqlCommand($"" +
$"SELECT * " +
$"FROM Publisher " +
$"WHERE ID = {publisherId}", DB.Connection).ExecuteReaderAsync();
reader.Read();
Publisher result = new()
{
PublisherId = (int)reader["Id"],
Name = (string)reader["Name"],
AddressInfo = new Address
{
Country = (string)reader["Country"],
Province = (string)reader["Province"],
City = (string)reader["City"],
Street = (string)reader["Street"]
}
};
return result;
}
}
public class Address
{
public string Country { get; set; }
public string Province { get; set; }
public string City { get; set; }
public string Street { get; set; }
}
As it is clear in the preceding code, the Address class does not have any method or code to communicate with the database, and all the communication of this class with the database happens through its owner, that is, the Publisher class. In other words, AddressInfo in the Publisher class is a value object. As it is evident in the Address class, this value object has no identity, and its identity finds its meaning through the owner. This is precisely why the Address class is not mapped to a separate table but only to a series of special columns of its owner object.
Notes:
Understanding when two objects can be stored in the embedded value format is a point that should be considered. The important point in making this decision is to pay attention to data retrieval and storage processes. The question must be answered whether the dependent element is useful outside the owner's domain. If yes, then should we probably store both in the same table (note, it is said probably because this decision is subject to other conditions as well), or should the dependent element data also be fetched whenever the owner data is fetched?
This design pattern is usually used for one-to-one relationships.
This design pattern is a special mode of the dependent mapping design pattern.
Consequences: Advantages
Using this design pattern, you can easily work with value objects.
Consequences: Disadvantages
This design pattern is only suitable for simple dependent elements. If the dependent elements are complex, this design pattern will not be suitable, and the serialized LOB design pattern should be used.
Applicability:
When we are faced with a table that has many columns, but in terms of modeling and object formation, we need to divide this table into different models and put methods specific to each class; in this case, this design pattern can be useful.
Related patterns:
Some of the following design patterns are not related to embedded value design patterns, but to implement this design pattern, checking the following design patterns will be useful:
Dependent mapping
Serialized LOB
Serialized LOB
Name:
Serialized LOB
Classification:
Object-relational structures design patterns
Also known as:
---
Intent:
Using this design pattern, a graph of objects can be converted into a Large Object (LOB) and stored in a database field.
Motivation, Structure, Implementation, and Sample code:
In the continuation of the previous example for the embedded value design pattern, suppose a requirement is raised and, in its format, it is necessary to select and save the publisher's field of activity from thematic categories. The subject category has a tree structure as follows:
General
Novel
Science fiction
Motivational
Academic
Computer field
Software
Hardware
Industries
Law
International law
English language
Children
Teenagers
Adults
For example, for publisher X, we should be able to create and store the following tree:
Academic
Computer field
Software
English language
Children
Teenagers
Adults
There are different ways to implement this scenario, but one of the simplest ways is to convert each publication tree into a LOB and store the LOB result in a table field. In other words, the following code can be imagined to implement the above requirement:
public class Publisher
{
public int PublisherId { get; set; }
public string Name { get; set; }
public ICollection
}
public class Category
{
public string Title { get; set; }
public ICollection
}
As you can see, the Publisher class has a property called Categories, where the LOB result is supposed to be stored. The structure of the Publisher table in the database is as follows:
CREATE TABLE Publisher(
PublisherId INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(200) NOT NULL,
Categories NVARCHAR(MAX)
)
As it is clear from the table structure, the Categories column in the Publisher table has a string data type, so the Categories property in the Publisher class must be somehow converted into a string. Although there was no requirement for this column to be a string, it could be binary. With this explanation, there are different methods to generate LOB, which are:
Binary LOB (BLOB): The object is converted into binary content and stored. The advantage of this method is the simplicity of production and storage and its small volume. The drawback of this method is that the result is not human-readable (of course, this may be an advantage in terms of security).
Character LOB (CLOB): The object is converted into a string and stored. The advantage of this method is that the result is human-readable, and it is also possible to run queries on them in the database without the need to convert them into objects again. The drawback of this method is that the content is bulky compared to the BLOB method.
After choosing a method to convert the object to LOB, the mechanisms for converting an object to LOB and LOB to an object must be implemented. We assume we want to use the CLOB method in the above example. Therefore, the following methods can be considered to insert or convert LOB into the expected object:
public async Task
{
return (await new SqlCommand($"" +
$"INSERT INTO Publisher " +
$"(Name, Categories) VALUES " +
$"N'{publisher.Name}',
N'{JsonConvert.SerializeObject(publisher.Categories)}'",
DB.Connection).ExecuteNonQueryAsync()) > 0;
}
public ICollection
=> JsonConvert.DeserializeObject
As shown in the AddAsync method, when inserting publisher information, the information related to Categories is converted into a string with a JSON structure and then saved. Also, using the GetCategories method, the CLOB stored in the table is converted into the expected object. Newtonsoft Json.NET1 tool was used for these conversions.
Notes:
Sometimes it may be necessary to place the LOB in another table instead of the main table. This dependent table usually has two columns, one for ID and another for LOB.
In this design pattern, you should always be sure that the LOB content will be available only through its owner.
Suppose there is a separate database or data warehouse for reporting. In that case, the content of the LOB can be converted into a suitable table in the destination data source so that we do not face performance problems in reporting.
By using compression methods, it is possible to reduce the space consumption of CLOB. In this case, the effect of the most important drawback of this method is slightly reduced.
Consequences: Advantages
Converting the less used part of the object to LOB makes it possible to save the used space, improving efficiency.
Consequences: Disadvantages
When using this design pattern, if data outside the LOB has a reference inside the LOB, it will cause this design pattern to cause problems, including performance, for the program.
Some database management systems do not allow queries in XML or JSON formats; therefore, it is difficult to work with this data type on the database side. (Microsoft SQL Server allows writing queries on both XML and JSON).
Applicability:
When we are faced with an object where some of its data are not used much, then by using this design pattern, we can convert that part of the object into a LOB and store it in a field.
Related patterns:
Some of the following design patterns are not related to serialized LOB design patterns, but to implement this design pattern, checking the following design patterns will be useful:
Embedded Value
Single table inheritance
Name:
Single table Inheritance
Classification:
Object-relational structures design patterns
Also known as:
Table per hierarchy or Discriminated mapping
Intent:
Using this design pattern, you can map the classes that participated in the inheritance structure to one table.
Motivation, Structure, Implementation, and Sample code:
Following the example in the previous sections, suppose that authors who work with publishers collaborate in two formats. Some authors work hourly, and others work with the publisher on a fixed monthly salary. Now a requirement has been raised, and we want to have the financial information of the authors as well. Specifically, the design that can be considered for this requirement can be seen in the form of the following class diagram:
Figure%209.6.png
Figure 9.6: Relation between the Author and his/her collaboration format
According to Figure 9.6 class diagram, authors are of two types. Authors who are paid monthly, whose monthly receipt information is in the MonthlyPaidAuthor class, and authors who are paid hourly and their hourly working information is in the HourlyPaidAuthor class. The Figure 9.6 class diagram can be implemented as follows:
public abstract class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class HourlyPaidAuthor : Author
{
public int HourlyPaid { get; set; }
public int HoursWorked { get; set; }
}
public class MonthlyPaidAuthor : Author
{
public int Salary { get; set; }
}
As the preceding code shows, the HourlyPaidAuthor and MonthlyPaidAuthor classes inherit from the Author class. Now, this inheritance structure can be mapped to a table in the database so that the authors' data can be retrieved and worked with without using JOIN. Therefore, for the whole preceding structure, the following table can be considered:
CREATE TABLE Author(
AuthorId INT PRIMARY KEY IDENTITY(1,1),
FirstName NVARCHAR(200) NOT NULL,
LastName NVARCHAR(200) NOT NULL,
HourlyPaid INT,
HoursWorked INT,
Salary INT
)
As shown in the preceding T-SQL code, all the properties in the inheritance structure are presented as a table in the database. The important point is that there should be a way to distinguish between the records related to HourlyPaidAuthor and MonthlyPaidAuthor classes. To create this feature, we need to use a separate column. This column contains information about the corresponding class, such as the class name or a unique code for each class. This column is called Discriminator. Therefore, with these explanations, the structure of the table changes as follows:
CREATE TABLE Author(
AuthorId INT PRIMARY KEY IDENTITY(1,1),
FirstName NVARCHAR(200) NOT NULL,
LastName NVARCHAR(200) NOT NULL,
HourlyPaid INT,
HoursWorked INT,
Salary INT,
Type VARCHAR(100) NOT NULL
)
In the preceding table, the Type column plays the role of Discriminator. When the information of authors with monthly income is placed in this table, then the values of HourlyPaid and HoursWorked columns will be empty. The same rule is also true for records related to hourly paid authors, and for them, the value of the Salary column will be empty.
The following code shows how to retrieve records for each class:
public abstract class Author{
private readonly string discriminator;
protected Author(string discriminator) => this.discriminator = discriminator;
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
protected async Task
=> await new SqlCommand($"" +
$"SELECT * " +
$"FROM Authors " +
$"WHERE Discriminator=N'{discriminator}'",
DB.Connection).ExecuteReaderAsync();
}
public class HourlyPaidAuthor : Author{
public HourlyPaidAuthor() : base(nameof(HourlyPaidAuthor)) { }
public int HourlyPaid { get; set; }
public int HoursWorked { get; set; }
public async Task> ReadAllAsync(){
var result = new List
var reader = await base.GetAllAsync();
while (reader.Read())
result.Add(new HourlyPaidAuthor
{
AuthorId = (int)reader["AuthorId"],
FirstName = (string)reader["FirstName"],
LastName = (string)reader["LastName"],
HourlyPaid = (int)reader["HourlyPaid"],
HoursWorked = (int)reader["HoursWorked"]
});
return result;
}
}
public class MonthlyPaidAuthor : Author{
public MonthlyPaidAuthor() : base(nameof(MonthlyPaidAuthor)) { }
public int Salary { get; set; }
public async Task> ReadAllAsync(){
var result = new List
var reader = await base.GetAllAsync();
while (reader.Read())
result.Add(new MonthlyPaidAuthor
{
AuthorId = (int)reader["AuthorId"],
FirstName = (string)reader["FirstName"],
LastName = (string)reader["LastName"],
Salary = (int)reader["Salary"]
});
return result;
}
}
As specified in the GetAllAsync method in the Author class, the discriminator filter is applied when sending a request to retrieve records. To implement this section, you may take help from other design patterns, but here we have tried to display the code in the simplest possible way. Also, in the preceding code, the Author class is defined abstractly.
Notes:
Discriminator content can be either a class name or a unique code. If the class name is used, its advantage is the ease of use, and its drawback is the large space that the string content occupies in the database. Also, if the code is used, the advantage is that it takes up less space, and its drawback is the need for a converter to find out which class should be instantiated for each code.
When inserting or retrieving records, records related to a specific class can be accessed by providing the Discriminator value.
To simplify the implementation, the inheritance mapper design pattern can be used.
Consequences: Advantages
Using this design pattern, we only deal with one table in the database, simplifying the work in the database.
Since different data are in the same table, there is no need to use Join to retrieve and read records.
It is possible to refactor the code without changing the structure of the database. You can move various features in the inheritance tree without changing the database structure.
Consequences: Disadvantages
All the fields in this type of structure are not necessarily related to each other, and this can increase the complexity when using the table.
Using this design pattern, some columns in the table will have empty content, which can negatively impact performance and used space. Of course, many database management systems today provide different methods for compressing empty spaces.
In this case, we will face a very large and bulky table. Increasing the volume and size of the table means the presence of different indexes and the placement of multiple locks on the table, which can have a negative effect on performance.
Applicability:
When we face an inheritance structure and want to store the data related to the entire structure in a table, we can use this design pattern.
Related patterns:
Some of the following design patterns are not related to the Single Table Inheritance design pattern, but to implement this design pattern, checking the following design patterns will be useful:
Inheritance mapper
Class table inheritance
Concrete table inheritance
Class table inheritance
Name:
Class table inheritance
Classification:
Object-relational structures design patterns
Also known as:
Root-leaf mapping
Intent:
Using this design pattern, in an inheritance structure, each class is mapped to a separate table in the database.
Motivation, Structure, Implementation, and Sample code:
Suppose that in the example provided in the single table inheritance section, we want to put the information of each class in its table. Suppose we need to store the author's general information in the authors table, financial information related to authors with hourly payments in the hourlyPaidAuthor table, and monthly payments in the monthlyPaidAuthor table.
With these explanations, the codes presented in the single table inheritance design pattern can be rewritten as follows:
public class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public async Task
$"SELECT * " +
$"FROM authors", DB.Connection).ExecuteReaderAsync();
}
public class HourlyPaidAuthor : Author
{
public int HourlyPaid { get; set; }
public int HoursWorked { get; set; }
public async Task
$"SELECT * " +
$"FROM authors AS a " +
$"INNER JOIN hourlyPaidAuthor as h" +
$"ON a.AuthorId = h.AuthorId", DB.Connection).ExecuteReaderAsync();
}
public class MonthlyPaidAuthor : Author
{
public int Salary { get; set; }
public async Task
=> await new SqlCommand($"" +
$"SELECT * " +
$"FROM authors AS a " +
$"INNER JOIN monthlyPaidAuthor as m" +
$"ON a.AuthorId = m.AuthorId", DB.Connection).ExecuteReaderAsync();
}
As it is clear in the preceding code, the Author class has the GetAllAsync method. By using this method, the author's general information is fetched from the authors table. If we need to retrieve the information of hourly authors, we can use the GetAllAsync method available in the HourlyPaidAuthor class. In this class, Join is required and used to retrieve the author's information.
Notes:
There is no requirement to map the entire inheritance structure with one method, and you can benefit from different mapping patterns when dealing with an inheritance structure.
The important point for this design pattern will be how to read data from the database. For this, you can send a specific query for each table, which will cause performance problems. Another method would be to use Join to read the data. In using Join, the Outer Join method may also be used depending on the conditions.
Another point that should be paid attention to is how different records in different tables are related. There are different ways for this relationship. One of these ways is to use the common value in the primary key field. Another method is that each class has its primary key value and has the foreign key of the parent table.
To simplify the implementation, the Inheritance Mapper design pattern can be used.
Consequences: Advantages
By using this design pattern, there will be no waste of space in the database.
The connection between the domain model and the tables is clear.
Consequences: Disadvantages
To retrieve the data, it is necessary to read it using Join from several tables or send several queries.
Any refactoring that changes the location of properties in the inheritance structure will change the table structure.
Tables that are close to the root of the inheritance tree can cause problems due to frequent references.
Applicability:
When we face an inheritance structure, and we want to store the data related to each class in the inheritance structure in its table, we can use this design pattern.
Related patterns:
Some of the following design patterns are not related to the class table inheritance design pattern, but to implement this design pattern, checking the following design patterns will be useful:
Single table inheritance
Inheritance mapper
Concrete table inheritance
Concrete table inheritance
Name:
Concrete table inheritance
Classification:
Object-relational structures design patterns
Also known as:
Leaf level inheritance
Intent:
Using this design pattern, each non-abstract class is mapped to a table in the database.
Motivation, Structure, Implementation, and Sample code:
In the example provided for the class table inheritance design pattern, suppose a requirement is raised so that we have two types of authors, the first type is paid hourly, and the second type is paid monthly. And we want to put the data of each type in its tables. Regardless of the type of author, each author has a series of common characteristics with other authors. Including first and last names, and so on. Because of this feature, usually, the following structure can be considered:
Figure%209.7.png
Figure 9.7: Relation between the Author and his/her collaboration format
As it is clear in the Figure 9.7 class diagram, the Author class is of an abstract type, and the two inherent classes MonthlyPaidAuthor and HourlyPaidAuthor are inherited from it. Using the concrete table inheritance design pattern, each intrinsic class is mapped to a table in the database. This means that in the database, we will encounter two tables monthlyPaidAuthor and hourlyPaidAuthor tables, each of which will contain columns related to themselves and their parent. Therefore, we will have the following tables in the database:
CREATE TABLE monthlyPaidAuthor(
AuthorId INT PRIMARY KEY IDENTITY(1,1),
FirstName NVARCHAR(200) NOT NULL,
LastName NVARCHAR(200) NOT NULL,
Salary INT
)
CREATE TABLE hourlyPaidAuthor(
AuthorId INT PRIMARY KEY IDENTITY(1,1),
FirstName NVARCHAR(200) NOT NULL,
LastName NVARCHAR(200) NOT NULL,
HourlyPaid INT,
HoursWorked INT
)
As can be seen, all the properties related to the parent class are repeated in each of the tables. With the preceding explanation, the following code can be imagined:
public abstract class Author
{
public int AuthorId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public abstract Task
}
public class HourlyPaidAuthor : Author
{
public int HourlyPaid { get; set; }
public int HoursWorked { get; set; }
public override async Task
=> await new SqlCommand($"" +
$"SELECT * " +
$"FROM hourlyPaidAuthor", DB.Connection).ExecuteReaderAsync();
}
public class MonthlyPaidAuthor : Author
{
public int Salary { get; set; }
public override async Task
=> await new SqlCommand($"" +
$"SELECT * " +
$"FROM monthlyPaidAuthor", DB.Connection).ExecuteReaderAsync();
}
As it is clear in the preceding code, the Author class is defined abstractly and will not be mapped to any table in the database. The HourlyPaidAuthor class is inherited from the Author class to be mapped to the hourlyPaidAuthor table in the database. Also, this class has the implementation of the GetAllAsync method, and in this way, it reads the records related to its table. The implementation of insert, edit and delete methods will be the same.
Notes:
There is no requirement to map the entire inheritance structure with one method, and you can benefit from different mapping patterns when dealing with an inheritance structure.
To simplify the implementation, you can use the inheritance mapper design pattern.
Using this design pattern, you should consider the primary key's value. Usually, the primary key column is in an abstract class. If the code for generating the primary key is also in that class, duplicate values may be generated for the primary key.
Consequences: Advantages
Each table contains all its related data, and there is no need for additional columns.
There is no need to use Join to read the table data; all the parent class features are repeated in the child classes, resulting in their tables.
Consequences: Disadvantages
Primary key value management can be difficult.
In the case of refactoring the codes and changing the location of the properties, it will be necessary to change the structure of the tables as well. This change is less than required in the class table inheritance design pattern and more than in the single table inheritance design pattern.
If the parent class is changed, all tables related to child classes must be changed.
Applicability:
When faced with an inheritance structure and we want to store the data related to each class inherent in the inheritance structure in our table, we can use this design pattern.
Related patterns:
Some of the following design patterns are not related to concrete table inheritance design patterns, but to implement this design pattern, checking the following design patterns will be useful:
Inheritance mapper
Class table inheritance
Single table inheritance
Inheritance mappers
Name:
Inheritance mappers
Classification:
Object-relational structures design patterns
Also known as:
---
Intent:
Organizing different existing mappers to manage the inheritance structure using this design pattern is possible.
Motivation, Structure, Implementation, and Sample code:
In the examples presented for different methods of mapping the inheritance structure to tables, it is often necessary to use a structure to prevent the generation of duplicate codes. There is also a need for a class to be responsible for loading and storing records related to a domain class. All these things can be done with the help of inheritance mappers' design patterns. Following the example provided for authors, suppose we want to implement fetch, insert, delete, and edit operations for each type of author. Consider the example of the concrete table inheritance design pattern. According to that example, we have an abstract class called Author and two intrinsic classes called HourlyPaidAuthor and MonthlyPaidAuthor.
Usually, we have a mapper for each model. The following class diagram can be considered for mappers:
Figure%209.8.png
Figure 9.8: Relation between AuthorMapper and different collaboration format mappers
As seen in the Figure 9.8 class diagram, the AuthorMapper class is abstract. We must use this model to implement fetching, inserting, editing, and deleting operations.
For retrieving records, since retrieving records from the database will result in the corresponding method returning one or a set of intrinsic objects. Therefore, according to the nature of this operation, the fetching operation must be placed in each of the inherent Mappers and cannot be placed in the parent’s abstract Mapper. Therefore, for fetching, the following codes for HourlyPaidAuthorMapper can be imagined:
public abstract class AuthorMapper { }
public class HourlyPaidAuthorMapper : AuthorMapper
{
protected HourlyPaidAuthor Load(IDataReader reader)
{
return new HourlyPaidAuthor()
{
AuthorId = (int)reader["AuthorId"],
FirstName = (string)reader["FirstName"],
LastName = (string)reader["LastName"],
HourlyPaid = (int)reader["HourlyPaid"],
HoursWorked = (int)reader["HoursWorked"]
};
}
public async Task> GetAllAsync()
{
var result = new List
var reader = await new SqlCommand($"" +
$"SELECT * " +
$"FROM hourlyPaidAuthor", DB.Connection).ExecuteReaderAsync();
while (reader.Read())
result.Add(Load(reader));
return result;
}
}
The implementation of these methods for the monthlyPaidAuthorMapper class is also the same. The mechanism can be different from inserting or editing, and you can get help from the AuthorMapper class. For example, as follows:
public abstract class AuthorMapper
{
private readonly string tableName;
public AuthorMapper(string tableName) => this.tableName = tableName;
protected string GetUpdateStatementById(Author author)
{
return $"UPDATE {tableName} " +
$"SET FirstName =N'{author.FirstName}',
LastName=N'{author.LastName}' " +
$"#UpdateToken# " +
$"WHERE AuthorId = {author.AuthorId}";
}
protected async Task
=> await new SqlCommand(query, DB.Connection).ExecuteNonQueryAsync() > 0;
}
public class HourlyPaidAuthorMapper : AuthorMapper
{
public HourlyPaidAuthorMapper() : base("hourlyPaidAuthor") { }
public async Task UpdateAsync(HourlyPaidAuthor obj)
=> await SaveAsync(base.GetUpdateStatementById(obj)
.Replace("#UpdateToken#", $"HoursWorked = {obj.HoursWorked}"));
}
For the sake of simplicity, the codes related to data retrieval have been removed. As can be seen in the preceding code, the UpdateAsync method in the HourlyPaidAuthorMapper class performs editing operations with the help of the GetUpdateStatementById and SaveAsync methods defined in the parent mapper. The insertion mechanism is also similar to this mechanism. The deletion process is also like this mechanism, which can be seen in the following code:
public abstract class AuthorMapper
{
private readonly string tableName;
public AuthorMapper(string tableName) => this.tableName = tableName;
public string GetDeleteStatementById(int authorId)
=> $"DELETE FROM {tableName} WHERE AuthorId = {authorId}";
protected async Task
=> await new SqlCommand(query, DB.Connection).ExecuteNonQueryAsync() > 0;
public virtual async Task DeleteAsync(int authorId)
=> await SaveAsync(GetDeleteStatementById(authorId));
}
public class HourlyPaidAuthorMapper : AuthorMapper
{
public HourlyPaidAuthorMapper() : base("hourlyPaidAuthor") { }
}
The codes related to editing and data retrieval have been removed for simplicity. As seen in the preceding code, the AuthorMapper class has implemented data deletion logic, and child mappers can use these methods.
Notes:
This design pattern is usually used alongside single, class, or concrete table inheritance design patterns.
Consequences: Advantages
It removes duplicate codes and thus increases the maintainability of the code.
Consequences: Disadvantages
For small scenarios, it can increase the complexity.
Applicability:
This design pattern can be useful when a database mapping based on inheritance exists.
Related patterns:
Some of the following design patterns are not related to inheritance mappers' design pattern, but to implement this design pattern, checking the following design patterns will be useful:
Single table inheritance
Class table inheritance
Concrete table inheritance
Conclusion
In this chapter, you got acquainted with various methods for data mapping and mapping the structure of classes and tables in databases. Also, in this chapter, you learned how to store and retrieve Value Objects with a proper table structure.
In the next chapter, you will learn about Object-relational metadata mapping patterns.
1 Newtonsoft Json.NET is a popular high-performance JSON framework for .NET
Join our book's Discord space
Join the book's Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:
https://discord.bpbonline.com