4.4. Creating an Entity Data Model (EDM)

In our application, we will use the Code First approach.

To create an EDM, right-click the project name in Solution Explorer and select Add New Item from the menu.

fbdevgd30 efw 006 en

Figure 16. Solution Explorer — Add New Item

Next, in the Add New Item wizard, select ADO.NET Entity Data Model.

fbdevgd30 efw 007 en

Figure 17. Add New Item wizard — select ADO.NET Entity Data Model

Since we already have a database, we will generate the EDM from the database. Select the icon captioned Code First from database.

fbdevgd30 efw 008 en

Figure 18. Add New Item wizard — select ‘Code First from database’

Now we need to select the connection the model will be created from. If the connection does not exist, it will have to be created.

fbdevgd30 efw 009 en

Figure 19. Add New Item wizard — choose Connection

You might need to specify some advanced properties in addition to the main connection properties. You might want to set the transaction isolation, for example, to a level different from the default Read Committed, or to specify connection pooling, or something else that differs from defaults.

fbdevgd30 efw 010 en

Figure 20. Add Connection wizard — Connection properties

fbdevgd30 efw 011 en

Figure 21. Add Connection wizard — Advanced connection properties

Snapshot is the recommended isolation level because Entity Framework and ADO.NET both use disconnected data access — where each connection and each transaction is active only for a very short time.

Next, the Entity Data Model wizard will ask you how to store the connection string.

fbdevgd30 efw 012 en

Figure 22. EDM wizard — connection string storage

For a web application or another three-tier architecture, where all users will be working with the database using a single account, select Yes. If your application is going to request authentication for connecting to the database, select No.

It is much more convenient to work with wizards if you select Yes for each property. You can always change the isolation level in the application when it is ready for testing and deployment by just editing the connection string in the <AppName>.exe.conf application configuration file. The connection string will be stored in the connectionStrings section and will look approximately like this:

  1. <add name=”DbModel”
  2. connectionString=”character set=UTF8; data source=localhost;
  3. initial catalog=examples; port number=3050;
  4. user id=sysdba; dialect=3; isolationlevel=Snapshot;
  5. pooling=True; password=masterkey;”
  6. providerName=”FirebirdSql.Data.FirebirdClient” />

For the configuration file to stop storing the confidential information, just delete this parameter from the connection string: password=masterkey;

Firebird 3.0 Notes

Unfortunately, the current ADO.Net provider for Firebird (version 5.9.0.0) does not support network traffic encryption, which is enabled by default in Firebird 3.0 and higher versions. If you want to work with Firebird 3.0, you need to change some settings in firebird.conf (or in databases.conf for a specific database) to make Firebird to work without trying to use network encryption.

To do it, change the setting from the default

  1. # WireCrypt = Enabled

to

  1. WireCrypt = Disabled

making sure to delete the ‘#’ comment marker. Remember that you must restart the server for configuration changes to take effect.

Next, you will be asked which tables and views should be included in the model.

fbdevgd30 efw 013 en

Figure 23. EDM wizard — select tables and views

For our project, select the four tables that are checked in the screenshot.

The basic EDM is now ready.

4.4.1. The EDM Files

When the wizard’s work is finished, you should have five new files: a model file and four files each describing an entity in the model.

An Entity File

Let’s take a look at the generated file describing the INVOICE entity:

  1. [Table("Firebird.INVOICE")]
  2. public partial class INVOICE
  3. {
  4. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
  5. "CA2214:DoNotCallOverridableMethodsInConstructors")]
  6. public INVOICE()
  7. {
  8. INVOICE_LINES = new HashSet<INVOICE_LINE>();
  9. }
  10. [Key]
  11. [DatabaseGenerated(DatabaseGeneratedOption.None)]
  12. public int INVOICE_ID { get; set; }
  13. public int CUSTOMER_ID { get; set; }
  14. public DateTime? INVOICE_DATE { get; set; }
  15. public decimal? TOTAL_SALE { get; set; }
  16. public short PAYED { get; set; }
  17. public virtual CUSTOMER CUSTOMER { get; set; }
  18. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
  19. "CA2227:CollectionPropertiesShouldBeReadOnly")]
  20. public virtual ICollection<INVOICE_LINE> INVOICE_LINES { get; set; }
  21. }

The class contains properties for each field of the INVOICE table. Each of these properties has attributes that describe constraints. You can study the details of the various attributes in the Microsoft document, Code First Data Annotations.

Navigation Properties and “Lazy Loading”

Two navigation properties are generated: CUSTOMER and INVOICE_LINES. The first one contains a reference to the customer entity. The second contains a collection of invoice lines. It is generated because the INVOICE_LINE table has a foreign key to the INVOICE table. Of course, you can remove this property from the INVOICE entity, but it is not really necessary. The CUSTOMER and INVOICE_LINES properties use “lazy loading” which means that loading is not performed until the first access to an object. That way, the loading of related data is avoided unless it is actually needed. Once the data are accessed via the navigation property, they will be loaded from the database automatically.

If lazy loading is in effect, classes that use it must be public and their properties must have the keywords public and virtual.

The DbModel File

Next, we examine the DbModel.cs file that describes the overall model.

  1. public partial class DbModel : DbContext
  2. {
  3. public DbModel()
  4. : base("name=DbModel")
  5. {
  6. }
  7. public virtual DbSet<CUSTOMER> CUSTOMERS { get; set; }
  8. public virtual DbSet<INVOICE> INVOICES { get; set; }
  9. public virtual DbSet<INVOICE_LINE> INVOICE_LINES { get; set; }
  10. public virtual DbSet<PRODUCT> PRODUCTS { get; set; }
  11. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  12. {
  13. modelBuilder.Entity<CUSTOMER>()
  14. .Property(e => e.ZIPCODE)
  15. .IsFixedLength();
  16. modelBuilder.Entity<CUSTOMER>()
  17. .HasMany(e => e.INVOICES)
  18. .WithRequired(e => e.CUSTOMER)
  19. .WillCascadeOnDelete(false);
  20. modelBuilder.Entity<PRODUCT>()
  21. .HasMany(e => e.INVOICE_LINES)
  22. .WithRequired(e => e.PRODUCT)
  23. .WillCascadeOnDelete(false);
  24. modelBuilder.Entity<INVOICE>()
  25. .HasMany(e => e.INVOICE_LINES)
  26. .WithRequired(e => e.INVOICE)
  27. .WillCascadeOnDelete(false);
  28. }
  29. }

The properties coded here describe a dataset for each entity, along with advanced properties that are specified for creating a model with Fluent API. A complete description of the Fluent API can be found in the Microsoft document entitled Configuring/Mapping Properties and Types with the Fluent API.

We will use the Fluent API to specify precision and scale for properties of type DECIMAL in the OnModelCreating method, by adding the following lines:

  1. modelBuilder.Entity<PRODUCT>()
  2. .Property(p => p.PRICE)
  3. .HasPrecision(15, 2);
  4. modelBuilder.Entity<INVOICE>()
  5. .Property(p => p.TOTAL_SALE)
  6. .HasPrecision(15, 2);
  7. modelBuilder.Entity<INVOICE_LINE>()
  8. .Property(p => p.SALE_PRICE)
  9. .HasPrecision(15, 2);
  10. modelBuilder.Entity<INVOICE_LINE>()
  11. .Property(p => p.QUANTITY)
  12. .HasPrecision(15, 0);