tags:

views:

617

answers:

2

I am extending this DataContext entity, which looks sort'a like this:

namespace Entities
{
    public class User
    {
        public Int32 Id { get; set; }
        public String Username { get; set; }
    }
}

.. Like so:

public class User : Entities.User
{
    new public Int32 Id
    {
        get { return base.Id; }
    }


    public void Insert()
    {
        using (var dc = new DataContext())
        {
/*
The "this" keyword should match the type that InsertOnSubmit() expects.
And it does. But I get the following error:

System.NullReferenceException: {"Object reference not set to an instance
of an object."}
*/
            dc.Users.InsertOnSubmit(this); // Exception occurs here

            dc.SubmitChanges();
        }
    }
}

I am using the custom User class like so:

var u = new User { Username = "Test" };

u.Insert();

What I don't get is this: I have instantiated the class, so why am I getting a NullReferenceException?


Update:


Extending entity class: overriding a property with an enumerator while still being able to use the "this" keyword on the Insert/Update and DeleteOnSubmit methods on a DataContext instance

enum AccessLevels
{
    Basic,
    Administrator
}


namespace Entities
{
    public class User
    {
        public Int32 Id { get; set; }
        public String Username { get; set; }
        public Int32 AccessLevel { get; set; }
    }
}

How would I extend or alter the above entity class and implement the AcessLevels enumerator, replacing the AccessLevel property?--this without altering the signature of the entity class, so I'm able to use the "this" keyword on Insert/Update and DeleteOnSubmit methods on a DataContexts.

+3  A: 

You can't extend LINQ-to-SQL entity types in this way via inheritance - you should instead use a partial class to add extra methods to the existing generated entity. Because LINQ-to-SQL supports inheritance (for discriminated tables, etc), it expects an exact match to a known entity type - not unexpected subclasses.

i.e.

namespace Entities {
    partial class User {
        /* your extra method(s) here */
    }
}

In the above, this is combined with the partial class in the designer.cs to create you type.

The other way to do this (if partial class isn't an option) is via an extension method.

static class EntityExtensions {
    public static void SomeMethod(this User user) {...}
}

If there are methods common between types, you can do this by declaring an interface, using extension methods on that interface, and using partial classes to add the interface to the specific types:

namespace Entities {
    partial class User : IFunkyInterface {
        /* interface implementation, if necessary */
    }
}

static class EntityExtensions {
    public static void SomeMethod(this IFunkyInterface obj)
    {...}
}

or if you need to know the type:

static class EntityExtensions {
    public static void SomeMethod<T>(this T obj)
          where T : class, IFunkyInterface
    {...}
}
Marc Gravell
Good advice. Only.. I need to override a property of the entity class, which is not possible using partial classes, so I guess I'm forced to do extension methods--only; how can I do that? How can I add specific methods to specific entity classes? Could you update your answer?
roosteronacid
What do you mean "override a property"? There are existing partial methods for most of the common before-change/after-change scenarios. I'll add an example for extension methods.
Marc Gravell
Hey Marc. Updated my question. I'd appreciate your take on it.
roosteronacid
+1  A: 

Re the enum edit (added as a second answer to keep things simple)...

Firstly - is there a direct 1:1 mapping between the enum and the values? For example, if Basic is 7 and Administrator is 12, then:

enum AccessLevels
{
    Basic = 7,
    Administrator = 12
}

Then change the type of that property in the dbml (via the designer) from int to your (fully-qualified) enum: Entities.AccessLevel. LINQ-to-SQL supports enums either as direct integer mappings, or as direct string mappings.

If this isn't possible (more complex scenerios), you can isolate the storage (int) and object-oriented (enum) models; rename the property to AccessLevelStorage (or anything else you like), and in a partial class do the mapping:

partial class User {
    public AccessLevel AccessLevel {
        get {
            switch(AccessLevelStorage) {
                case 1: return AccessLevelStorage.Foo;
                ... etc
                default: ...throw an exception?
            }
         }
         set {
            switch(value) {
                case AccessLevel.Foo: AccessLevelStorage = 1; break;
                ...etc
                default: ...throw an exception?
            }
         }
}

The only caveat here is that LINQ queries will only work against the storage properties - not the custom mapped property. If you do your queries at the level that declares the context, you can change the access of the storage property to internal - but if you do queries outside of this assembly you'll need to leave is public. You might want to add [Browsable(false)] to stop it appearing in UI models, but that is about it.

Marc Gravell
Excellent! First option in my case. Thanks a bunch Marc :)
roosteronacid