If we were to buy the materials to build a house, would we buy the bare minimum to get four walls up and a roof, without a kitchen or a bathroom? Or would we buy enough material to build the house with multiple bedrooms, a kitchen, and multiple bathrooms?
The problem lies in how we define the bare minimum. The progression of software development has made us realize that there are ways of building software that do not require additional effort, but reap serious rewards. This is the same choice we are faced with when we decide on the approach to take with Entity Framework. We could just get it running and it would work most of the time.
Customizing and adding to it later would be difficult, but doable. There are a few things that we would need to give up for this approach. The most important among those is control over how the code is written. We have already seen that applications grow, mature, and have features added. The only thing that stays constant is the fact that at some point in time, in some way, we will come to push the envelope of almost every tool that we leverage to help us. The other side is that we could go into development, being aware of the value-added benefits that cost nothing, and with that knowledge, avoid dealing with unnecessary constraints.
When working with Entity Framework, there are some paths and options available to us. There are two main workflows for working with Object-Relational Mapper (ORM) tools such as Entity Framework:
- Database first: We start by defining our database objects and their relations, then write our classes to match them, and we bind them together
- Code first: We start by designing our classes as Plain Old CLR Objects (POCOs) to model the concepts that we wish to represent, without caring (too much!) how they will be persisted in the database
Note
The model-first approach was dropped in Entity Framework Core 1.0.
While following the database-first approach, we are not concerned with the actual implementation of our classes, but merely the structures—tables, columns, keys—on which they will be persisted. In contrast, with POCOs or code first, we start by designing the classes that will be used in our programs to represent the business and domain concepts that we wish to model. This is known as Domain-Driven Design (DDD). DDD certainly includes code first, but it is much more than that.
All of these approaches will solve the problem with varying degrees of flexibility.
Starting with a database-first approach in Entity Framework means we have an existing database schema and are going to let the schema, along with the metadata in the database, determine the structure of our business objects and domain model. The database-first approach is normally how most of us start out with Entity Framework and other ORMs, but the tendency is to move toward more flexible solutions as we gain proficiency with the framework. This will drastically reduce the amount of code that we need to write, but will also limit us to working within the structure of the generated code. Entities, which are generated by default here, are not 100% usable with WCF services, ASP.NET Web APIs, and similar technologies – just think about lazy loading and disconnected entities, for example. This is not necessarily a bad thing if we have a well-built database schema and a domain model that translates well into
Data Transfer Objects (DTOs). Such a domain and database combination is a rare exception in the world of code production. Due to the lack of flexibility and the restrictions on the way these objects are used, this solution is viewed as a short-term or small-project solution.
Modeling the domain first allows us to fully visualize the structure of the data in the application, and work in a more object-oriented manner while developing our application. Just think of this: a relational database does not understand OOP concepts such as inheritance, static members, and virtual methods, although, for sure, there are ways to simulate them in the relational world. The main reasons for the lack of adoption of this approach include the poor support for round-trip updates, and the lack of documentation on manipulating the POCO model so as to produce the proper database structure. It can be a bit daunting for developers with less experience, because they probably won't know how to get started. Historically, the database had to be created each time the POCO model changed, causing data loss when structural changes were made.
Coding the classes first allows us to work entirely in an object-oriented direction, and not worry about the structuring of the database, without the restrictions that the model-first designer imposes. This abstraction gives us the ability to craft a more logically sound application that focuses on the behavior of the application rather than the data generated by it. The objects that we produce that are capable of being serialized over any service have true persistence ignorance, and can be shared as contract objects as they are not specific to the database implementation. This approach is also much more flexible as it is entirely dependent on the code that we write. This allows us to translate our objects into database records without modifying the structure of our application. All of this, however, is somewhat theoretical, in the sense that we still need to worry about having primary key properties, generation strategies, and so on.
In each of the recipes presented in this book, we will follow an incremental approach, where we will start by adding the stuff we need for the most basic cases, and later on, as we make progress, we will refactor it to add more complex stuff.