David Brown
David Brown

Reputation: 36239

Generic method is picking up type of base class

I have the following classes (trimmed to only show the basic structure):

public abstract class BaseModel {
    public bool PersistChanges() {
        // Context is of type "ObjectContext"
        DatabaseHelper.Context.SafelyPersistChanges(this);
    }
}

public static class ObjectContextExtensions {
    public static bool SafelyPersistChanges<T>(this ObjectContext oc, T obj) {
        // Persist the object using a transaction
    }
}

[Persistent("LEADS")]
public class Lead : BaseModel {
    // Extra properties
}

public class LeadsController : Controller {
    public ActionResult Save(Lead lead) {
        lead.PersistChanges()
    }
}

My Lead class derives from BaseModel, which contains a method to persist the object's changes to the database using a transaction. I implemented the transactional persist with an extension method. The problem is that by passing this to SafelyPersistChanges in my BaseModel class, the generic T on the extension method is set to BaseModel. However, since BaseModel isn't marked as a persistent object (which it cannot be), the ORM framework throws an exception.

Example:

Lead lead = LeadRepository.FindByNumber(2);
lead.SalesmanNumber = 4;
// Calls "ObjectContextExtensions.SafelyPersistChanges<BaseModel>(BaseModel obj)"
// instead of "ObjectContextExtensions.SafelyPersistChanges<Lead>(Lead obj)"
lead.PersistChanges();

The above block raises the following exception:

Cannot create mapping for type 'SalesWeb.Data.BaseModel' without persistent attribute.

Any ideas?

Upvotes: 1

Views: 1624

Answers (4)

munificent
munificent

Reputation: 12364

You could solve this using the curiously recurring template pattern:

// change your code to this

public abstract class BaseModel<TDerived> where TDerived : BaseModel
{
    public bool PersistChanges() {
        // Context is of type "ObjectContext"
        DatabaseHelper.Context.SafelyPersistChanges((TDerived)this);
        //                                            ^
        // note the cast here: -----------------------|
    }
}

public class Lead : BaseModel<Lead> { }

// the rest of your code is unchanged

That would work, but I'd probably just follow the other suggestions and use a virtual method.

Upvotes: 0

Amy B
Amy B

Reputation: 110071

So, you want a "single" implementation, that varies against a type known by the caller. Sounds like a job for Generics.

public static bool PersistChanges<T>(this T source)
  where T : BaseModel
{
        // Context is of type "ObjectContext"
//static property which holds a Context instance is dangerous.
        DatabaseHelper.Context.SafelyPersistChanges<T>(source);
}

Upvotes: 0

JaredPar
JaredPar

Reputation: 754525

Extension Methods are statically bound at compile time. At the point in which SafelyPersistChanges is called, this is typed as BaseModel and hence your exception. In order to get the behavior you want, you'll either need to do an ugly if statement with lots of casting or force the call to the derived class.

Make PersistChanges an abstract method. Then implement the call in the derived classes with exactly the same code.

public class Lead { 
    public override bool PersistChanges() {
        // Context is of type "ObjectContext"
        DatabaseHelper.Context.SafelyPersistChanges(this);
    }
}

Now this will properly be Lead

Upvotes: 3

sfossen
sfossen

Reputation: 4778

I would have designed this differently, making "public bool PersistChanges()" call a virtual method, that is overridden in each subclass.

Upvotes: 1

Related Questions