高档蟹爪兰盆景图图片:Code-First Development with Entity Framework 4

来源:百度文库 编辑:九乡新闻网 时间:2024/10/06 01:43:34

Code-First Development with Entity Framework 4

.NET 4 ships with a muchimproved version of Entity Framework (EF) – a data access library thatlives in the System.Data.Entity namespace.

When EntityFramework was first introduced with .NET 3.5 SP1, developers provided alot of feedback on things they thought were incomplete with that firstrelease.  The SQL team did a good job of listening to this feedback, andreally focused the EF that ships with .NET 4 on addressing it. 

Some of the big improvements in EF4 include:

  • POCO Support: You can now define entities without requiring base classes or data persistence attributes.
  • Lazy Loading Support: You can now load sub-objects of a model on demand instead of loading them up front.
  • N-Tier Support and Self-Tracking Entities: Handle scenarios where entities flow across tiers or stateless web calls.
  • Better SQL Generation and SPROC support : EF4 executes better SQL, and includes better integration with SPROCs
  • Automatic Pluralization Support : EF4 includes automatic pluralization support of tables (e.g. Categories->Category).
  • Improved Testability : EF4’s object context can now be more easily faked using interfaces.
  • Improved LINQ Operator Support : EF4 now offers full support for LINQ operators.

VisualStudio 2010 also includes much richer EF designer and tooling support.The EF designer in VS 2010 supports both a “database first” developmentstyle – where you construct your model layer on a design surface from anexisting database.  It also supports a “model first” development style –where you first define your model layer using the design surface, andcan then use it to generate database schema from it.

Code-First Development with EF

In addition to supporting a designer-based development workflow, EF4 also enables a more code-centricoption which we call “code first development”.  Code-First Developmentenables a pretty sweet development workflow.  It enables you to:

  • Develop without ever having to open a designer or define an XML mapping file
  • Define your model objects by simply writing “plain old classes” with no base classes required
  • Use a “convention over configuration” approach that enables database persistence without explicitly configuring anything
  • Optionally override the convention-based persistence and use a fluent code API to fully customize the persistence mapping

EF’s“code first development” support is currently enabled with a separatedownload that runs on top of the core EF built-into .NET 4.  CTP4 ofthis “code-first” library shipped this week and can be downloaded here. 

It works with VS 2010, and you can use it with any .NET 4 project (including both ASP.NET Web Forms and ASP.NET MVC).

Step by Step Tutorial: Building NerdDinner using a Code-First Approach

Last year I wrote an ASP.NET MVC 1.0 tutorial that was published both onlineand in a book.  The tutorial walked through creating a simpleapplication, called “NerdDinner”, which provides an easy way for peopleto organize, host and RSVP for dinners online.  You can read my originalASP.NET V1 NerdDinner tutorial here.  An updated version of the tutorial is also included in the new Professional ASP.NET MVC 2 book.

TheNerdDinner tutorial used a “database first approach” where the databaseschema was defined first, and then we used a Visual Studio designer tocreate our LINQ to SQL / LINQ to Entities model objects that mapped toit.  

Below I’m going to demonstrate how we could instead usea “code first approach” using EF4 to build the NerdDinner model layerand database schema, and construct a CRUD application using ASP.NET MVC.

Wewill walkthrough building this application step-by-step.  A downloadlink to a completed version of the sample is available at the end ofthis blog post.

Step 1: Create a New Empty ASP.NET MVC 2 Application

We’llstart by creating a new ASP.NET MVC 2 Project within Visual Studio2010.  Choose File->New Project and use the “ASP.NET MVC 2 Empty WebApplication” project template to do this.

This will create an empty ASP.NET MVC 2 project that does not have any controllers, models or views within it:

We’llnext work to define our NerdDinner “model” – which refers to theobjects that represent the data of our application, as well as thecorresponding domain logic that integrates validation and business ruleswith it.  The model is the "heart" of an MVC-based application, andfundamentally drives the behavior of it.  We’ll create this model layerusing the new EF4 “Code First” capabilities.

Step 2: Create our Model

Let’sassume we do not already have a database defined, and that we arebuilding our new NerdDinner application completely from scratch.

We do not need to start with a database

When using a code-first development workflow, we do notneed to begin our application by creating a database or specifyingschema.  Instead we can begin by writing standard .NET classes thatdefine the domain model objects that are most appropriate for ourapplication – without having to worry about intermixing data persistencelogic within them.

Creating Model Classes

NerdDinneris a small application, and our data storage needs with it are prettysimple.  We want to be able to define and store “Dinners” that refer tospecific events that people can attend.  We also want to be able todefine and store “RSVP” acceptances, which are used to track a person’sinterest in attending a particular Dinner.

Let’s create twoclasses (Dinner and RSVP) to represent these concepts.  We’ll do this byadding two new classes to our ASP.NET MVC project - “Dinner” and“RSVP”:

The above “Dinner” and “RSVP” model classes are “plain old CLR objects” (aka POCO).  They do notneed to derive from any base classes or implement any interfaces, andthe properties they expose are standard .NET data-types.  No datapersistence attributes or data code has been added to them. 

Theability to define model classes without having to tie them to aparticular database, database API, or database schema implementation isreally powerful – and provides us with much more data accessflexibility.  It allows us to focus on our application/business needswithout having to worry about persistence implementation.  It also givesus the flexibility to change our database schema or storageimplementation in the future – without having to re-write our modelobjects, or the code that interacts with them.

Creating a Context Class to Handle Database Persistence

Nowthat we’ve defined our two POCO model classes, let’s create a classthat we can use to handle the retrieval/persistence of Dinner and RSVPinstances from a database. 

We’ll name this class“NerdDinners”. It derives from the DbContext base class, and publishestwo public properties – one that exposes our Dinner objects, and onethat exposes our RSVP objects:

TheDbContext and DbSet classes used above are provided as part of the EF4Code-First library.  You’ll need to add a reference to theSystem.Data.Entity.CTP assembly that is installed into the \Program Files\Microsoft ADO.NET Entity Framework Feature CTP4\Binariesdirectory to reference these classes.  You’ll also want to add a “usingSystem.Data.Entity” namespace statement at the top of your“NerdDinners” class file.

That is all the code we need to write

The above three classes contain all of the code necessary to implement a basic model and data persistence layer for our NerdDinner application.  We do notneed to configure any additional database schema mapping information,nor run any tools, nor edit any XML files, nor use any designers inorder to start using our classes to retrieve, update, and save data intoa database.

Convention Based Persistence Mapping

Wedo not need to write any additional code, nor create any XML files, noruse any tools in order to map our model classes to and from adatabase.  How, you might ask, is that possible?

By default,EF code-first supports a “convention over configuration” approach thatenables you to rely on common mapping conventions instead of having toexplicitly configure things.  You can override theseconventions if you want to provide custom database mapping rules.  Butif you instead just use the default conventions you’ll find that theamount of code you have to write is really small, and the common 90% ofscenarios “just work” the way you’d expect them to without any extracode or configuration.

In our example above, our NerdDinnerscontext class will by default map its “Dinners” and “RSVPs” propertiesto “Dinners” and “RSVPs” tables within a database.  Each row within theDinners table will map to an instance of our “Dinner” class.  Likewise,each row within the RSVPs table will map to an instance of our “RSVP”class.  Properties within the “Dinner” and “RSVP” classes in turn map tocolumns within the respective “Dinners” and “RSVPs” database tables.

Otherdefault conventions supported by EF include the ability toautomatically identify primary-key and foreign keys based on commonnaming patterns (for example: an ID or DinnerID property on the Dinnerclass will be inferred as the primary key).  EF also includes smartconventions for wiring-up association relationships between models.  TheEF team has a blog post that talks more about how the default set ofconventions work here.

