2
votes

I need to develop an application that, for different customers, needs to target different existing legacy databases as persistence store, or that could also be able to run completely stand-alone with its own independent database.

So my approach is:

  • Develop the domain model independently from the final persistence store
  • Use an EF Code-First repository implementation to map it to its own standalone database if needed
  • Use other repository implementations to map it to the legacy databases systems if needed

I know for a fact that one of the targetted existing systems is a CRM 2011 system. The "CRMRepository" implementation would best use the Microsoft CRM SDK I guess, instead of directly targetting the underlying SQL database.

But, CRM uses GUIDs as its primary keys for all its entities, while the other database systems will mostly use integers.

So I'm kinda confused on what the best approach is to design my domain entities to later not run into problems when mapping it to the persistence stores in the repository implementation.

A typical domain entity would be

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
  • For the standalone solution: use a code-first based EF repository, no problem
  • For targeting an existing database with ints as Primary Keys: map it to the correct primary key property, no problem
  • However, how to target a CRM backend ? A "Person" entity would typically map to the "Account" entity in CRM, however there the PK is accountid, which is a GUID, as with all other CRM entities.

Should I change my domain entities to use strings as primary key properties, so it can accomodate all kinds of datatypes, and then perform the conversion to and from the correct datatype inside each repository implementation ? Or are there other and better ways to tackle this ?

One of the things I thought of would be to declare all my 'Id' properties as type object, and then use the repository pattern along with Automapper to implement specific implementations and map my domain objects to for instance EF Entities (which would then have PKs of type int) in a EFRepository implementation.

For the CRM implementation of the repository, the repo would use the CRM SDK and map the 'object Id' on the GUIDs internally used by CRM.

Could that be a viable solution, or is it too far fetched ?

Thanks

EDIT 1: I'm open for commercial solutions. Could the use of LLBLGen be an option here ? Have zero experience with it but it looks like it does not allow you to reuse the same domain definition into separate "repository" implementations, or am I wrong ?

EDIT 2: Current solution structure overview, trying to follow onion architecture. Different repository implementations would go into "Infrastructure". Repository implementation would then be "pluggable" by customer by means of DI.

Solution structure, trying to follow onion architecture

3
Have you considered using Guids as the key in EF and mapping ints to Guids rather than trying to get Guids into ints - perhaps with a database view for each table? stackoverflow.com/questions/19656213/…Colin
I'm not trying to get GUIDs into ints. I'm trying to figure out what datatype to use for the "key" values in my domain model, knowing for a fact that some users of the application will use a backing store with integers as PK types, while others will use a backing store with guids. I'm trying to figure out if there's a way to avoid another layer of abstraction/mapping between my domain model objects and the persistence store layer objects (ORM or whatever)tjeuten
Is your domain model essentially just in-memory business objects that know how to save themselves to the correct repository?Daryl
See my edited question (Edit 2). I'm trying to follow the onion architecture. Domain objects have business rules and validation and stuff, but NO persistence methods (i.e. no Save() method). CRUD is done in the repository implementations, in their turn called by the "Service" layertjeuten
The "Implementations" folder is there to accommodate customer-specific implementations of any kind of interfacetjeuten

3 Answers

1
votes

Looks like you have two options.

  1. Define Id as an Object, then it doesn't matter what the repository uses, it'll just expect it to be the same time that it saves. This comes at the small cost of requiring explicit casts and handling failures.

  2. Create an int Id and a Guid GuidId. This removes the cost of the explicit casts and may help in type safety. Also means you'll have issues if some other system uses a long or a string as the id.

0
votes

I had a similar situation. Different types of companies had different information, were handled differently, and mapped to older legacy tables with ints and guids as primary keys etc.

What I ended up going with was a base (set to abstract in EF) table with a GUID primary key and all shared common data), I then had an inheriting table for each type (primary key of these tables is foreign key on base). In these I am able to declare any specific information about that specific type and link them to other older or new tables.

In your case, you could have a base Customer Type, but then declare as many different types of customers as you like and extend and link them as and when you need. You can then target them as such:

var baseCust = db.Customers.FirstOrDefault(x => x.Id == someId);
if(baseCust is CustTypeA)
{
   // Access extended properties and Legacy tables (via lazy loading)
   var custA = (CustTypeA)baseCust;
   custA.SomeExtendedProperty = blah;
   var oldCompletedOrders = custA.Orders.Where(x => x.Completed).ToList();
   //etc

This gave us the flexibility to move forward and access all the old rubbish that they wanted to see.

0
votes

How about having all my domain objects inherit from an abstract base class Entity and then use a property "public TKey Id {get; set;}". Or even on a per class basis if I am to allow different types of keys on different domain objects. Would that be a viable solution, keep in mind that the repository implementation could be anything ranging from EF to NHibernate to a custom repo like CRM?

You could implement a few interfaces and a base repository (I've read about something like this else where.) Please consider the following linqpad script (I only used dump for verification purposes):

void Main()
{
    var personList = new List<PersonSql>
    {
        new PersonSql
        {
            Id = 1,
            FirstName = "Some",
            LastName = "Person"
        }
    }.AsQueryable();

    var repo = new PersonRepo();
    repo.Query = personList;

    repo.GetById(1).Dump();
}

// Define other methods and classes here

//entity base
class BaseEntity<TKey>
{
    public TKey Id { get; set; }
}


//common constructs
class PersonBase<Tkey> : BaseEntity<Tkey>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

//actual entity with type of int as the id
class PersonSql : PersonBase<int>
{

}

//and so on
class PersonNoSql : PersonBase<string>
{

}

//likley you would generate these or code them based on your data source when creating your mapping classes.

//repositories

interface IRepository<TEntity, TKeyI>
    where TEntity : BaseEntity<TKeyI>
{
    IQueryable<TEntity> Query {get; set;}
    TEntity GetById(TKeyI key);
    //Other Repository Methods here
}


abstract class RepoBase<TBaseEntity, TKeyB> : IRepository<TBaseEntity, TKeyB>
    where TBaseEntity : BaseEntity<TKeyB>
{
    //Base Implementations Here
    public IQueryable<TBaseEntity> Query { get; set; }
    public virtual TBaseEntity GetById(TKeyB key)
    {
        throw new NotImplementedException();
    }
}

abstract class PersonRepoBase<TkeyType> : RepoBase<PersonBase<TkeyType>, TkeyType>
{
    public override PersonBase<TkeyType> GetById(TkeyType key)
    {
        //Get a person.
        throw new NotImplementedException();
    }
}

//class PersonRepo : RepoBase<PersonNoSql, string>
//{
//  public override PersonNoSql GetById(string id)
//  {
//      throw new NotImplementedException();
//  }
//}

class PersonRepo : RepoBase<PersonSql, int>
{
    public override PersonSql GetById(int id)
    {
        return Query.First(x => x.Id == id);
        throw new NotImplementedException();
    }
}

What do you gain doing it this way? Well, its a repository. Its strongly typed with Generic Constraints. My initial thought for doing it this way was to have a base implementation of GetById, but my compiler was not intelligent enough to determine they TKeyB between the BaseRepo and BaseEntity as Constrained on BaseRepo were the same type. So it didn't give me the benefit of a base implementation. All the same, maybe you could give it a try and refine to suit your needs.

However, it may just be better to do as other have suggested and deal with having the Id as an object on your home domain for a given model.