Code-First Database Design with Entity Framework and PostgreSQL

Entity Framework is an Object-Relational Mapper for .NET applications, and the code-first approach using Entity Framework allows developers to use their domain model to build and query databases directly from their data model objects.

In this article, we’ll take a look at how to use the Entity Framework in C# to create a table in a PostgreSQL database. Using a domain model for Car a  as an example, we’ll take a look at different ways to configure the framework using data annotations and take a look at how different model decoration attributes affect the underlying table created by Entity Framework.

You can view the full application source on GitHub.

Creating the Entity Framework Application

Let’s start by creating a new console project. Open Visual Studio and click File menu, then New –> Project. From the dialog box, choose Installed –> Templates –> Visual C# –> Windows Classic Desktop. Choose Console App (.NET Framework), then provide location and a name (I typed PostgreCodeFirst).

Next, let’s add PostgreSQL Entity Framework provider – add the latest version of Npgsql.EntityFramework NuGet package.

01-install-Npgsql.EntityFramework_thumb.pnguploading.4e448015.gif轉存失敗重新上傳取消Add Npgsql.EntityFramework NuGet package

The operation will also install Entity Framework 6 NuGet package as one of its dependencies.

02-EntityFramework-installed_thumb.pnguploading.4e448015.gif轉存失敗重新上傳取消Install Npgsql.EntityFramework NuGet package and dependences

The latest version of the Npgsql provider (2.2.7) references Entity Framework version 6.0.0. There is a more recent version as of this writing, and we will cover upgrading Entity Framework to the latest version shortly.

Adding the PostgreSQL Connection String

Next, we’ll need to configure our PostgreSQL database. Open the App.configfile and add a connectionStrings section. Please keep configuration as the first child element of the  node – it’s a strict .NET requirement. Otherwise, the application will crash at runtime.

<configuration>  
   <configSections>
     (...)
   </configSections>
   <connectionStrings>
     <add
       name="Default"
       connectionString="host=hostname;port=5432;database=databaseName;user id=userName;password=secret"
       providerName="Npgsql" />
   </connectionStrings>
   (...)
</configuration>

The providerName attribute in the connection string definition is pointing to the PostgreSQL provider (Npgsql). At the outset, the database in the databaseNameconnectionString section of the  should not exist yet; it will be created by Entity Framework. Since you’re creating a new database, you’ll need to make sure that the user used for the connection has the ability to create a new database.

Define the Database Context

To add a database context for our Car object, let’s add a class called CarContext to the project.

Add DbContext class

The class should inherit from System.Data.Entity.DbContext . It will be the main interface for accessing the PostgreSQL database.

using System.Data.Entity;

namespace PostgreCodeFirst  
{
    public class CarContext : DbContext
    {
        public CarContext() : base(nameOrConnectionString: "Default") { }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.HasDefaultSchema("public");
            base.OnModelCreating(modelBuilder);
        }
    }
}

Notice that we’re passing in "Default" in the nameOrConnectionString  field of the base class. This creates a database context from our default connection string. We’ll also have to change the name of our model schema – the default schema has a name of dbo, but PostgreSQL requires us to use a schema named public. Overriding the OnModelCreating method lets us change the default schema name for of our Car entities.

Creating the Car Entity

Next, we’ll add our Car class to the project. It contains the data model for all of the Car entities in our database.

using System.ComponentModel.DataAnnotations;

namespace PostgreCodeFirst  
{
    public class Car
    {
        [Key]
        public string LicenceNumber { get; set; }
        public string Insurance { get; set; }
        public int? Year { get; set; }

        public override string ToString()
        {
            return LicenceNumber;
        }
    }
}

Every entity in Entity Framework needs to have a primary key defined. We’ll do that by adding a Key decorator to the primary key field. In this case, we’ve added it to the LicenseNumber property.

Let’s switch back to the  CarContext class and add our Car entities to it.

public DbSet<Car> Cars { get; set; }

The Cars  property will be responsible for all operations on Car entities throughout our application.

Initialize the PostgreSQL Database

We can make Entity Framework initialize the database by just querying for any entity. To do that, let’s instantiate a new  CarContext and get some data. Update the Main method of Program class with following code.

using System;  
using System.Linq;

namespace PostgreCodeFirst  
{
    public class Program
    {
        public static void Main(string[] args)
        {
            using (var context = new CarContext())
            {
                var cars = context.Cars.ToArray();
                Console.WriteLine($"We have {cars.Length} car(s).");
            }
        }
    }
}

After running the application we should see following output.

We have 0 car(s).
Press any key to continue . . .

If we check the database schema, we’ll see the new table with columns for each property in our  Car entity.

04-Car-table-created_thumb.pnguploading.4e448015.gif轉存失敗重新上傳取消Cars table

We should see a ‘Cars’ table with 3 columns: LicenceNumber, Insurance and Year. We haven’t created any cars yet, so the table is empty as expected.

Adding Data to the Cars Table

Now that we know the schema has been created, let’s add some data. Update the code in the main method with the following:

public static void Main(string[] args)  
{
  using (var context = new CarContext())
  {
    var licenceNumbers = new[] { "ABC 1234", "CD 54321", "EF 55577" };
    foreach (var number in licenceNumbers)
    {
      context.Cars.Add(new Car { LicenceNumber = number });
    }
    context.SaveChanges();

    var cars = context.Cars.ToArray();
    Console.WriteLine($"We have {cars.Length} car(s).");
    foreach(var car in cars)
    {
      Console.WriteLine(car);
    }
  }
}

The LicenseNumber  primary key is the only field we need to create a  new Car , so we’ll use an array of license numbers to add new cars. Running the application should now produce the following output:

We have 3 car(s).
ABC 1234
CD 54321
EF 55577
Press any key to continue . . .

Updating Table and Column Names

Now that we’ve seen how to create new tables and columns, let’s try modifying the table name. Instead of a ‘Car’ table, let’s try to force Entity Framework to create a table name ‘Cars’. We can do this by decorating the Car class with a Table attribute from  the System.ComponentModel.DataAnnotations.Schema namespace. Update the Car class with the following:

[Table("Cars")]
public class Car

Let’s also try to modify ‘LicenceNumber’ column by giving it the name Licence_Number.  We have to decorate that property with a Column attribute.

[Key]
[Column("Licence_Number")]

The entire class now looks like the following:

using System.ComponentModel.DataAnnotations;  
using System.ComponentModel.DataAnnotations.Schema;

namespace PostgreCodeFirst  
{
    [Table("Cars")]
    public class Car
    {
        [Key]
        [Column("Licence_Number")]
        public string LicenceNumber { get; set; }
        public string Insurance { get; set; }
        public int? Year { get; set; }

        public override string ToString()
        {
            return LicenceNumber;
        }
    }
}

Re-creating the Database Schema

Even though we’ve modified the  Car data model above, our changes won’t propagate to the database quite yet. Entity Framework won’t update the database schema by default, so we have to re-create the database manually. While we could use database migrations to update the database schema automatically, there are quite a few issues that can arise from using migrations. For that reason we won’t cover them here. However, you can learn more about migrations on the MSDN site.

Instead of running migrations, we’ll instead re-create the database manually. We’ll do this by dropping the database then running the application. Entity Framework will initialize the database again. The application output will be the same.

We have 3 car(s).
ABC 1234
CD 54321
EF 55577
Press any key to continue . . .

If we take a look at the database schema, we can see that Entity Framework has now used the updated table and column name we specified.

05-updated-car-table_thumb.pnguploading.4e448015.gif轉存失敗重新上傳取消Updated table and column names

Using a Composite Key

Our initial example has a simple primary key that consists of one column. Entity Framework supports composite keys consisting of two or more columns as well. Let’s modify the Car class to have a two column primary key. Let’s add a new composite key by creating a property called Id and decorating it with Key attribute. If we run the application now we will get following exception.

System.InvalidOperationException:
Unable to determine composite primary key ordering for type 'PostgreCodeFirst.Car'.
Use the ColumnAttribute or the HasKey method to specify an order for composite primary keys.

We get this error because Entity Framework can’t infer the order of columns in the primary key. The order of the columns in a composite primary key is important, and we have to tell Entity Framework which column comes first, LicenceNumber or Id. To fix it, we have to use Column attribute with Order parameter.

[Key]
[Column("Licence_Number", Order = 1)]
public string LicenceNumber { get; set; }

[Key]
[Column(Order = 2)]
public Guid Id { get; set; }

After dropping the database, the application will create a new composite key.

06-car-table-with-composite-key_thumb.pnguploading.4e448015.gif轉存失敗重新上傳取消Composite key

Notice that the table primary key is updated as well.

Primary key

Minimum and Maximum Length Validations

Entity Framework allows us to create validators for our properties. Let’s take a look at the built-in Minimum and Maximum validators, which validate a field for a minimum and maximum length. Let’s constrain our LicenceNumber property so it has the minimum length of 4 and maximum length of 10 characters.

[Key]
[Column("Licence_Number", Order = 1)]
[MinLength(4), MaxLength(10)]
public string LicenceNumber { get; set; }

If you will try to save change breaking the constraint you will get an exception.

System.Data.Entity.Validation.DbEntityValidationException:
Validation failed for one or more entities.
See 'EntityValidationErrors' property for more details.

As described in the exception, the details are available in the EntityValidationErrors property.

The Required Validator

The next validator offered by Entity Framework specifies a required property. To get it working you just need to decorate the property with the Required attribute. Let’s make the Insurance property required:

[Required]
public string Insurance { get; set; }

If we try to run our application now, we’ll get a crash when attempting to add our sample data. So let’s modify the Main method of our Program.cs file to fix it:

using System;  
using System.Linq;

