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.
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.
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.