Code Examples of How to Use Our Model

The three classes we created earlier contain all of the codenecessary to implement our model and data persistence for NerdDinner. Let’s now look at a few code examples of how we can use these classes toperform common data scenarios:

Query Using LINQ Expressions

Wecan write LINQ query expressions to retrieve data from a database usingthe following code.  Below we are using a LINQ expression to retrieveall dinners that occur in the future:

Wecan also take advantage of relationships between Dinners and RSVPs whenwriting our LINQ expressions.  Notice below how our “where” statementfilters by dinners whose RSVP count is greater than 0:

Notethat the “where” filter in the above query (where we are retrievingonly those Dinners who have at least one RSVP) executes in the databaseserver – making the query and the amount of data we retrieve veryefficient.

Retrieving a Single Instance

We can use LINQ’s Single() method with a lambda query to retrieve a single instance of a Dinner using code like below:

Alternatively,we can also take advantage of a Find() method that EF “code-first”exposes that allows you to easily retrieve an instance based on its ID:

Adding a new Dinner

Thecode below demonstrates how to create and add a new Dinner to thedatabase.  All we need to do is to “new” a Dinner object, set propertieson it, and then add it to the Dinners property of our NerdDinnerscontext object.  The NerdDinner context class supports a “unit of work”pattern that enables you to add multiple models to the context, and thencall “SaveChanges()” on it to persist all of the changes to a databaseas a single atomic transaction.

Updating a Dinner

Thecode below demonstrates how to retrieve a Dinner, update one of itsproperties, and then save the changes back to the database:

Step 3: Create a ASP.NET MVC Controller that uses our Model

Let’snow look at a more complete scenario involving our model, where we use acontroller class to implement the functionality necessary to publish alist of upcoming dinners, and enable users to add new ones:

We’llimplement this functionality by right-clicking on the “Controllers”folder and choose the “Add->Controller” menu command.  We’ll name ournew controller “HomeController”.

We’ll then add three “action methods” within it that work with the NerdDinners model we created earlier using EF “Code-First”:

The “Index” action method above retrieves and renders a list of upcoming dinners. 

The“Create” action methods allow users to add new dinners.  The first“Create” method above handles the “HTTP GET” scenario when a user visitsthe /Home/Create URL, and send back a “New Dinner” form to fill out. The second “Create” method handles the “HTTP POST” scenario associatedwith the form – and handles saving the dinner in the database.  If thereare any validation issues it redisplays the form back to the user withappropriate error messages.

Adding Views for our Controllers

Our next step will be to add two “View templates” to our project – one for “Index” and one for “Create”. 

We’lladd the “Index” view to our project by moving our cursor within theIndex action method of our controller, and then right-click and choosethe “Add View” menu command.  This will bring up the “Add View” dialog. We’ll specify that we want to create a strongly-typed view, and that weare passing in a IEnumerable list of “Dinner” model objects to it:

