HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Wrapping the Sage300 View API with... a Repository

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thesage300withviewwrappingrepositoryapi

Problem

Related: Something like a LINQ provider

I needed to work with the Sage300 View API. I had never worked with it, but my first impression has been that the API is stringly-typed, and makes you write very procedural and repetitive code.

So I decided to make my life easier, and wrap it with a familiar interface:

public interface IRepository where TEntity : class, new()
{
    /// 
    /// Projects all entities that match specified predicate into a  instance.
    /// 
    /// A function expression that returns true for all entities to return.
    /// 
    IEnumerable Select(Expression> filter);

    /// 
    /// Projects the single that matches specified predicate into a  instance.
    /// 
    /// Thrown when predicate matches more than a single result.
    /// A function expression that returns true for the only entity to return.
    /// 
    TEntity Single(Expression> filter);

    /// 
    /// Updates the underlying View for the specified entity.
    /// 
    /// The existing entity with the modified property values.
    void Update(TEntity entity);

    /// 
    /// Deletes the specified entity from the underlying View.
    /// 
    /// The existing entity to remove.
    void Delete(TEntity entity);

    /// 
    /// Inserts a new entity into the underlying View.
    /// 
    /// A non-existing entity to create in the system.
    void Insert(TEntity entity);
}


So, to implement a repository, I make a simple POCO class, and I use a custom MapsToAttribute to tell the "engine" how to map the class and its properties to a View and its fields - note, SageViews is an internal static class exposing nothing but internal const string members:

```
[MapsTo(SageViews.HeadersViewId)]
public class PurchaseOrderHeader
{
[MapsTo("PONUMBER")]
public string Number { get; set; }

[MapsTo("VDCODE")]
public string VendorCode { get; set; }

[MapsTo("PORTYPE")]
public PurchaseOrderType Type { get; set; }

[MapsTo("ONHOLD")]
public boo

Solution

The code looks good, but I can see a couple of things that could be a bit cleaner:

e.g. this method:

public override PurchaseOrderHeader Single(Expression> filter)
{
    var result = Select(filter).ToList();
    return result.Single();
}


result is a poor name IMO as it's a list (even if it only has one item). I think it should be pluralised: results.

There's no need to call .ToList() which I think makes the intermediate variable pointless:

public override PurchaseOrderHeader Single(Expression> filter)
{
    return Select(filter).Single();
}


Regarding the DBLink dependency... I definitely think you should either pass it in as a constructor parameter or create a factory method for your repository. Even when you do that, I don't see why you'd need to store the link as a field which means your Dispose implementation would be unchanged.

FWIW, I would go with a Create method over the constructor as you're doing some work there and I don't know whether any of that takes time... Either way, by having an extra method that the caller has to know about before they can use the instance is always frustrating.

You could create it as an extension method on the DBLink if you want to treat that a bit like a unit of work. You could leave your create + initialize as separate steps, or you could create a static factory method.

public class DBLinkExtensions
{
      IRepository GetPurchaseOrderHeadersRepository(this DBLink link)
     {
         if (link == null) throw new ArgumentNullException("link");
         return new PurchaseOrderHeadersRepository().Compose(link);
         // OR
         // return PurchaseOrderHeadersRepository.Create(link);
     }
}


Then your client code would be:

using (var dbLink = GetTheDBLink(...))
using (var purchaseOrderHeadersRepository = dbLink.GetPurchaseOrderHeadersRepository())
{
    // Your stuff.
}


Just in case you're interested, what you're creating here is called an Anticorruption Layer. You're hiding all of the nastiness of the 3rd party library with a well designed API - a very good idea!

Code Snippets

public override PurchaseOrderHeader Single(Expression<Func<PurchaseOrderHeader, bool>> filter)
{
    var result = Select(filter).ToList();
    return result.Single();
}
public override PurchaseOrderHeader Single(Expression<Func<PurchaseOrderHeader, bool>> filter)
{
    return Select(filter).Single();
}
public class DBLinkExtensions
{
      IRepository<PurchaseOrderHeader> GetPurchaseOrderHeadersRepository(this DBLink link)
     {
         if (link == null) throw new ArgumentNullException("link");
         return new PurchaseOrderHeadersRepository().Compose(link);
         // OR
         // return PurchaseOrderHeadersRepository.Create(link);
     }
}
using (var dbLink = GetTheDBLink(...))
using (var purchaseOrderHeadersRepository = dbLink.GetPurchaseOrderHeadersRepository())
{
    // Your stuff.
}

Context

StackExchange Code Review Q#118970, answer score: 4

Revisions (0)

No revisions yet.