2

My model has a base class that is NOT abstract and does NOT have any key defined (it's an externally defined class that I cannot modify). Instead, I defined a derived class with MyID property to be the key. Something like this:

public class MyBaseClass // From external assembly
{
    //public int ID { get; set; }
    public string Name { get; set; }
}

public class MyClass // From external assembly
{
    public int ID { get; set; }
    public List<MyBaseClass> Objects { get; set; }
}

public class MyDerivedClass : MyBaseClass
{
    public Guid MyID { get; set; }

    public MyDerivedClass()
    {
        MyID = Guid.NewGuid();
    }
}

public class MyClasses : DbContext
{
    public DbSet<MyClass> Classes { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MyDerivedClass>().HasKey(entity => entity.MyID);
        modelBuilder.Entity<MyDerivedClass>().Map(entity =>
        {
            entity.MapInheritedProperties();
            entity.ToTable("MyBaseClass");
        });
        modelBuilder.Ignore<MyBaseClass>();
    }
}

class Program
{
    static void Main(string[] args)
    {
        Database.SetInitializer<MyClasses>(new DropCreateDatabaseIfModelChanges<MyClasses>());
        var myClass = new MyClass() // Just as example, in real code is somethog like: MyClass myClass = ExtenalAssembly.getMyClass()
        {
            ID = 0,
            Objects = new List<MyBaseClass>()
            {
                new MyBaseClass()
                {
                    //ID = 0,
                    Name = "My Test Object 1"
                },
                new MyBaseClass()
                {
                    //ID = 1,
                    Name = "My Test Object 2"
                }
            }
        };
        Mapper.CreateMap<MyBaseClass, MyDerivedClass>();
        for (var index = 0; index < myClass.Objects.Count; index++)
        {
            myClass.Objects[index] = Mapper.Map<MyDerivedClass>(myClass.Objects[index]);
        }
        var myObjects = new MyClasses();
        myObjects.Classes.Add(myClass);
        myObjects.SaveChanges();
    }
}

If I comment out the line modelBuilder.Ignore<MyBaseClass>(); the runtime throws an exception because MyBaseClass doesn't have a key defined; on the other hand, when I include that line to don't save the state of an instance of base class but only the state of an instance of derived class, the system doesn't persist any data in the generated tables.

What should I do in order to persist only the state of derived instance?

2
  • You should move the ID to the base class or provide a Identity Id in this class. Commented Dec 2, 2013 at 18:02
  • 1
    @Fals Which part of it's an externally defined class that I cannot modify you missed? :) Commented Dec 2, 2013 at 18:31

2 Answers 2

1

I ran your solution with EF6.0.1 from NuGet and it all checks out (no errors). I persisted the data correctly and both fields were persisted as expected.

If I'm reading you correctly, it sounds like you don't want the Name property of the base class mapped to EF. If that's the case, you can add this line to your modelBuilder and regenerate your Migration file.

modelBuilder.Entity<MyDerivedClass>().Ignore(d => d.Name);

This gives me the following "Up" script

CreateTable(
    "dbo.MyBaseClass",
        c => new
            {
                MyID = c.Guid(nullable: false),
            })
        .PrimaryKey(t => t.MyID);

After running your main, only the ID is persisted.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for your work, when I made this example, I didn't made it correct, now I remade the example and you can reproduce exactly my problem using the updated code.
0

Change the navigation collection property to use the derived type as the generic type parameter (you're telling EF to ignore MyDerivedType, after all). This will get things working.

Update: if you can't change the MyClass class, then derive a new class from MyClass with a property that converts MyClass.Objects to/from List<MyDerivedClass> and use this newly derived class in place of MyClass.

Update 2: made the code safer by automatically converting instances of MyBaseClass in MyClass.Objects to instances of MyDerivedClass when accessing via MyClass2.DerivedObjects property. This allows the addition of MyBaseClass instances to the MyClass.Objects list (as shown in the question) without fear of invalid cast exceptions upon accessing with MyClass2.DerivedObjects.

Update 3: updated yet again to make MyClass2 wrap MyClass instead of deriving from it

// DbSet property in your DbContext
public DbSet<MyClass2> Classes { get; set; }

// continue to ignore MyBaseClass
...
modelBuilder.Ignore<MyBaseClass>();
...


public class MyBaseClass // From external assembly
{
    //public int ID { get; set; }
    public string Name { get; set; }
}

public class MyDerivedClass : MyBaseClass
{
    public Guid MyID { get; set; }

    public MyDerivedClass()
    {
        MyID = Guid.NewGuid();
    }

    public MyDerivedClass( MyBaseClass myBaseClass ) : this()
    {
        // map myBaseClass to `this` here:
        this.Name = myBaseClass.Name;
    }
}

public class MyClass
{
    public int ID { get; set; }
    public List<MyBaseClass> Objects { get; set; }
}

public class MyClass2
{
    public static implicit operator MyClass( MyClass2 mc2 )
    {
        return mc2._myClass;
    }

    public virtual List<MyDerivedClass> Objects
    {
        get
        {
            for( int i = 0; i < _myClass.Objects.Count; ++i )
            {
                var o = _myClass.Objects[ i ];

                if( null == ( o as MyDerivedClass ) )
                {
                    _myClass.Objects[ i ] = new MyDerivedClass( o );
                }
            }

            return _myClass.Objects.Cast<MyDerivedClass>().ToList();
        }
        set
        {
            _myClass.Objects = value.Cast<MyBaseClass>().ToList();
        }
    }

    public int ID
    {
        get { return _myClass.ID; }
        set { _myClass.ID = value; }
    }

    private MyClass _myClass;

    public MyClass2()
    {
        _myClass = new MyClass();
    }

    public MyClass2( MyClass myClass )
    {
        if( null == myClass )
            throw new ArgumentNullException( "myClass" );

        _myClass = myClass;
    }
}

Application code:

using( var db = new TestContext() )
{
    var myClass = new MyClass()
    {
        ID = 0,
        Objects = new List<MyBaseClass>()
        {
            new MyBaseClass()
            {
                Name = "Object 1"
            },
            new MyBaseClass()
            {
                Name = "Object 2"
            }
        }
    };

    var myClass2 = new MyClass2( myClass );

    db.Classes.Add( myClass2 );

    db.SaveChanges();
}

7 Comments

That was only an example to show what is happening but I don't have control about how "myClass" instance is generated because the external third party library initializes and returns the instance that way in (using MyBaseClass).
alrighty then - derive a new class from MyClass that casts base.Objects to/from List<MyDerivedClass> and use this new class (which I named MyClass2 in my example) instead of MyClass
I'm sorry, I put the initialization of myClass instance just as example but I can't change the way it is generated. In my real code is something like MyClass myClass = ExtenalAssembly.getMyClass(). That call returns me the instance already initialized with values of types "MyClass" containing a collection of "MyBaseClass".
Then simply use MyClass2 as a wrapper around MyClass instead of a subtype with the above solution
Updated to show how you can wrap MyClass instead of deriving from it
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.