When we click “Add”, Visual Studio will create a / Views/Home/Index.aspxfile.  Let’s then add the following code to it – which generates a

    list of Dinners, and renders a hyperlink that links to ourcreate action:

    We’llthen add the “Create” view to our project by moving our cursor withinthe Create action method of our controller, and then right-click andchoose the “Add View” menu command.  Within the “Add View” dialog we’llspecify that we want to create a strongly-typed view, and that we arepassing it a Dinner object.  We’ll also indicate that we want to“scaffold” using a “Create” template:

    When we click “Add”, Visual Studio will create a /Views/Home/Create.aspxfile with some scaffold-generated content within it that outputs anHTML

    for a “Dinner” object.  We’ll tweak it slightly andremove the input element for the DinnerID property.  Our final viewtemplate content will look like this:

    Wehave now implemented all of the code we need to write within ourController and Views to implement the Dinner listing and Dinner creationfunctionality within our web application.

    Step 4: The Database

    We’ve written our code.  Now let’s run the application. 

    But what about the database?

    Wedon’t have a database yet – and haven’t needed one so far because our“code first” development workflow hasn’t required us to have one todefine and use our model classes. 

    But we will need adatabase when we actually run our application and want to store ourDinner and RSVP objects.  We can create the database one of two ways:

    1. Manually create and define the schema ourselves using a database tool (e.g. SQL Management Studio or Visual Studio)
    2. Automatically create and generate the schema directly from our model classes using the EF Code-First library

    This second option is pretty cool and is what we are going to use for our NerdDinner application.

    Configuring our Database Connection String

    Tobegin with, we’ll setup a connection-string to point to where we wantour database to live.  We’ll do this by adding a “NerdDinners”connection-string entry to our application’s web.config file like so: 

     

    By default, when you create a DbContext class with EF code-first,it will look for a connection-string that matches the name of thecontext-class.  Since we named our context class “NerdDinners”, it willby default look for and use the above “NerdDinners” databaseconnection-string when it is instantiated within our ASP.NETapplication.

    Taking advantage of SQL CE 4

    You can use many different databases with EF code-first – including SQL Server, SQL Express and MySQL.

    Two weeks ago I blogged about the work we are also doing to enable the embedded SQL CE 4 database engineto work within ASP.NET.  SQL CE 4 is a lightweight file-based databasethat is free, simple to setup, and can be embedded within your ASP.NETapplications.  It supports low-cost hosting environments, and enablesdatabases to be easily migrated to SQL Server.

    SQL CE can be a useful option to use when you are in the earlystages of defining (and redefining) your model layer – and want to beable to quickly create and recreate your database as you do so.  We’lluse SQL CE 4 to begin with as we develop our NerdDinner application.  Wecan later optionally change the connection-string to use SQL Express orSQL Server for production deployment – without having to modify asingle line of code within our application.

    The connection-string I specified above points to a NerdDinners.sdfdatabase file, and specifies the SQL CE 4 database provider.  In orderfor this to work you need to install SQL CE 4 – either via the Standalone SQL CE Installer or by installing WebMatrix (which includes it built-in).  SQL CE 4 is a small download that only takes a few seconds to install.

    Important: In the connection-string above I’mindicating that we want to create the NerdDinners.sdf file within the|DataDirectory| folder – which in an ASP.NET application is the\App_Data\ folder immediately underneath the application directory.  Bydefault the “Empty ASP.NET MVC Web Application” project template doesnot create this directory.  You will need to explicitly create thisdirectory within your project (right click on the project and choose the“Add->ASP.NET Folder->Add_Data” menu item).

    Automatic Database Schema Creation

    EF code-first supports the ability to automatically generatedatabase schema and create databases from model classes – enabling youto avoid having to manually perform these steps.

    This happens by default if your connection-string points to either a SQL CE or SQL Express database file that does not already exist on disk.  You do not need to take any manual steps for this to happen.

    To see this in action, we can press F5 to run our NerdDinnerapplication.  This will launch a browser at the root “/” URL of ourapplication.  You should see a screen like below rendered back:

    The “/” URL to our application invoked the HomeController.Index()action method – which instantiated and queried our NerdDinners contextobject to retrieve all upcoming Dinners from our database.  Because theNerdDinners.sdf database file we pointed our connection-string to didn’talready exist, the EF code-first library automatically generated it forus.  It used our NerdDinners context object to automatically infer thedatabase schema for the database it generated. 

    To see the SQL CE database file that was generated, click the “Showall Files” icon within the Visual Studio solution explorer, and thenpress the “Refresh” button and expand the App_Data folder:

     

    We will be shipping an update to VS 2010 in the future that enablesyou to open up and edit SQL CE 4 databases within the “Server Explorer”tab (just like you do with SQL databases today).  This will enable youto easily see (and optionally tweak) the schema and contents of thedatabase.  Until then you can optionally use the database tools withinWebMatrix to examine the SQL CE 4 database file’s contents. 

    We did not specify any custom persistence mapping rules with ourNerdDinners context – so the database that was generated followed thedefault EF code-first naming conventions to map the schema.  If we hadspecified any custom mapping rules, though, the EF code-first librarywould have honored those and generated a database that matched them. 

    Just to refresh our memory – here are the two POCO model classes and the NerdDinners context class that we defined earlier:

    Below are the tables that were added when we ran our applicationand the database was automatically created based on the above model:

     

    The definition of the “Dinners” table looks like below.  The columnnames and data-types map to the properties of the Dinner class wedefined.  The DinnerID column has also been configured to be both aprimary key and an identity column:

    The definition of the “RSVPs” table looks like below.  The columnnames and data-types map to the properties of the RSVP class wedefined.  The RsvpID column has also been configured to be both aprimary key and an identity column:

    A one to many primary key/foreign key relationship was alsoestablished between the Dinners and RSVPs tables.  The EF code-firstlibrary inferred that this should be established because our Dinnerclass has an ICollection property named RSVPs, and the RSVPclass has a Dinner property.  

    Populating the Database with some Dinners

    Let’s now create and add some Dinners to our database.  We’ll dothis by clicking the “Create New Dinner” link on our home-page tonavigate to our “Create” form:

    When we click the “Create” button, our new Dinner will be saved inthe database.  We can repeat this multiple times to register severaldifferent Dinners.  Each new Dinner we create will be persisted withinour database and show up in our Home listing of upcoming dinners:

     

    Step 5: Changing our Model

    We are going to continually evolve and refactor our model as ourapplication grows.  The EF code-only library includes some nicedevelopment features that make it easier to coordinate this evolutionwith a development database.

    Adding a new Property to the Dinner Model

    Let’s walkthrough making a simple change to our Dinner class. Specifically, we’ll add an additional property to our Dinner classcalled “Country”:

    Now that we’ve made this change, let’s press F5 in Visual Studio tobuild and re-run the application.  When we do this we’ll see the belowerror message:

    This error message occurs because we’ve changed the structure ofour Dinner class, and our model object is now no longer the same shapeas the “Dinners” table we automatically created within our database. 

    When EF automatically creates a database for you, it by defaultadds an “EdmMetadata” table to the database that tracks the shape of themodel objects that were used to automatically create the databaseschema for you: 

    The error message above occurs when EF detects that you’ve made achange to a model object and it is now out of sync with the database itautomatically created for you. 

    Re-synchronizing our Model Classes with the Database

    There are a couple of ways we can “re-sync” our model objects and our database:

    • We can manually update our database schema to match our models
    • We can manually delete our database file, re-run the application, and have EF automatically re-create the database
    • We can enable a feature of EF code-first that automatically updates our database for us whenever we change our models

    Let’s look at how we can use this last automatic option with our NerdDinner application.

    The RecreateDatabaseIfModelChanges Feature

    CTP 4 of the EF Code First library includes a useful development-time feature that enables you to automatically re-create your databaseanytime you make modifications to your model classes.  When you enableit, EF identifies when any of the model classes that were used toautomatically create a database are modified, and when that happens canre-create your database to match the new model class shape – without you having to take any manual steps to do so.

    This capability is especially useful when you are first developingan application, since it gives you the freedom and flexibility toquickly refactor and restructure your model code however you want -without having to do any manual work to keep your database schema insync along the way.  It works especially well with SQL CE – since it is afile-based database that can be dropped and recreated on the fly inunder a second.  This can enable an incredibly fluid developmentworkflow.

    The easiest way to enable this capability is to add aDatabase.SetInitializer() call to the Application_Start() event handlerwithin our Global.asax class:

    This tells EF to re-create our NerdDinners.sdf database to matchour NerdDinners model anytime our model classes change shape.  Now whenwe re-run our application we will no longer get that error messagetelling us that our model classes and database are out of sync.  EF willinstead automatically re-create a database for us that matches our newmodel class shape, and our application will run fine:

    Seeding Initial Data in Automatically Created Databases

    One of the things you might have noticed in the above screen-shotis that we lost our dinner data when we recreated the database.  This isbecause the automatic “RecreateDatabaseIfModelChanges” behavior isn’tintended for production scenarios where you want to “migrate” existingdata from one schema to another.  Instead it is designed for developmentscenarios where you want the database to be quickly and automaticallyupdated for you – without you having to take any manual steps or specifymigration rules to do so. 

    Note: We are separately working to provide better datamigration support for scenarios where you are working with productiondata and want to version the schema.  We think of that as a differentscenario than this early development-time feature that I’m describinghere.  The data migration capability isn’t enabled yet with this week’sCTP.

    EF supports the ability for us to optionally “seed” our generateddatabase with default/test data anytime the database iscreated/recreated.  I find this feature really useful since it enablesme to refactor a model, and then quickly run the application to try out ascenario – without having to enter in a bunch of test data manually todo so.

    We can “seed” our NerdDinners database with default data by writinga “NerdDinnersIntializer” class like below.  I’m using it to create two“sample dinners” and adding them to our database like so:

    We can then update the Database.Initializer() call we added to ourGlobal.asax to use this “NerdDinnersInitializer” class at startup:

    And now anytime we make a change to one of our NerdDinner modelclasses, the database will be automatically dropped and recreated tomatch our models, and we’ll have two dinners already seeded in thedatabase for testing purposes:

    Easy Refactoring

    The above features make it really easy to evolve and refactor yourcode at development time – without having to use tools or run scripts tomanually keep your database in sync with your code changes.

    Because our model classes, LINQ expressions, and “seed” test dataare all strongly typed, we can also take advantage of refactoring toolsupport inside Visual Studio to quickly and automatically apply changesacross our code base in a quick and easy way.

    Step 6: Adding Validation Rules

    We’ve built a nice, simple data-entry application.

    One problem with it, though, is that we don’t currently have anytype of input validation in place to ensure that fields are filled outcorrectly within our Create Dinner form.  Let’s fix that.

    Adding Validation using DataAnnotations

    Validation rules in an ASP.NET MVC based application are usuallybest expressed within a model.  This enables them to be maintained in asingle place, and enforced across any number of controllers and viewsthat might interact with them.  ASP.NET MVC enables you to implementvalidation rules using a variety of different mechanisms, and isflexible enough to support just about any validation scheme you want touse. 

    ASP.NET MVC 2 includes built-in support for using .NET’s System.ComponentModel.DataAnnotationslibrary of validation rules – which enable you to declaratively applyvalidation rules to model classes using validation attributes.  You canlearn more about this capability in a previous blog post I wrote.  We’ll take advantage of this approach to enable input validation for our NerdDinner application.

    Let’s go back to the Dinner class we defined earlier and add somevalidation attributes to its properties (note: we need to add a “usingSystem.ComponentModel.DataAnnotations” namespace as well):

    The [Required] validation attribute indicates that a particularproperty must be specified.  The [StringLength] validation attributeallows us to indicate a maximum length for a particular stringproperty.  The [RegularExpression] validation attribute allows us toindicate that a particular string property must match a specifiedregular expression in order to be valid – in this case an email address.

    Each of the validation attributes supports an “ErrorMessage”property – which allows us to specify an error message that should bedisplayed if the validation fails.  This can either be hard-coded as astring (like above) or pulled from a resource – enabling it to be easilylocalized.

    Referencing some CSS and JavaScript files

    The last step will be to go back to our Create.aspx view templateand add a reference to a Site.css file in our project, aswell as two