Wednesday, September 9, 2009

Persistent interfaces, part 1

First of all, interfaces can’t be persistent. An interface represents no more than a contract that must be implemented by some type. It is an abstract term. Speaking about persistent interfaces within DataObjects.Net 4 project we mean that we want OR/M to support the following scenarios (they are our goals):

1. Reference:

public class Animal {
  public IOwner Owner { get; set; }
}

2. Collection of references:

public class Person {
  public EntitySet<IAnimal> Pets { get; private set; }
}

3. Query:

Query<IAnimal>.All.Where(a => a.Owner = myOwner);

 

In order to achieve the goals we should keep in mind the following facts:

1. DataObjects.Net 4 supports multiple persistent hierarchies. This means that one may define hierarchies with different key structure, e.g.:

[HierarchyRoot]
public class Cat : Entity, IAnimal
{
  [Field, Key]
  public int Id { get; private set; }
}

[HierarchyRoot]
public class Dog : Entity, IAnimal
{
  [Field, Key]
  public Guid Id { get; private set; }
}

In Cat class the key field is of type Int32, but in Dog class it is of type Guid. As any reference field is stored as a copy of referenced object key field(s), we can’t handle a reference to IAnimal because we can’t decide which type of reference field for underlying SQL table to choose.

public class Person : Entity
{
  public IAnimal Pet { get; set; }
}

So the first rule is: all persistent types that implement given interface must have equal key structure. Otherwise, reference to interface implementor can’t be persisted and query for interface implementors can’t be executed.

2. DataObjects.Net 4 supports various types of key generators. Key generator acts as a source of unique keys for one or more hierarchies. It can be shared between hierarchies if they have equal key structure. If key generator is not specified manually, default key generator implementation is used but this is true only for simple key structure, i.e. key consists of one primitive field.
Going back to interface support: if two types has equal key structure but have different key generators then instances of these types might have equal keys. If so, when attempting to resolve the reference property we can’t decide which instance is referenced: instance of Dog type or instance of Cat type. This is true for the case when key doesn’t contain type discriminator (TypeId in case of default type discriminator in DataObjects.Net 4).

So, the second rule is: all persistent types that implement given interface must have the same key generator if key doesn’t contain type discriminator. Otherwise, we can’t decide which type to construct while trying to resolve a reference property of interface type.

3. Interface could be implemented explicitly. Entity class can contain for example Name property and explicitly implemented IHasName.Name property. In this case when querying for IHasName interface we must use explicitly implemented property instead of regular one. This means that we must detect this cases and provide the information how interface is mapped to its implementor.

CodeProject

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. I haven't had time to fiddle with 4.0 (we're still involved with 3.9 dev until 4.0 is more mature).

    But the question is, will 4.0 create indexed views for persistent interfaces?

    ReplyDelete
  3. Hello Ara,

    By default DO 4 doesn't create indexed views for persistent interfaces. But if you want DO 4 to do so, mark interface with MaterializedView attribute, e.g.:

    [MaterializedView]
    public interface IPerson : IEntity {
    ...
    }

    In this case DO 4 will try to create corresponding IndexedView/MaterializedView for IPerson interface. If Storage doesn't support indexed views, regular table will be created for the interface and its contents will be automatically updated during persist routine.

    ReplyDelete