BTI420 notes – Tue Feb 10

Working with your classes in an Entity Framework environment. AutoMapper and associated objects. Data annotations. Quiz.

.

Quiz today

Near the end of the class/session, we’ll have a quiz.

The quiz will be ten (10) minutes in length.

You will answer two or three questions, on a single sheet of paper. The questions will cover topics, concepts, definitions, descriptions, and so on. Think about a question-and-answer session in a job interview. Those are the kinds of questions you can expect.

.

Working with your classes in an Entity Framework environment

Use the AssocationsIntro code example to help you learn the topics in this section.

The AssociationsIntroSolutions code example includes solutions to these topics.

The following information will help you when working in an Entity Framework (EF) environment.

You must be aware of the following:

  • The Entity Framework infrastructure provides runtime mapping between your design model classes and the entity classes in the conceptual model (and implemented by the relational database management server)
  • An object (an instance of a model class) can be used in methods that need a TEntity object
  • Collections of objects are typically not generic lists; see below for more

.

The data context is the gateway from your app to your database server. Its data type is DbContext. It is the object that your app’s code uses when working with the persistent store.

As you know, you must create a class that inherits from DbContext. In that class, you write a constructor, and include properties for each data model class collection. The data type of the properties is DbSet<TEntity>, where TEntity is the name of one of your model classes.

DbSet<TEntity>…

  • inherits from DbQuery<TEntity>
  • implements IDbSet<TEntity>
  • implements IQueryable<TEntity>
  • implements IEnumerable<TEntity>
  • implements IQueryable
  • implements IEnumerable

.

In the data service / manager (or repository) class, you typically define and create an instance of the data context:

private DataContext ds = new DataContext(); // ds simply means “data store”

Assume that our app domain data model covers administrative objects in the School of ICT. For example Program, Subject, Employee, Semester, Course.

If you want to work with the Program collection, you use a statement like this:

var fetchedObjects = ds.Programs;

As noted earlier, the data type of programs is DbSet<TEntity>, specifically DbSet<Program>. When you are ready to return the collection, you MUST transform it into a collection that’s based on a view model class.

You MUST NOT leak Entity Framework types to controller classes. That’s why we use AutoMapper and do this:

return Mapper.Map<IEnumerable<ProgramBase>>(fetchedObjects);

.

Fetching one single matching object

In the past, you used the SingleOrDefault() method to fetch a single matching object from the persistent store. You can continue to use that.

However, you can use the Find() method, if the following two conditions are true:

  1. you are working with a DbSet<TEntity>
  2. you are working with the primary key property (the one named “Id” or “<entityName>Id”)

Its syntax is simpler. Its argument is the value of the primary key. Compare the following:

var supplier = ds.Suppliers.SingleOrDefault(i => i.Id == newItem.SupplierId);
// ...or...
var supplier = ds.Suppliers.Find(newItem.SupplierId);

.

More about SingleOrDefault() and Find()

There are several extension methods that a beginner may view as similar:

  • Find
  • First
  • FirstOrDefault
  • Single
  • SingleOrDefault

We will use only two:

  • As noted above, if we are working with the primary key of a DbSet<TEntity>, we can use Find
  • Otherwise, we must use SingleOrDefault

Why?

A successful SingleOrDefault expects to return exactly one object.

An unsuccessful SingleOrDefault will return null.

This is the behaviour we want, when we are working with a (primary key) identifier. In our code, we simply need to do one check/test – “if null” – to determine how to use the result.

In contrast, FirstOrDefault is used when the data source contains zero or more matching objects (that meet the fetch condition). If successful, it returns the first object found. If unsuccessful, null is returned. As a result, you would have to perform two checks/tests – number of objects, and null state.

First or Single should not be used, when following our coding standards. If First or Single is unsuccessful, the statement it appears in will raise an exception. In other words, it will not assign null as the return value, as with the related methods FirstOrDefault or SingleOrDefault.

.

Filtering

Within a manager/repository method (and before returning the results), we often need to perform other tasks on a collection, such as filtering or sorting. For example, assume that we wish to select only those Program objects that have a Credential property value of “Diploma”:


var fetchedObjects = ds.Programs.Where(c => c.Credential == "Degree");

Reminder: “c” is the range variable.

It represents an object in the Programs collection. Its type is Program.

The letter “c” does not have any special significance.

Like other variables, its name is meaningful to you only. Choose your own name. It can be one or more characters in length.

.

Sorting 

In this example, assume that we wish to sort the Program collection by a property named Code:


var fetchedObjects = ds.Programs.OrderBy(c => c.Code);

The OrderBy extension method is defined in the Queryable class (look at the inheritance and implementation list above). This method can be used on any object of type IQueryable<TEntity>. Its return type is IOrderedQueryable<T>.

.

Filtering and sorting

You can combine tasks by chaining the filtering and sorting tasks above (using the “fluent” syntax). The statement below has been wrapped for clarity:


var fetchedObjects = ds.Programs
    .Where(prog => prog.Credential == "Degree"))
    .OrderBy(sort => sort.Code);

Here’s how to read, in English, this statement:

Fetch the Program collection…
but only where a Program object’s Credential property value…
matches the string “Degree”…
then sort the result by the Program object’s Code property value.

.

Filtering, and associated objects

In the following example, assume that we wish to select only those Program objects that include a Subject object with a specific identifier. Let’s build the statement in two steps.

By default, when we fetch an object that is related/associated with another object and/or collection, the object that is fetched does not include the related/associated objects. That is the default behaviour of the way we have configured the DbContext object.

Therefore, to build the statement, we first must use the Include() method, as shown next:


var fetchedObjects = ds.Programs.Include("Subjects");

Its return type is DbQuery<TEntity>. (It’s a collection.)

The second task is to add the filtering to the statement, as shown next:


// Assume that "id" has been passed into the method
var fetchedObjects = ds.Programs.Include("Subjects")
    .Where(prog => prog.Credential == "Degree");

The range variable – prog – represents an object in the Programs collection. Its type is Program.

The return type of this statement is IQueryable<TEntity>.

.

Filtering and sorting, and associated objects

You can combine tasks by chaining the filtering and sorting tasks above (using the “fluent” syntax). The statement below has been wrapped for clarity:


var programs = ds.Programs
    .Include("Subjects")
    .Where(prog => prog.Credential == "Degree")
    .OrderBy(sort => sort.Code);

Here’s how to read, in English, this statement:

Fetch the Program collection…
and for each Program object…
include the related Subject object(s)…
but only where a Program object’s Credential property value…
matches the string “Degree” …
then sort the result by the Program object’s Code property value.

.

AutoMapper and associated entities

Use the AssocationsIntro code example to help you learn the topics in this section.

The AssociationsIntroSolutions code example includes solutions to these topics.

AutoMapper is a convention-based mapper. Between classes, it will map/convert properties that have the same name and type.

It also works nicely with associated entities. The notes below discuss three scenarios:

  1. Mapping the individual properties of a related object (in a ‘to-one’ association)
  2. For an object, mapping an associated object
  3. For an object, mapping an associated collection

.

Mapping the individual properties of a related object (in a ‘to-one’ association)

If you have an entity that has an associated object – for example, a Subject belongs to a Program, or a Product belongs to a Supplier – you may want to return individual properties from its associated object.

To do this, you need to do four tasks:

1. In a new view model class, add a property with a type that matches the property in the associated type. The property name must be a composite of the design model class name and the property name.

2. Add an AutoMapper map to Global.asax.cs, which maps the design model class to the new view model class that you created in step 1 above.

3. In the manager/repository, when the code fetches the object, add the Include() extension method to fetch its associated object.

4. In the controller, add a method that will call the manager/repository method.

Here’s how these code changes work together. Click to open it in a new tab/window:

AutoMapperComposite

.

You can see more in the AssociationsIntroSolutions code example. Look in the Subject_vm.cs source code file, and notice the composite-named properties from the Program class.

.

For an object, mapping an associated object 

If you have an entity that has an associated entity – for example, a Subject is associated with a single Program – you may want to return an object with its associated object.

To do this, you need to do four tasks:

1. In a new view model class, add a navigation property for the object. The type is a view model class type. The property name must match the property name in the design model class.

2. Add an AutoMapper map to Global.asax.cs, which maps the design model class to the new view model class that you created in step 1 above.

3. In the manager/repository, when the code fetches the object, add the Include() extension method to fetch its associated object.

4. In the controller, add a method that will call the manager/repository method.

.

