Programming Against the ADO.NET Entity Framework (Cont.) A Sample Entity Data Model For the purpose of illustration in this article, I’ll use the sample data model shown in Figure 2. This model represents various types of athletic events, like bike races, running events, and triathlons. There are two base types in this model: Event, which encapsulates the attributes common to all athletic events, and Participant, which represents a participant in one or more events. The model declares a many-to-many relationship between Events and Participants called Event_Participant.  Figure 2: This sample data model describes athletic events and their participants.Both Event and Participant have sub-types. Event is the parent for sub-types BikeEvent, RunEvent, SwimEvent, and TriathlonEvent. Each of these sub-types augments the base type with new properties. Participant has a sub-type called Volunteer: This is used to represent participants who volunteer, as opposed to compete, in the events. Note that Volunteer does not add any new fields to its base type, Participant. | " | In cases where the application does not require objects, the EntityClient layer provides a lower-level entry point that offers the benefits of entities without the overhead of object materialization.
| " |
The model also declares an entity container called TriEventsEntities, which contains entity sets for Events and Participants and an association set for the Event_Participant association. Figure 2 does not show the entity container, but it is a fundamental part of the model. The ADO.NET Entity Framework supports several ways to map a model like this one to relational tables. I happened to use a Table-per-Type (TPT) mapping (a table for each base type and a table for each sub-type that stores just the additional properties declared by the sub-type). For the purposes of this article, the mapping strategy is irrelevant: the ADO.NET Entity Framework supports the same programming patterns regardless of how you map the model. Programming with Object Services To program against the model with Object Services, you need data classes that correspond to the types declared in the model. The ADO.NET Entity Framework includes tools that can generate these classes from the model definition. Alternatively, you can code these classes by hand (doing so requires you to implement a specific interface mandated by the ADO.NET Entity Framework). I’ve based the code snippets shown in this article on classes generated by the ADO.NET Entity Framework’s tools. Here is a code snippet that enumerates entities in the model: using (TriEventsEntities entities = new TriEventsEntities()) { // Enumerate entities foreach (Event e in entities.Events) { Console.WriteLine("Event Name: {0}", e.EventName); } }
The preceding snippet is all that’s required to connect to the model and enumerate the Event entities within it. Dissecting this code highlights several important aspects of working with the ADO.NET Entity Framework. The first line of code creates an instance of the TriEventsEntities class. The ADO.NET Entity Framework tools generated this class and it corresponds to the entity container declared in the model. It inherits from the Object Services base class, ObjectContext, and you can loosely refer to it as the “object context” for the model. You can think of this class as a starting point for interaction with the model-It encapsulates both a connection to the database and the in-memory state required to do change tracking and identity resolution for the objects that result from queries. It is instantiated within a using block, which ensures that it is disposed of properly at the end of the code. Within the using block, the foreach loop iterates over objects in the Events property in the object context. This property represents the contents of the Events entity set (the generated object context class has a property for each entity set in the container). Iteration over the Events property sends a query for all Events to the database. The ADO.NET Entity Framework materializes the results of this query into objects of the generated Events class. Within the foreach loop, the code simply prints the value of one of the entity’s properties. You could easily imagine more sophisticated processing logic here. At this point, you may be wondering how the ADO.NET Entity Framework establishes the connection to the database because the code snippet does not include any connection information. The default constructor generated on the object context class, TriEventsEntities, looks for a named connection string in the App.config file with the same name. The ADO.NET Entity Framework tools conveniently create the TriEventsEntities connection string in the App.config file when generating the class. This connection string will look something like the following: metadata=.\TriEvents.csdl|.\TriEvents.ssdl|. \TriEvents.msl;provider=System.Data.SqlClient; provider connection string="Data Source=.\SqlExpress; Initial Catalog=TriEvents; Integrated Security=True; MultipleActiveResultSets=true"
You can think of this connection string as consisting of three parts. The first part specifies a list of metadata files; these files define the model, the database schema, and the mapping between the two in a format defined by the ADO.NET Entity Framework. The second part of the connection string specifies which ADO.NET data provider to use to connect to the database. This example specifies the SqlClient data provider. The final part of the connection string is really another connection string: the one the data provider uses to connect to the database. The ADO.NET Entity Framework uses the three parts of the connection string to establish a connection to a model: it uses the metadata files to load the model and mapping information, it uses the data provider name to instantiate the data provider, and it uses the provider’s connection string to connect to the database. Navigating Relationships The Entity Data Model contains an explicit relationship between Events and Participants. The Event and Participant types have navigation properties that traverse this relationship and allow you to find entities on one end of the relationship when you are holding a reference to the other end. For example, given an Event object, I can use the Participants navigation property to find the related Participant entities. The following code snippet shows how (in this and subsequent code snippets, the code shown goes within the using block declared in the previous code snippet; the using block is omitted here for brevity): // Navigate relationships explicitly foreach (Event e in entities.Events) { Console.WriteLine("Event Name: {0}", e.EventName);
e.Participants.Load();
foreach (Participant p in e.Participants) { Console.WriteLine("\tParticipant:{0}", p.ParticipantName); } }
This code navigates through the events and prints the related participants for each one. As in the previous example, the foreach loop that iterates over the Events results in a database query for all events. By default, when issuing such a query, the ADO.NET Entity Framework does not pull in related entities (doing so would add overhead that is unnecessary, unless you are going to access those related entities). Therefore, to obtain the related Participant objects, the code makes a call to the Load() method on the Participants collection on each Event entity. This issues a separate query to the database to retrieve the participants associated with each event. Thereafter, you can enumerate the participants with a foreach loop. If you know you will need to pull in related entities, you can take advantage of an Object Services feature called Relationship Span and avoid having to issue separate queries. The following code snippet illustrates how to retrieve Event entities and the related Participants at the same time: // Relationship span foreach (Event e in entities.Events.Include("Participants")) { Console.WriteLine("Event Name: {0}", e.EventName);
foreach (Participant p in e.Participants) { Console.WriteLine("\tParticipant:{0}", p.ParticipantName); } }
In the outer foreach loop, the code calls the Include() method to specify which related entities the query should retrieve. The Include() method takes a text representation of a path across one or more navigation properties (here you reference the “Participants” navigation property). In models that have several levels of relationships (for example, Categories related to Products related to Suppliers), you can use the Include method to traverse more than one level. Because the code called the Include() method in the outer enumeration, it is not necessary to call the Load() method before enumerating the Participants on each Event. | & | | Composing Queries
The ObjectQuery<T> class supports query composition: You can compose any ObjectQuery<T> with additional query operators to form a new query. You do this via query-builder methods that correspond to the various query operators. For example, the ObjectQuery<T> class contains a Where() method that takes a predicate. It composes this predicate with the original query to form a new, more restrictive query. ObjectQuery<T>’s query builder methods support both LINQ query specification (via lambda expressions) and Entity SQL fragments. |