Hand Written Entity in C#

Posted in software by Christopher R. Wirz on Sun Sep 13 2015



Entity Framework is an object-relational mapper (O/RM) that enables .NET developers to work with a database using .NET objects. It eliminates the need for most of the data-access code that developers usually need to write.

Note: This post has not been updated for Entity Framework 6 or Entity Framework Core.

The main advantage of Entity is that you can create data objects which perform all the sanitization and validation for you - and the database can store data without much additional logic. This means your code gains portability and you are not tied to a specific database.

Note: For this post you will have to reference EntityFramework, Oracle.ManagedDataAccess, Oracle.ManagedDataAccess.EntityFramework, System.Data.Entity
Also, for compatibility all properties and table names will be in ALL_CAPS.

Let's consider the user example, in which I would like to reserve multiple user names and multiple email addresses for the same account (for example if I stop paying for one of my many domains). I won't bother with comments, this should be pretty self-explanatory.


namespace Test.Objects
{
    public partial class User
    {
        public long USER_ID { get; set; }
        public string FIRST_NAME { get; set; }
        public string MIDDLE_INITIAL { get; set; }
        public string LAST_NAME { get; set; }
    }

    public partial class EmailAddress
    {
        public string ADDRESS { get; set; }
        public long USER_ID { get; set; }
    }

    public partial class UserName
    {
        public string NAME { get; set; }
        public long USER_ID { get; set; }
    }
}

In this example, the USER_ID of the EmailAddress and UserName tell me which User they are tied to. This allows for a One-to-Many relationship between a User and EmailAddress or User and UserName. We won't get into foreign keys in this example, or entity relationships. Now it's time to connect to database!


using System.Data.Entity;
using System.ComponentModel.DataAnnotations.Schema;
using Oracle.ManagedDataAccess.Client;

namespace Test.Context
{
	/// <summary>
	///     A DbContext to use for testing
	/// </summary>
    public class TestContext : System.Data.Entity.DbContext
    {
        /// <summary>
        ///     The constructor for the context
        /// </summary>
        public TestContext() : base(
            new OracleConnection(
                "Data Source=(DESCRIPTION=" +
                    "(ADDRESS=(PROTOCOL=TCP)(HOST=oracle.chriswirz.com)(PORT=1521))" +
                    "(CONNECT_DATA=(SID=PRODUCTION)));" +
                "User Id=CHRISWIRZ;Password=oraclepassword1;Persist Security Info=True"
            ), true){}

        /// <summary>
        ///     This method is called when the model for a derived context has been initialized,
        ///     but before the model has been locked down and used to initialize the context.
        ///     The default implementation of this method does nothing, but it can be overridden
        ///     in a derived class such that the model can be further configured before it is
        ///     locked down.
        /// </summary>
        /// <param name="modelBuilder">The builder that defines the model for the context being created.</param>
        /// <remarks>
        ///     Typically, this method is called only once when the first instance of a derived
        ///     context is created. The model for that context is then cached and is for all
        ///     further instances of the context in the app domain. This caching can be disabled
        ///     by setting the ModelCaching property on the given ModelBuidler, but note that
        ///     this can seriously degrade performance. More control over caching is provided
        ///     through use of the DbModelBuilder and DbContextFactory classes directly.
        /// </remarks>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Test.Objects.User>()
                .HasKey(u => u.USER_ID)
                .Property(u => u.USER_ID)
                .IsRequired()
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
            modelBuilder.Entity<Test.Objects.User>().ToTable("CHRISWIRZ.TEST_USERS");

            modelBuilder.Entity<Test.Objects.EmailAddress>()
                .HasKey(e => e.ADDRESS)
                .Property(e => e.ADDRESS)
                .IsRequired()
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
            modelBuilder.Entity<Test.Objects.EmailAddress>().ToTable("CHRISWIRZ.TEST_EMAILADDRESSES");

            modelBuilder.Entity<Test.Objects.UserName>()
                .HasKey(n => n.NAME)
                .Property(n => n.NAME)
                .IsRequired()
                .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
            modelBuilder.Entity<Test.Objects.UserName>().ToTable("CHRISWIRZ.TEST_USERNAMES");
        }

        public DbSet<Test.Objects.User> Users { get; set; }
        public DbSet<Test.Objects.EmailAddress> EmailAddresses { get; set; }
        public DbSet<Test.Objects.UserName> UserNames { get; set; }
    }
}

We see that we needed to establish which properties are keys and are required. Obviously more logic can be applied, but this is a great start. Let's write a simple program to save and retrieve the data.


namespace Test
{
	class Test
	{
		static void Main(string[] args)
		{
			using (var context = new Test.Context.TestContext())
			{
				context.Database.CreateIfNotExists();

				var c = context.Users.Add(new Test.Objects.User()
				{
					USER_ID = 1,
					FIRST_NAME = "Christopher",
					MIDDLE_INITIAL = "R",
					LAST_NAME = "Wirz"
				});
				var em1 = context.EmailAddresses.Add(new Test.Objects.EmailAddress()
				{
					USER_ID = c.USER_ID,
					ADDRESS = "crwirz@wirzbrothers.com"
				});
				var em2 = context.EmailAddresses.Add(new Test.Objects.EmailAddress()
				{
					USER_ID = c.USER_ID,
					ADDRESS = "chris@chriswirz.com"
				});
				var un1 = context.UserNames.Add(new Test.Objects.UserName()
				{
					USER_ID = c.USER_ID,
					NAME = "crwirz"
				});
				var un2 = context.UserNames.Add(new Test.Objects.UserName()
				{
					USER_ID = c.USER_ID,
					NAME = "chriswirz"
				});
				int rows = context.SaveChanges();
				Console.WriteLine("Added {0} rows", rows); // Added 5 rows
			}

			using (var context = new Test.Context.TestContext())
			{
				var ch = context.Users.Where(u => u.LAST_NAME == "Wirz").First();
				var emails = context.EmailAddresses.Where(e => e.USER_ID == ch.USER_ID);
				var usernames = context.UserNames.Where(n => n.USER_ID == ch.USER_ID);
				Console.WriteLine("Found {0} emails and {1} usernames for {2}", emails.Count(), usernames.Count(), ch.FIRST_NAME);
				// Found 2 emails and 2 usernames for Christopher
			}
		}
	}
}

That's it. We have made a context, using entity, which we can query using LINQ.