You can see more in the AssociationsIntroSolutions code example. Look in the Subject_vm.cs source code file, and notice the SubjectBaseWithProgram class, which includes a ProgramBase property.

.

For an object, mapping an associated collection

If you have an entity that has an associated (related) collection – for example, a Program has a collection of Subjects, or a Supplier has a collection of Products – you may want to return an object with its associated collection.

To do this, you need to do four tasks:

1. In a new view model class, add a navigation property for the collection. The type is a view model class type.  The property name must match the property name in the design model class.

2. Add an AutoMapper map to Global.asax.cs, which maps the design model class to the new view model class that you created in step 1 above.

3. In the manager/repository, when the code fetches the object, add the Include() extension method to fetch its associated collection.

4. In the controller, add a method that will call the manager/repository method.

Here’s how these code changes work together. Click to open it in a new tab/window:

AutoMapperAssociatedCollection

.

You can see more in the AssociationsIntroSolutions code example. Look in the Program_vm.cs source code file, and notice the ProgramBaseWithSubjects class, which includes a IEnumerable<SubjectBase> collection property.

.

Data annotations overview

Data annotations are descriptive text attributes that we add to properties in a class. A data annotation is located just before/above the property declaration.

Textbook coverage:

Chapter 6.

You can skim the section titled “CUSTOM VALIDATION LOGIC”.

Data annotations can help with:

  • property constraint and limits
  • input data validation
  • property display and formatting in views
  • communicating error messages to users

Some data annotations are intended only for design model classes, while others are for use with view model classes. And, some work with both.

Their overall benefit is to reduce the amount of code that has to be written to handle typical data management scenarios.

Please note that data annotations DO NOT replace the need to validate data against business rules and/or application logic. You still need to do that. Data annotations help most by improving the quality of input data, so that it can then be inspected for its integrity and appropriateness.

.

We will build some examples in class.

Note: The Google Chrome browser do a good job of rendering the <input> fields. Some IE and Firefox versions do not offer the same support, unless you use a polyfill.

.

Data annotations for design model classes

In your design model classes source code file, add a ‘using’ directive for this namespace:

System.ComponentModel.DataAnnotations

Useful data annotations for design model classes include:

[Required] (implemented as ‘not null’ in the database)

[StringLength(n)] (implemented as a specific ‘varchar’ value in the database)

You can comma-separate multiple data annotations.

Value types (e.g. int, double) CANNOT use the [Required] attribute.

There are a few others that you may use occasionally; we may discuss them later in the course.

Do you need a ‘default value’ for a property? A data annotation will not help.

Instead, as you have learned, simply create a class constructor, and set the property’s default value there.

.

Data annotations for view model classes

In your view model classes source code files, add a ‘using’ directive for this namespace:

System.ComponentModel.DataAnnotations

Useful data annotations for view model classes include:

[Required]

[StringLength(n)] or [StringLength(n, MinimumLength = m)]

[Range(min, max)] (int or double types) 

[Display(Name=”Improved property display name”)]  

[DataType(DataType.Password)] (others include EmailAddress, Url, Currency, Date, Time, and MultilineText)

[RegularExpression(“regex“)] (a regular expression; omit the ^ and $ delimiters, because they’re assumed)

In a regular expression, sometimes you need the backslash character (e.g. \d). In a .NET Framework string, the backslash is a quote character. So, you either precede the entire string with an at sign (@), or use a double backslash (\\d).

Common situations:

[0-9]+ – digits only
[a-zA-Z]+ – letters only
[0-9a-zA-Z]+ – digits and letters only

[Compare(“PropertyName”)] (often used for password entry; compares this field’s value with the value in PropertyName)

[HiddenInput] (rendered as <input type=hidden…, often used for an object’s identifier that will not be shown to the browser user)

[Editable(false)] (not rendered)

[ReadOnly(true)] (not rendered) (in System.ComponentModel namespace)

[ScaffoldColumn(false)] (not rendered)

As before, you can comma-separate multiple data annotations, and value types (e.g. int, double) CANNOT use the [Required] attribute (but the [Range(min, max)] attribute effectively solves that problem).

.

Custom error messages

All attributes accept a parameter named “ErrorMessage”. The value is a custom error message for the user. For example:

[Range(2, 6, ErrorMessage=”Selected gizmos must range from 2 to 6″)]

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: