DPS907 notes – Mon Sep 21

Associations – one-to-many, in a web service. Work on Lab 2.

.

Associations between objects (navigation properties)

Most students in the course are familiar with associations between objects. This section, and the next, will refresh your memory. If you need to learn about associations between objects, continue reading and studying.

Depending upon the situation, we will encounter different kinds of associations:

  • one to many
  • many to many
  • one to one
  • self-referencing one to one
  • self-referencing one to many

One to many will be discussed today, and the others in the next class session.

.

One to many

You will often model real-life objects that have associations with other objects, in a one-to-many relationship.

For example:

  • a ‘Program’ has a collection of ‘Subject’ objects,
  • a ‘Supplier’ has a collection of ‘Product’ objects, and
  • a ‘Manufacturer’ has a collection of ‘Vehicle’ objects.

Looking at the other side of the association:

  • a ‘Subject’ object belongs to a ‘Program’, and similarly,
  • a ‘Product’ is made by a ‘Supplier’, and
  • a ‘Vehicle’ is made by a ‘Manufacturer’.

.

Refresh your memory – declaring an association

When writing associated classes, ALWAYS add a navigation property to both associated classes.

Declare a to-one property with the data type of the associated class. For example if a Product is sold by a single Supplier:

// For most one-to-many associations, the to-one end is "required"
[Required]
public Supplier Supplier { get; set; }

.

Declare a to-many property with a collection type of the associated class. For example, a Supplier sells a number of Product items:

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

In a design model class, the collection type will be an ICollection<T>.

In a resource model class, the collection type will be an IEnumerable<T>.

Remember, whenever you declare a collection property, you MUST initialize it in a default constructor.

.

Refresh your memory – set the value of a navigation property

Before setting the value of a navigation property, you must have a reference to an object (of that data type). Although you can create a new object in the statement, you will often have another existing variable for (or reference to) the object.

Set a to-one property as follows. Assume that ‘walmart’ is a Supplier object, and ‘shirt’ is a Product object:

shirt.Supplier = walmart;

Setting a to-many property requires some thought. Do you want to add a new object to the collection? Do you want to replace the existing collection with a new collection? Here are some examples, using the same ‘walmart’ and ‘shirt’ objects, but we also have a collection of toaster objects named ‘toasters’:

// add a new object to the collection
walmart.Products.Add(shirt); 

// replace the existing collection with a new collection
walmart.Products = toasters; 

Important: When you write a statement in code to set one end of the association, do you need to write another statement to set the other end? No.

.

Refresh your memory – getting the value of a navigation property

Getting the value of a navigation property is natural and simple. Just pay attention to data types (an object, or a collection of objects).

Also, when you have a to-one association, it’s easy to ‘walk’ the object graph to get access to properties in the associated object. For example, using the ‘shirt’ object from above, you could get the “Name” or “Address” of the supplier:

string supplierName = shirt.Supplier.Name;

.

AutoMapper usage and tips

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

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 resource 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 resource 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 AssociationsIntro code example. Look in the Vehicle_vm.cs source code file.

.

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 resource model class, add a navigation property for the object. The type is a resource 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 resource 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 AssociationsIntro code example. Look in the Vehicle_vm.cs source code file.

.

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 resource model class, add a navigation property for the collection. The type is a resource 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 resource 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 AssociationsIntro code example. Look in the Manufacturer_vm.cs source code file, and notice the ManufacturerWithVehicles class, which includes a IEnumerable<VehicleBase> collection property.

.

Request a resource and include associated resources

There are two typical ways to configure a request for a resource that includes associated resources:

  1. Add a query string parameter to the URI
  2. Use the new attribute routing feature (covered next)

.

Attribute based routing

This feature is ON by default in our projects. We can use it in combination with the existing convention based routing.

This article by Mike Wasson (on the official ASP.NET Web API web site) fully explains attribute routing. Read it, and begin using the feature in your work.

In the AssociationsIntro code example, look for its usage in the Manufacturers and Vehicles controllers. There are methods that enable a requestor to get an object (or collection) with or without the associated object(s).

.

How to handle the “delete” task

The DbSet<TEntity> Remove() method will remove, or delete, an object.

As you would expect, if you delete an object (that is not associated with another object), the object is deleted. Obviously.

In a one-to-many association – for example, manufacturers and vehicles – what happens when you want to delete an object at the end of the to-many association – for example, a vehicle? That’s simple and intuitive: The (vehicle) object is deleted.

What happens when you want to delete an object at the end of the to-one association – for example, a manufacturer?

Well, the default behaviour of the data context and Entity Framework implementation is to delete the to-one object, and ALL the to-many objects. In our manufacturer/vehicle example, deleting a manufacturer will also delete all its vehicles.

Is that a good idea? It’s very destructive.

Why does it do this? The reason is that the default settings have “cascade delete” enabled.

If you don’t like this behaviour – and we don’t, for most of the scenarios in this course – we can change the setting. How?

  1. In the ApplicationDbContext class, implement the base class’ OnModelCreating() method
  2. In the method, call the base class’ OnModelCreating() method
  3. Then, add a statement that disables the “cascade delete” behaviour

Here’s a code example:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // The default behaviour for the one (Manufacturer) to many (Vehicles)
    // association is "cascade delete"
    // This means that deleting a Manufacturer will cause the Vehicles to be deleted
    // In this code example, we do NOT want that behaviour

    // We cannot do this with a data annotation
    // We must use this Fluent API

    // First, call the base OnModelCreating method,
    // which uses the existing class definitions and conventions

    base.OnModelCreating(modelBuilder);

    // Then, change the "cascade delete" setting
    // This can be done in at least two ways; un-comment the desired code

    // Alternative #1
    // Turn off "cascade delete" for all default convention-based associations

    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

    // Alternative #2
    // Turn off "cascade delete" for a specific association

    //modelBuilder.Entity<Vehicle>()
    //    .HasRequired(m => m.Manufacturer)
    //    .WithMany(v => v.Vehicles)
    //    .WillCascadeOnDelete(false);
}

 

Work on Lab 2

Lab 2 has been posted.

A portion of your work will be checked and graded in class before you leave today.

The rest of the work is due next Monday.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

Advertisements
%d bloggers like this: