Partager via


EF Feature CTP4 Walkthrough: Productivity Improvements

 


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.


 

Introduction

We recently announced the release of Entity Framework Feature Community Technology Preview 4 (CTP4) . Feature CTP4 contains a preview of new features that we are considering adding to the core framework in the future and would like to get community feedback on. Feature CTP4 builds on top of the existing Entity Framework 4 (EF4) functionality that shipped with .NET Framework 4.0 and Visual Studio 2010.

If you need assistance with CTP4 we have an Entity Framework Pre-Release Forum.

This walkthrough provides an introduction to the Productivity Improvement work that is included in our Entity Framework Feature CTP4 release. Our recent Design Blog post provides a detailed background on the Productivity Improvements, here is a quick summary extract from that post:

We’ve been paying attention to the most common patterns that we see developers using with the EF and have been brewing up a set of improvements to the Entity Framework designed to allow developers to accomplish the same tasks with less code and fewer concepts.

These improvements provide a cleaner and simpler API surface that focuses your attention on the most common scenarios but still allows you to drill down to more advanced functionality when it’s needed. We hope you will enjoy this simpler experience, but we should be quick to assure you that this is NOT a new data access technology. These improvements are built on the same technology for mapping, LINQ, providers and every other part of the Entity Framework.  

Database First & Model First

The Productivity Improvement work benefits developers using Database First, Model First and Code First development patterns. This walkthrough will use the Code First development pattern. The T4 Templates to support Database First and Model First development didn’t make it into CTP4 but we will provide some sample templates in a separate post in the coming weeks.

1. Install EF CTP4

If you haven’t already done so then you need to install Entity Framework Feature CTP4.

2. Create the Application

To keep things simple we’re going to build up a basic console application that uses the EF Productivity Improvements to perform data access.

· Open Visual Studio 2010

· File -> New -> Project…

· Select “Windows” from the left menu and “Console Application”

· Enter “EF.PI.Walkthrough” as the name

· Select “OK”

3. Create the Model

Let’s define a very simple model using classes. I’m just defining them in the Program.cs file but in a real world application you would split your classes out into separate files and probably a separate project.

· Below the Program class definition in Program.cs I am defining the following two classes

public class Category

{

    public string CategoryId { get; set; }

    public string Name { get; set; }

    public virtual ICollection<Product> Products { get; set; }

}

public class Product

{

    public int ProductId { get; set; }

    public string Name { get; set; }

    public string CategoryId { get; set; }

    public virtual Category Category { get; set; }

}

 

4. Create a Context

The simplest way to start using the classes for data access is to define a context that derives from System.Data.Entity.DbContext and exposes a typed DbSet<TEntity> for each class in my model.

· We’re now starting to use types from the CTP so we need to add a reference to the CTP assembly

 o Project -> Add Reference…

 o Select the “.NET” tab

 o Select “Microsoft.Data.Entity.Ctp” from the list

 o Click “OK”

 

· You’ll also need a reference to the existing Entity Framework assembly

 o Project -> Add Reference…

 o Select the “.NET” tab

 o Select “System.Data.Entity” from the list

 o Click “OK”

 

· Add a using statement for System.Data.Entity at the top of Program.cs

using System.Data.Entity;

· Add a derived context below the existing classes that we’ve defined

public class ProductCatalog : DbContext

{

    public DbSet<Category> Categories { get; set; }

    public DbSet<Product> Products { get; set; }

}

 

That is all the code we need to write to start storing and retrieving data. Obviously there is quite a bit going on behind the scenes and we’ll take a look at that in a moment but first let’s see it in action.

5. Access Data

· I’m padding out the Main method in my program class as follows

class Program

{

    static void Main(string[] args)

    {

        using (var context = new ProductCatalog())

        {

            var food = new Category { CategoryId = "FOOD", Name = "Foods" };

            context.Categories.Add(food);

            int recordsAffected = context.SaveChanges();

            Console.WriteLine(

                "Saved {0} entities to the database, press any key to exit.",

                recordsAffected);

       Console.ReadKey();

        }

    }

}

You can now run the application and see that the new category is inserted.

Where’s My Data?

DbContext has created a database for you on localhost\SQLEXPRESS. The database is named after the fully qualified name of your derived context, in our case that is “EF.PI.Walkthrough.ProductCatalog”. We’ll look at ways to change this later in the walkthrough.

Model Discovery

DbContext worked out what classes to include in the model by looking at the DbSet properties that we defined. It then uses the default Code First conventions to find primary keys, foreign keys etc. The conventions are discussed in detail in this Conventions Design Blog post, the conventions included in CTP4 are:

  • Primary Key
  • Relationship Inverse
  • Foreign Key
  • Pluralization of Table Names

 

Simplified API Surface

If you’re familiar with ObjectContext you’ll notice that intellisense on your derived context now only shows you 10 members, as opposed to the 47+ that showed up on ObjectContext.

You’ll see we have started to factor functionality away so that only the most commonly used operations are exposed at the root level. For example all members related to the underlying database are available from context. Database. This is just one example of the work we are doing to provide a more discoverable and intuitive API surface, more detail is available in the API Surface section of our Productivity Improvements Design Blog post.

6. Access More Data

Let’s pad out the program we just wrote to show a bit more functionality. We are going to make use of the Find method on DbSet that will locate an entity based on primary key. If no match is found then Find will return null. We’re also making use of LINQ to query for all products in the Food category ordered alphabetically by name. Querying uses the exiting LINQ to Entities provider so it supports the same queries that are possible with ObjectSet/ObjectQuery in EF4.

· I’m replacing the Main we wrote above with the following

class Program

{

    static void Main(string[] args)

    {

        using (var context = new ProductCatalog())

        {

            // Use Find to locate the Food category

            var food = context.Categories.Find("FOOD");

            if (food == null)

            {

                food = new Category { CategoryId = "FOOD", Name = "Foods" };

                context.Categories.Add(food);

            }

            // Create a new Food product

            Console.Write("Please enter a name for a new food: ");

            var productName = Console.ReadLine();

            var product = new Product { Name = productName, Category = food };

            context.Products.Add(product);

            int recordsAffected = context.SaveChanges();

            Console.WriteLine(

                "Saved {0} entities to the database.",

                recordsAffected);

            // Query for all Food products using LINQ

            var allFoods = from p in context.Products

                            where p.CategoryId == "FOOD"

                            orderby p.Name

                            select p;

            Console.WriteLine("All foods in database:");

            foreach (var item in allFoods)

            {

                Console.WriteLine(" - {0}", item.Name);

            }

            Console.WriteLine("Press any key to exit.");

            Console.ReadKey();

        }

    }

}

7. Changing the Database Name

If you want to change the name of the database that is created for you then there is a constructor on DbContext that allows you to specify the name.

· Say we want to change the name of the database to “MyProductCatalog” we could add a default constructor to our derived context that passes this name down to DbContext:

public class ProductCatalog : DbContext

{

    public ProductCatalog()

       : base("MyProductCatalog")

    { }

    public DbSet<Category> Categories { get; set; }

    public DbSet<Product> Products { get; set; }

}

Other Ways to Change the Database

There are a number of other ways to specify which database should be connected to. We’ll cover these in more detail in a separate post in the near future.

  • App.config Connection String
    Create a connection string in the App.Config file with the same name as your context.
  • DbConnection
    There is a constructor on DbContext that accepts a DbConnection.
  • Replace the Default Convention
    The convention used to locate a database based on the context name is an AppDomain wide setting that you can change via the static property System.Data.Entity.Infrastructure.Database.DefaultConnectionFactory.

8. Setting an Initialization Strategy

In the next section we are going to start changing our model which in turn means the database schema needs to change as well. Currently there is no out of the box solution to migrate your existing schema in place, although this is something we are looking at addressing. There is however the opportunity to run some custom logic to initialize the database the first time a context is used in an AppDomain. This is handy if you want to insert seed data for test runs but it’s also useful to re-create the database if the model has changed. In CTP4 we include a couple of strategies you can plug in but you can also write custom ones.

For the walkthrough we just want to drop and re-create the database whenever the model has changed.

· At the top of the Main method in my Program class I’ve added the following code

Database.SetInitializer<ProductCatalog>(new RecreateDatabaseIfModelChanges<ProductCatalog>());

We’ll provide more details on this feature and the scenarios that it enables in a separate post.

9. Data Annotations

So far we’ve just let EF discover the model using its default conventions but there are going to be times when our classes don’t follow the conventions and we need to be able to perform further configuration. There are two options for this; we’ll look at Data Annotations in this section and then the Code First Fluent API in the next section.

· Let’s add a supplier class to our model

public class Supplier

{

    public string SupplierCode { get; set; }

    public string Name { get; set; }

}

· And we also need to add a set to our derived context

public class ProductCatalog : DbContext

{

    public ProductCatalog()

        : base("MyProductCatalog")

    { }

    public DbSet<Category> Categories { get; set; }

    public DbSet<Product> Products { get; set; }

    public DbSet<Supplier> Suppliers { get; set; }

}

 

Now if we ran our application we’d get an InvalidOperationException saying “Unable to infer a key for entity type 'EF.PI.Walkthrough.Supplier'.” because EF has no way of knowing that SupplierCode should be the primary key for Supplier.

· We’re going to use Data Annotations now so we need to add a reference

 o Project -> Add Reference…

 o Select the “.NET” tab

 o Select “System.ComponentModel.DataAnnotations” from the list

 o Click “OK

 

· Add a using statement at the top of Program.cs

using System.ComponentModel.DataAnnotations;

 

· Now we can annotate the SupplierCode property to identify that it is the primary key:

public class Supplier

{

    [Key]

    public string SupplierCode { get; set; }

    public string Name { get; set; }

}

 

Data Annotations are described in detail in this Design Blog post, the annotations supported in CTP4 are:

  • Key
  • StringLength
  • ConcurrencyCheck
  • Required
  • Timestamp
  • DataMember
  • RelatedTo
  • MaxLength
  • StoreGenerated

10. Fluent API

We looked at configuring the model using Data Annotations but you may not want to add attributes to your classes or in some cases you may not be able to if they belong to another class library. The other option is to use the Code First Fluent API within DbContext.

Let’s say we want to configure Name to be a required property for Supplier.

· Add a using statement for System.Data.Entity.ModelConfiguration at the top of Program.cs

using System.Data.Entity.ModelConfiguration;

 

· Override the OnModelCreating method in the derived context, as highlighted below
Note: The base implementation of OnModelCreating is blank so there is no need to call the base method.

public class ProductCatalog : DbContext

{

    public ProductCatalog()

        : base("MyProductCatalog")

    { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)

    {

        modelBuilder.Entity<Supplier>().Property(s => s.Name).IsRequired();

    }

    public DbSet<Category> Categories { get; set; }

    public DbSet<Product> Products { get; set; }

    public DbSet<Supplier> Suppliers { get; set; }

}

Summary

In this walkthrough we looked at the core patterns for Code First development using the EF Productivity Improvements included in EF Feature CTP4. We looked at defining and configuring a model, storing and retrieving data, configuring the database connection and updating the database schema as our model evolved.

We’d like to hear any feedback you have on these new Productivity Improvements for EF.

Rowan Miller
Program Manager
ADO.NET Entity Framework

 

EF.PI.Walkthrough.zip

Comments

  • Anonymous
    July 14, 2010
    I've tried this seveal times but I am getting a 'System.TypeLoadException'. It cannot load types from the Microsoft.Data.Entity.CTP .dll.

  • Anonymous
    July 14, 2010
    @Lynn Eriksen Can you confirm that you have installed CTP4, you would see this issue if the CTP3 dll was still installed in the GAC.

  • Anonymous
    July 14, 2010
    POCO only - no proxies! Nice. How did you manage to do that?

  • Anonymous
    July 14, 2010
    Ah ... I see the proxies. Very nice work! For an navigation property using ICollection<Object> should it always use a public setter? Also, depending on Q1, should we always initalize the collection in the constructor?

  • Anonymous
    July 14, 2010
    @lynn eriksen You don’t have to use proxies, EF in general does support pure POCO objects. In this walkthrough the navigation properties are virtual to allow EF to create proxies that support lazy loading. If you are using pure POCO and you are working with DbContext it will work out when it needs to scan your objects for changes and do so for you (If you are working with ObjectContext then you would need to call DetectChanges() to prior to calling an API so that the context knows it should scan for changes) There is no requirement to have a public setter for the collection. EF will only create a new collection if it needs to, so if you initialize the property in the constructor then EF won’t try and set it again. If you do make the setter private and you application runs in medium trust then you would have to initialize the collection because EF will not call the private setter.

  • Anonymous
    July 14, 2010
    1)Index Creating 2)Exclude Attribute 3)Drop All Table,Create All Table Hope next ctp will include~

  • Anonymous
    July 15, 2010
    I just upgraded from CTP 3 and the ContextBuilder seems to have gone away. Is there a new way to build an ObjectContext? See my question on StackOverflow:stackoverflow.com/.../building-an-objectcontext-in-ef-code-first-ctp4

  • Anonymous
    July 15, 2010
    @Keith We still support creating an ObjectContext, the code looks like this:    var builder = new ModelBuilder();    builder.DiscoverEntitiesFromContext(typeof(MyContext));    var model = builder.CreateModel();    var context = model.CreateObjectContext<MyContext>(connection);

  • Anonymous
    July 15, 2010
    @sipo

  1. We may look at adding first class support for specifying indexes. Even if we do support indexes there may still be database concerns that you can’t configure through the Fluent API, we provide a hook to run custom logic when it comes time to initialize the database. Using the model out of the walkthrough as an example here is some code that would create an index on the CategoryId column in the Products table. There are of course some limitations in that I’m specifying raw SQL which bypasses the EF provider model. First create a custom initialization strategy for the context:    public class MyCustomInitializer : IDatabaseInitializer<ProductCatalog>    {        public void InitializeDatabase(ProductCatalog context)        {            if (!context.Database.Exists() || !context.Database.ModelMatchesDatabase())            {                context.Database.DeleteIfExists();                context.Database.Create();                var cmd = context.Database.Connection.CreateCommand();                cmd.CommandText = "CREATE NONCLUSTERED INDEX IX_Products_CategoryId ON dbo.Products ( CategoryId )";                context.Database.Connection.Open();                cmd.ExecuteNonQuery();                context.Database.Connection.Close();            }        }    } Then register this strategy before the first context is created in the AppDomain:    Database.SetInitializer<ProductCatalog>(new MyCustomInitializer());
  2. The StoreIgnore attribute for excluding properties didn’t make it into CPT4 but will be there in the next release
  3. We’ve had this feedback from a few people, mostly around using Code First to create tables in an externally hosted database. We haven’t started work on this yet but it’s definitely on the list of things we want to support.
  • Anonymous
    July 16, 2010
    I realy like how foreign keys are named in database. CategoryId, but not Category_Id or CategoryID. I think it is most often used naming pattern. Don`t change it, please.

  • Anonymous
    July 16, 2010
    I hope that the EF4 team is talking to the DB Pro team about Code-First and how it may work with DB Pro.  It would be good for MS to have a strategy before the Code First feature ships on how the DB Creation capability would be migrated over to a more sustainable model - I can see reasons to start out using Code First on a project and then at some future maturity point want to leverage the benefits of DB Pro (Data Dude) to manage the DB Schema objects.  It sounds like there are plans to mature the EF way of updating databases (perhaps to enable something similar to Rails migration capabilities) - I will hope for a story that includes DB Pro as well because I see Code First being useful for a lot of apps even Enterprise ones initially that down the road will need more rigor - it would be disappointing for that scenario to not be considered.  Thanks for all of the great new stuff - keep it coming!

  • Anonymous
    July 17, 2010
    Hi, Where can I post suggestions about EF4 ?

  • Anonymous
    July 19, 2010
    I'm also getting an error when trying to run the first example. System.TypeLoadException was unhandled  Message=Could not load type 'System.Data.Entity.DbContext' from assembly 'Microsoft.Data.Entity.Ctp, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.  Source=EF.PI.Walkthrough  TypeName=System.Data.Entity.DbContext  StackTrace:       at EF.PI.Walkthrough.Program.Main(String[] args)       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)       at System.Threading.ThreadHelper.ThreadStart()  InnerException: I checked in Add/Remove programs and I don't see any other CTP installed (I don't believe I ever installed a previous CTP).

  • Anonymous
    July 20, 2010
    I uninstalled CTP4, then uninstalled the WebMatrix beta in case that was causing an issue, then reinstalled CTP4 and now it's working fine for me.

  • Anonymous
    July 22, 2010
    @Alexey Good to hear :) @Bryan Hinton We are talking :) It’s not clear when or what the integration will be but we are looking at the options. @NaZaf You can post bugs/suggestions and vote on existing bugs/suggestion through Microsoft Connect; connect.microsoft.com/.../SearchResults.aspx

  • Anonymous
    July 27, 2010
    I like naming convention too, and BTW you forgot to mention about including namespace System.Data.Entity.Infrastructure in the walkthrough.

  • Anonymous
    August 03, 2010
    I am also getting the DbContext TypeLoadException. I have CTP4 installed, and no former version. Anyone has been able to solve this issue yet?

  • Anonymous
    August 10, 2010
    @Sergio Can you check what version of Microsoft.Data.Entity.Ctp is installed in the GAC; %WinDir% Microsoft.NETassemblyGAC_MSILMicrosoft.Data.Entity.Ctp It should be 4.0.30319.0, if it isn’t then try repairing the install If this doesn’t work then start up a thread in our pre-release forum and we’ll work out what is going wrong for you; social.msdn.microsoft.com/.../threads

  • Anonymous
    August 12, 2010
    Using CTP4 - It is possible define  user db function in code (without edmx)? F.E. SQL function --- create function f_test(@i int) returns varchar(10) begin  return (cast(@i as varchar(10)) end


using in code something like this: .. class ProductCatalog :DBContext{ ...  protected override void OnModelCreating(ModelBuilder modelBuilder)        {            modelBuilder.AddFunction("f_test", x= >this.MyTest);        }  public static sting MyTest(int i){      throw new Exception(" ");  } } test{ context = new ProductCatalog ();  var qry = from p in context.Products     select new {p,stringid=MyTest(p.ProductId )}; }

  • Anonymous
    August 14, 2010
    @Alex Code First currently only supports mapping to tables. We are looking at introducing more options into ModelBuilder but it’s not clear if functions will be included in this work yet.

  • Anonymous
    August 17, 2010
    Hi there, Do you know if there is a way to load some of the data with a where clause. When we call context.Categories i see in the profiler that it selects the whole table content. However if the table is big we may want to select only some of the data, for example orders from the last week, rather than all the orders... is there a way to do it with code first? Thanks N

  • Anonymous
    August 17, 2010
    Sorry - forgot to mention above when using 'Find()'

  • Anonymous
    August 28, 2010
    @NNZZ If you are using DbSet<T>.Find() then only one row will be selected from the database (the SQL statement will contain a WHERE clause for the row with the specified key) If you wanted to select orders from the last week you could use LINQ as follows:    var date = DateTime.Today.AddDays(-7);    var recentOrders = from o in context.Orders                        where o.OrderDate > date                        select o;

  • Anonymous
    August 30, 2010
    Thanks Rowan - for great stuff with Code-First. Plz suggest way to deal with: Have a BaseType with say 5 properties. I inherit another 5 types, say Type1... to Type5 from it. I then want to use TPH and use BaseTypeConfig class for MapHierarchy(). Now some properties of Type1 to Type5 are common, but not in all. So I specify those properties in relevant types and not in BaseType. When I am defining Hierarchy and discriminator, I define those property mapping as well for the relevant types. This all compiles fine, and when I run the app to create my DB - I go and discover that EF4 has actually created multiple properties say Title1, Title2, Title3, while I wanted my Type1 to Type3 to use same column Title and utilize Discriminator column to discover matching columns from rows. What am I missing? Is scenario supported? Appreciate the help.

  • Anonymous
    September 07, 2010
    @Sharad This scenario is supported by overriding the default column mapping, something like this:    modelBuilder.Entity<BaseType>().MapHierarchy()        .Case<BaseType >(b => new { b.Id, discriminator = "B" })        .Case<Type1>(d => new { Title = d.Title, discriminator = "T1" })        .Case<Type2>(d => new { Title = d.Title, discriminator = "T2" });

  • Anonymous
    November 11, 2010
    Eric Message  Message=Could not load type 'System.Data.Entity.DbContext' from assembly 'Microsoft.Data.Entity.Ctp, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' itself answers that you need to include assembly System.Data.Entity All the references you may need are using System.Data.Entity; using Microsoft.Data.Entity.Ctp; (ctf4) using Microsoft.Data.Entity; using System.Data.Entity.Infrastructure; Two assembly need to be added ( System.Data.Entity And

  • Anonymous
    November 17, 2010
    Any news on this : "Note: We are separately working to provide better data migration support for scenarios where you are working with production data and want to version the schema.  We think of that as a different scenario than this early development-time feature that I’m describing here.  The data migration capability isn’t enabled yet with this week’s CTP."

  • Anonymous
    July 02, 2011
    I wondered how changing the model can be done and still keep any content of the related tables.

  • Anonymous
    July 02, 2011
    I wondered how changing the model can be done and still keep any content of the related tables.