namespace PostgreCodeFirst  
{
    public class Program
    {
        public static void Main(string[] args)
        {
            using (var context = new CarContext())
            {
                var cars = new[]
                    {
                        new Car { LicenceNumber = "ABC 1234", Insurance = "ABCD" },
                        new Car { LicenceNumber = "CD 54321", Insurance = "XYZ" },
                        new Car { LicenceNumber = "EF 55577", Insurance = "INSURANCE" }
                    };
                foreach (var car in cars)
                {
                    context.Cars.Add(car);
                }
                context.SaveChanges();

                cars = context.Cars.ToArray();
                Console.WriteLine($"We have {cars.Length} car(s).");
                foreach(var car in cars)
                {
                    Console.WriteLine(car);
                }
            }
        }
    }
}

Notice that we’ve now added an Insurance field to our default objects. This satisfies the validator and allows us to run the program without any issues.

Data Complex Types

Sometimes you split your domain entities to smaller objects but want to keep them all in one big table. For example, we might want to have Person class and keep it as part of the Car class (for example, in a role called Owner ). We can do that by using a complex type. First, let’s add Person class:

using System.ComponentModel.DataAnnotations.Schema;

namespace PostgreCodeFirst  
{
    [ComplexType]
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

Note the ComplexType attribute annotation to this class. This tells Entity Framework to make this a complex type. Next, we’ll add a property to Car class:

public Person Owner { get; set; }

This ensures that each car has the ability to store a Person with it. Finally, let’s modify the application to fill owner property when we initialize our data.

var owner = new Person  
{
    FirstName = "Adam",
    LastName = "Who"
};

var cars = new[]  
{
    new Car { LicenceNumber = "ABC 1234", Insurance = "ABCD" },
    new Car { LicenceNumber = "CD 54321", Insurance = "XYZ" },
    new Car { LicenceNumber = "EF 55577", Insurance = "INSURANCE" }
};
foreach (var car in cars)  
{
    car.Owner = owner;
    context.Cars.Add(car);
}
context.SaveChanges();  

We’ll manually update the schema by dropping the database and running the application to re-create it. We should now see the following in our database:

Complex type

NotMapped property

Entity Framework allows the creation of “virtual” properties, which are properties that are not mapped to a column in database table. Virtual properties are useful for storing data that is computed from other fields rather than stored directly in the database itself. Let’s create a Summary property that outputs a summary of the Car object. We’ll add a new property, Summary, annotated with the NotMapped attribute.

[NotMapped]
public string Summary { get { return LicenceNumber + ", " + Insurance; } }

public override string ToString()  
{
  return Summary;
}

We can now call the object in Entity Framework just like any other property. This time there is no need to drop the database, as the schema is not changed by not mapped properties.

Indexing the Database

It’s quite common to optimize database queries by adding indexes to the database. Indexes were added to Entity Framework in Version 6.1, so we need to update it to at least 6.1 version. At the moment of writing the article, the latest version available is 6.1.3.

Update Entity Framework to 6.1.3

Once, we have the framework updated, we can add an index for Year column. We have to add Index attribute to the Year property.

[Index]
public int? Year { get; set; }

Once we re-create the database, we can see the index.

10-car-table-index_thumb.pnguploading.4e448015.gif轉存失敗重新上傳取消Add Year index

We can observe the default index naming convention that Entity Framework applies. The name consist of two parts separated by underscore character:

  • Table name (Cars)
  • Index name – by default it’s property name with ‘IX_’ prefix (IX_Year)

The index name can be changed by adding the name to the Index attribute.

[Index("MyIndexName")]
public int? Year { get; set; }

The above code will result in following index name.

11-index-name-changed_thumb.pnguploading.4e448015.gif轉存失敗重新上傳取消Changed index name

Unique Index

We can add a unique index to guarantee that every row in the table has a unique value in a specified column. As a unique index is, in fact, an index we will use the same Index attribute and will add an IsUnique parameter to our Index attribute. Let’s make the Insurance column unique.

[Required]
[Index(IsUnique = true)]
public string Insurance { get; set; }

It will create following index in the database.

Add unique index

Multicolumn Indexes

Entity Framework allows adding a multi-column index as well. It’s done just by adding Index attribute with the same name to few properties. We also have to add one more parameter to the attribute that will define the column order.

Let’s add an index to sort data after Id and Insurance columns.

[Index("MulticolumnIndexName", 1)]
public Guid Id { get; set; }

[Index("MulticolumnIndexName", 2)]
public string Insurance { get; set; }

It will result in following index being created.

13-multicolumn-index_thumb.pnguploading.4e448015.gif轉存失敗重新上傳取消Multicolumm index

Summary

Entity Framework is a powerful and flexible object-relational mapping tool, making code-first data modeling in .NET a breeze. We’ve covered the basics of getting started with Entity Framework, but there are many more options for database optimization or data validation can be achieved just by decorating appropriate model properties. You can learn more about Entity Framework by checking out the MSDN Entity Framework Documentation.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章