Skip to content
Matthieu MEZIL (MSFT) edited this page Jun 6, 2015 · 4 revisions

In the previous workshop, you saw how to create some service methods.

However, in a SOA approach, you don't want to run the method on the client.
You should call a service and run methods on the server.

This allows to reduce significantly deployment issues.

With WAQS, you can specify that a service method or a calculated property or a validate method (we will see validate methods later) have to be executed on the server and not in the client.

To do it, you can use a WAQS attribute: NotApplicableOnClient.

If your method needs to CRUD, the attribute is useless, your method will be executable in the server only.

You can create an InvoiceSpecifications class:

public static class InvoiceSpecifications
{
    public static Invoice AddInvoice(int orderId, INorthwindService context)
    {
        var order = context.Orders.Select(o => new { Order = o, CompanyName = o.Customer.CompanyName, ContactName = o.Customer.ContactName }).FirstOrDefault(o => o.Order.Id == orderId);
        if (order == null)
        {
            throw new ArgumentException("The is no order for this id", "orderId");
        }
        Invoice invoice = new Invoice
        {
            OrderId = order.Order.Id,
            CustomerId = order.Order.CustomerId,
            CustomerCompanyName = order.CompanyName,
            CustomerContactName = order.ContactName,
            Total = order.Order.GetTotal()
        };
        foreach (var od in context.OrderDetails.Where(od => od.OrderId == orderId))
        {
            invoice.InvoiceDetails.Add(CreateInvoiceDetail(od));
        }
        context.Invoices.Add(invoice);
        context.SaveChanges();
        return invoice;
    }

    [NotApplicableOnClient]
    public static InvoiceDetail CreateInvoiceDetail(this OrderDetail od)
    {
        return new InvoiceDetail
        {
            OrderDetailId = od.Id,
            Quantity = od.Quantity,
            UnitPrice = od.UnitPrice,
            Discount = od.Discount,
            Amount = od.GetAmount()
        };
    }
}

With the following usings:

using WAQS.DAL.Interfaces;
using WAQS.Specifications;
using WAQSWorkshopServer.Service;
using WAQSWorkshopServer.Service.Interfaces;

So, for a performance aspect, the previous code gets the customer company name and contact name in the same query than the Order without getting anything else from Customer.

However, IMHO, it would be better to use the With method I introduced with the calculated property workshop.

You can also use With methods in the server.

So in OrderSpecifications class, add these two methods:

    public static string GetCustomerCompanyName(this Order order)
    {
        return order.Customer.CompanyName;
    }

    public static string GetCustomerContactName(this Order order)
    {
        return order.Customer.ContactName;
    }

Then, after running WAQS / Update Solution Generated Code, you can use this code which is simpler IMHO:

public static Invoice AddInvoice(int orderId, INorthwindService context)
{
    var order = context.Orders.IncludeOrderDetails().WithCustomerCompanyName().WithCustomerContactName().FirstOrDefault(o => o.Id == orderId);
    if (order == null)
    {
        throw new ArgumentException("The is no order for this id", "orderId");
    }
    Invoice invoice = new Invoice
    {
        OrderId = order.Id,
        CustomerId = order.CustomerId,
        CustomerCompanyName = order.CustomerCompanyName,
        CustomerContactName = order.CustomerContactName,
        Total = order.GetTotal()
    };
    foreach (var od in order.OrderDetails)
    {
        invoice.InvoiceDetails.Add(CreateInvoiceDetail(od));
    }
    context.Invoices.Add(invoice);
    context.SaveChanges();
    return invoice;
}

Then, add a new calculated property to Order:

public static bool GetHasInvoice(this Order order)
{
    return order.Invoice != null;
}

After running WAQS / Update Solution Generated Code, WAQS generates a proxy in the client and some methods to call our server service methods from the client context.

In this workshop, you will add a new button to create an Invoice in the CustomerWindow.

Then, update the customer load query in the CustomerViewModel.

Customer = await _context.Customers.AsAsyncQueryable().FirstOrDefault(c => c.Id == customerId).IncludeOrdersWithExpression(orders => orders.IncludeOrderDetails().WithHasInvoice()).ExecuteAsync();

Now use this code:

private RelayCommand _createInvoice;
public ICommand CreateInvoice
{
    get { return _createInvoice ?? (_createInvoice = new RelayCommand(() => CreateInvoiceAsync().ConfigureAwait(true), () => SelectedOrder != null && !SelectedOrder.HasInvoice)); }
}
private void RefreshCreateInvoiceCanExecute()
{
    if (_createInvoice != null)
    {
        _createInvoice.RaiseCanExecuteChanged();
    }
}

private async Task CreateInvoiceAsync()
{
    SelectedOrder.HasInvoice = true;
    RefreshCreateInvoiceCanExecute();
    await _context.AddInvoiceAsync(SelectedOrder.Id);
}

Add a call to RefreshCreateInvoiceCanExecute on the SelectedOrder set.

And here you are.

Note that, because the Invoice is created in the server from a DB query, the created invoices won't include changes made in the Customer Window (not saved)

As usual, you can download the code here.