次の方法で共有


EF Feature CTP5: Code First Walkthrough

 


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.

For Code First to a New Database see https://msdn.com/data/jj193542

For Code First to an Existing Database see https://msdn.com/data/jj200620


 

We have released Entity Framework Feature Community Technology Preview 5 (CTP5) . Feature CTP5 contains a preview of new features that we are planning to release as a stand-alone package in Q1 of 2011 and would like to get your feedback on. Feature CTP5 builds on top of the existing Entity Framework 4 (EF4) functionality that shipped with .NET Framework 4.0 and Visual Studio 2010 and is an evolution of our previous CTPs.

This post will provide an introduction to Code First development. Code First allows you to define your model using C# or VB.Net classes, optionally additional configuration can be performed using attributes on your classes and properties or by using a Fluent API. Your model can be used to generate a database schema or to map to an existing database.

Mapping to an Existing Database

In CTP5 we have removed the need to perform additional configuration when mapping to an existing database. If Code First detects that it is pointing to an existing database schema that it did not create then it will ‘trust you’ and attempt to use code first with the schema. The easiest way to point Code First to an existing database is to add a App/Web.config connection string with the same name as your derived DbContext, for example;

 <connectionStrings>
  <add 
    name="MyProductContext" 
    providerName="System.Data.SqlClient" 
    connectionString="Server=.\SQLEXPRESS;Database=Products;Trusted_Connection=true;"/>
</connectionStrings>

This walkthrough is going to demonstrate Code First generating the database schema but the same principals apply to mapping to an existing database, with the exception of ‘8. Setting an Initialization Strategy’ which does not apply to existing databases.

 

1. Install EF CTP5

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

 

2. Create the Application

To keep things simple we’re going to build up a basic console application that uses the Code First to perform data access:

  • Open Visual Studio 2010
  • File -> New -> Project…
  • Select “Windows” from the left menu and “Console Application”
  • Enter “CodeFirstSample” 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 potentially 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 CTP5 so we need to add a reference to the CTP5 assembly:

  • Project -> Add Reference…
  • Select the “.NET” tab
  • Select “EntityFramework” from the list
  • Click “OK”

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

  • Project -> Add Reference…
  • Select the “.NET” tab
  • Select “System.Data.Entity” from the list
  • 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 ProductContext : 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. Reading & Writing Data

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

 class Program
{
    static void Main(string[] args)
    {
        using (var db = new ProductContext())
        {
            // Add a food category 
            var food = new Category { CategoryId = "FOOD", Name = "Foods" };
            db.Categories.Add(food);
            int recordsAffected = db.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 by convention 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 “CodeFirstSample.ProductContext”. 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 full set of conventions implemented in CTP5 are discussed in detail in this Conventions Design Blog post.

 

6. Reading & Writing 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 db = new ProductContext())
        {
            // Use Find to locate the Food category 
            var food = db.Categories.Find("FOOD");
            if (food == null)
            {
                food = new Category { CategoryId = "FOOD", Name = "Foods" };
                db.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 };
            db.Products.Add(product);

            int recordsAffected = db.SaveChanges();

            Console.WriteLine(
                "Saved {0} entities to the database.",
                recordsAffected);

            // Query for all Food products using LINQ 
            var allFoods = from p in db.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, one of the ways to configure it is to alter the constructor on your DbContext, specifying the new database name.

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

 public class ProductContext : DbContext
{
    public ProductContext()
        : base("MyProductDatabase")
    { }

    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 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.Database.DbDatabase.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 evolve your existing schema in place. Database evolution is something we are currently working on and a sample of the direction we are heading is provided in a recent design blog post.

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 CTP5 we include a couple of strategies you can plug in but you can also write custom ones.

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

 using System.Data.Entity.Database;

For the walkthrough we just want to drop and re-create the database whenever the model has changed, so at the top of the Main method in my Program class I’ve added the following code

 DbDatabase.SetInitializer<ProductContext>(
    new DropCreateDatabaseIfModelChanges<ProductContext>());

 

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 is covered in a separate post.

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 ProductContext : DbContext
{
    public ProductContext()
        : base("MyProductDatabase")
    { }

    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 “ EntityType 'Supplier' has no key defined. Define the key for this EntityType.” 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:

  • Project -> Add Reference…
  • Select the “.NET” tab
  • Select “System.ComponentModel.DataAnnotations” from the list
  • 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; }
}

The full list of annotations supported in CTP5 is;

  • KeyAttribute

  • StringLengthAttribute

  • MaxLengthAttribute

  • ConcurrencyCheckAttribute

  • RequiredAttribute

  • TimestampAttribute

  • ComplexTypeAttribute

  • ColumnAttribute

    Placed on a property to specify the column name, ordinal & data type

  • TableAttribute

    Placed on a class to specify the table name and schema

  • InversePropertyAttribute

    Placed on a navigation property to specify the property that represents the other end of a relationship

  • ForeignKeyAttribute

    Placed on a navigation property to specify the property that represents the foreign key of the relationship

  • DatabaseGeneratedAttribute

    Placed on a property to specify how the database generates a value for the property (Identity, Computed or None)

  • NotMappedAttribute

    Placed on a property or class to exclude it from the database

 

10. Validation

In CTP5 we have introduced a new feature that will validate that instance data satisfies data annotations before attempting to save to the database.

Let’s add annotations to specify that Supplier.Name must be between 5 and 20 characters long:

 public class Supplier
{
    [Key]
    public string SupplierCode { get; set; }

    [MinLength(5)]
    [MaxLength(20)]
    public string Name { get; set; }
}

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

 using System.Data.Entity.Validation;

Let’s change the Main method to insert some invalid data, catch any exceptions and display information about any errors:

 class Program
{
    static void Main(string[] args)
    {
        DbDatabase.SetInitializer<ProductContext>(
            new DropCreateDatabaseIfModelChanges<ProductContext>());

        using (var db = new ProductContext())
        {
            var supplier = new Supplier { Name = "123" };
            db.Suppliers.Add(supplier);

            try
            {
                db.SaveChanges();
            }
            catch (DbEntityValidationException ex)
            {
                foreach (var failure in ex.EntityValidationErrors)
                {
                    Console.WriteLine(
                        "{0} failed validation", 
                        failure.Entry.Entity.GetType());

                    foreach (var error in failure.ValidationErrors)
                    {
                        Console.WriteLine(
                            "- {0} : {1}", 
                            error.PropertyName, 
                            error.ErrorMessage);
                    }
                }
            }

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

If we run our application we will see the following displayed:

CodeFirstSample.Supplier failed validation

- Name : The field Name must be a string or array type with a minimum length of '5'.

Press any key to exit.

Summary

In this walkthrough we looked at Code First development using the EF Feature CTP5. We looked at defining and configuring a model, storing and retrieving data, configuring the database connection, updating the database schema as our model evolved and validating data before it is written to the database.

Fluent API

In addition to Data Annotations Code First also exposes a Fluent API to allow configuration, this is covered in a separate blog post.

Feedback & Support

As always we would love to hear any feedback you have on Code First by commenting on this blog post.

For support please use the Entity Framework Pre-Release Forum.

 

Rowan Miller

Program Manager

ADO.NET Entity Framework

Comments

  • Anonymous
    December 07, 2010
    When a entity contains another entity, for example a Product contains a Category, is it necessary to have a CategoryId and a Category property? I'd like to only have a Category property. If this is possible, can you show the configuration for the EntityConfiguration for Product and Category?  

  • Anonymous
    December 07, 2010
    @RandyB : +1 mate ! Would love to see an example of that also.

  • Anonymous
    December 07, 2010
    The comment has been removed

  • Anonymous
    December 07, 2010
    Is there a way to have an event or function called in the dbcontext or on the object itself when an object is being saved? Like what could be done on the linqtosql stuff where you could have special handling on update, insert, and delete of an object in the db?

  • Anonymous
    December 07, 2010
    @Solmead There are a couple of options here: You can override the "ValidateEntity" method on the DbContext which is called prior to the SaveChanges and you can put your business logic there. You can also implement IValidatableObject on your entities and this is called prior to saving the entity. A third alternative is to override the SaveChanges method on DbContext and put your custom business logic for saving there.

  • Anonymous
    December 15, 2010
    Hi guys, Good article.  However I am now confused about performing a few more advanced operations, what is the best practice for assigning a foreign key or updating a foreign key relationship supposing I wish to have the user select a new entry for an ASP.NET MVC view drop down list? It seems finally after much tinkering I had to perform the assignment of the true object itself using the .Find() method (and not just set the TeamID field) .. this seems a little silly to me as I am performing an extra redundant Select statement on the server.   Is there a way to just assign the TeamID value and it have it know to go and create the record or update the FK if it's an edit? Thanks. My save action method below I would like reviewed: [HttpPost]        public ActionResult Save(Contestant contestant)        {            if (ModelState.IsValid)            {                if (contestant.ID == 0)                {                    contestant.StatusID = (int)Helper.Status.Active;                    contestant.CreatedAt = DateTime.Now;                    db.Contestants.Add(contestant);                }                else                {                    contestant = db.Contestants.Find(contestant.ID);                    UpdateModel(contestant);                    contestant.UpdatedAt = DateTime.Now;                }                contestant.Team = db.Teams.Find(contestant.TeamID); // best practice???                db.SaveChanges();                return this.RedirectToAction("Index");            }            return View(contestant);        }

  • Anonymous
    December 16, 2010
    Hi another one :) It seems without [ForeignKey] on my "TeamLeaderAsContestantID" my ID would not link as a proper FK, instead it would make a separate un-related INT property in my table and also a "ContestantID" FK separate, Once I added [ForeignKey], I only had one "TeamLeaderAsContestantID" as the proper FK as I would expect; why do I need to add [ForeignKey] to the property for my FK to work correctly in SQL?? Thanks team. public class Team    {        public int ID { get; set; }        public int TeamLeaderAsContestantID { get; set; }        [Required]        [DisplayName("Team name")]        public string Name { get; set; }        [ScaffoldColumn(false)]        public DateTime? UpdatedAt { get; set; }        [ScaffoldColumn(false)]        public DateTime CreatedAt { get; set; }        [DisplayName("Team leader")]        [ForeignKey("TeamLeaderAsContestantID")]        public virtual Contestant TeamLeaderAsContestant { get; set; }        public virtual ICollection<Contestant> Contestants { get; set; }    }

  • Anonymous
    December 19, 2010
    Is it me or the source code is missing?

  • Anonymous
    December 22, 2010
    Sample impressed me when I tried for sqlexpress. It was ok but I could not satisfy with "connection string" and with  "3th party connection". Create db on SQL Server name is : PocoTest2 Define connectionString App.config with same name with CataContext (it means connectionString name will be ProductContext. <add name="ProductContext" connectionString="Data Source=KingOfPc;Initial Catalog=PocoTest2;Integrated Security=True"/> and modify ctor;        public ProductContext ()            : base ("ProductContext")        {        } Run. "Expecting non-empty string for 'providerInvariantName' parameter." whats wrong?

  • Anonymous
    December 22, 2010
    a ha! It is running! nice!! really nice! I made big mistake when build connectionString. <add name="MyProductContext" providerName="System.Data.SqlClient" connectionString="Server=SeniorData;Database=ProductContext;Trusted_Connection=true;"/>

  • Anonymous
    January 02, 2011
    I have a web MVC project. In another project (called DataLayer) I define my TweetContext : DbContext. When I try to instantiate a TweetContext in my web MVC project, it doesn't compile, complaining that it needs a reference to EntityFramework. I don't think it should need the reference to EntityFramework since the web project isn't using EntityFramework, it's using TweetContext.

  • Anonymous
    January 04, 2011
    @Graham, If you have TeamID mapped as an FK then you should be able to just set its value and the correct data should be saved to the database.  You should not need to load the actual Team object unless you need it for some other reason.  When you say that this doesn’t work can you be more specific about the problem you are running into?  It might be easier to post a question on the ADO.NET Entity Framework and LINQ to Entities (Pre-Release) MSDN forum since it is easier to create a thread there that we can use to drill down into the issue.  The forum address is: social.msdn.microsoft.com/.../threads. Thanks, Arthur

  • Anonymous
    January 04, 2011
    @Graham, This appears to be a bug in the convention that detects FK properties.  The workaround is to use the ForeignKeyAttribute (as you are doing) or map the FK using the fluent API. Thanks, Arthur

  • Anonymous
    January 04, 2011
    @Sunny Which source code are you referring to? Thanks, Arthur

  • Anonymous
    January 04, 2011
    @Matt Frear Since TweetContext derives from DbContext the CLR needs to be able to access the code for DbContext which is in EntityFramework.dll.  This is how the .NET CLR works. Thanks, Arthur

  • Anonymous
    January 06, 2011
    Hi All I am relatively new to EF and ORM in general, so appologies if my post if off-topic in any way. I would like to create a data access layer for my application and have read many posts on ORM including NHibernate and EF with its great feature enhancements in CTP4/5, cosequently I find myself a bit confused at times with diffirent examples from different versions and diffirent patterns. It also seems that things change quite quickly. While I plan to use EF for my data access it seems to me that it is a good idea to abstract this into a DAL, but I am having difficuly understranding how to go about this. Since many egsamples seem to be interacting with EF directly. My thinking is that I would structure my project as follows

  • Follow the code-first approuch and creat my business Domain entities, as POCOs.
  • Create the Data Access Layer using EF referencing the POCOs.
  • Create the Business Logic Layer that uses the DAL, possibly via Dependency Injection and extend the POCOs to add functionality and business logic, while using the original POCOs to communicate with the DAL.
  • Implement the presentation layer, possibly in an MVC Controller, that uses only the BLL and it's extended entities If this thinking is correct, then adding EF specific attributes and other constructs to my POCOs feels wrong and I would opt to use fluent API inside my EF DAL instead. Also I assume that my DAL class would derive from DbContext, but that I would not need to expose the DbSet<> properties since they would only be used internally by the methods of the DAL and the BLL would interact with the DAL via those methods. I have seen many MVC examples and use of EF but they all seem to be accessing EF directly from controller or BLL, thus creating a direct dependency on it, which I was hoping to be able to avoid..Also I have seen much talk about Repository pattern and am confused as to whether EF is an implementation of the repository pattern or whether I should implement my DAL as a Repository pattern   My questions are: Am I barking ujp the wrong tree || Do I have the cat by the tail ? What are the problems with my approuch, in terms of testability and is there a better apporuch ? Where can I find examples of using EF with DI and Repository pattern ? Many thanks
  • Anonymous
    January 27, 2011
    Why does the DbDatabase.SqlQuery<T> method return IEnumerable<T> instead of IQueryable<T>?

  • Anonymous
    February 05, 2011
    The following bit doesn't work: DbDatabase.SetInitializer<ProductContext>(    new DropCreateDatabaseIfModelChanges<ProductContext>()); Most of the time, if you modify the model and try to run the code again, you get this error: Cannot drop database "EFFromCode.ProductContext" because it is currently in use.

  • Anonymous
    February 07, 2011
    Hi Rowan, I believe I should not have any problem to apply these techniques to MySql? As our website uses Mysql, but all other enterprise applications, shop apps, all using SQL server. regards, Amir

  • Anonymous
    February 09, 2011
    @Arthur Excellent, glad I could raise your attention to it. Keep up the good work.

  • Anonymous
    February 13, 2011
    @Rick I am facing the exact same dillema and I see nobody has answered your question. Did you come right in the end?

  • Anonymous
    February 13, 2011
    @Rick I am facing the exact same dillema and I see nobody has answered your question. Did you come right in the end?

  • Anonymous
    February 28, 2011
    Same problem with Tom, the sample project can't drop and recreate correctly with SqlException, I think it's an internal error of CTP5 which should be solved by something like changing to 'USE master' instead of ‘USE Products’. Waiting for reply.  

  • Anonymous
    March 04, 2011
    I created a simple template t4 to generate a DbContext class for my model edmx. This template takes the EntitySet as the name of the table. So the pluralization is set in the model.edmx in the property EntitySet (Nombre del conjunto de entidades) You can download my template here: www.mediafire.com/.../Modelo.tt Remember change... string inputFile = @"YourModelFileName.edmx";

  • Anonymous
    March 04, 2011
    Sorry, bad link. www.mediafire.com

  • Anonymous
    March 08, 2011
    Hello Dear; I recently used the CTP5 of EF 4 and I added all of references to my project and my project works properly, but I do not have access to those properties that are for Validation and Data Annotations! I added EntityFramework and System.Data.Entity in my project references. Please help me tanx Regards Dariush Tasdighi

  • Anonymous
    August 16, 2011
    We have database where the name of views and tables are like ACT.Table1, ACT.view1, ACT.Sp1, how to code so that ACT get passed in the query, otherwise the SQL error out with object not found.

  • Anonymous
    September 28, 2011
    The comment has been removed

  • Anonymous
    September 29, 2011
    As on today it is addressed in Alpha3 release blogs.msdn.com/.../10215177.aspx