1

I'm trying to new ICollections in my derived classes - should this be done in the object constructor or/and when I'm creating a new instance of an object in my Register ActionResult()?

Tenant inherits from UserProfile - simplified example:

public class Tenant : UserProfile
{
    public Tenant() 
    {
        this.ReferencePhotos = new List<ReferencePhoto>();
        // ReferencePhoto extends Image
    }

    // A Tenant can have many ReferencePhotos
    [ForeignKey("ImageId")] // Id of parent class
    public virtual ICollection<ReferencePhoto> ReferencePhotos { get; set; }
}

I've tried to do the above but it results in an InvalidCastException:

Unable to cast object of type 'System.Collections.Generic.List'1[Namespace.Models.ReferencePhoto]' to type [Namespace.Models.ReferencePhoto]'.

Why is it trying cast from one object to another when they're the same?

In my Register ActionResult() I've also tried this (with and without setting ICollections in object contructor):

public ActionResult Register(RegisterModel model)
{
    using (var db = new LetLordContext())
    {
        var tenant = db.UserProfile.Create<Tenant>();
        tenant.ReferencePhotos = new List<ReferencePhoto>();
        // Do I need to new it here at all/as well?
        db.UserProfile.Add(tenant);     // Exception being thrown here
        db.SaveChanges();

        Roles.AddUserToRole(model.UserName, "Tenant");

        WebSecurity.Login(model.UserName, model.Password);
        return RedirectToAction("Confirm", "Home", tenant);
     } 
}

The above also throws the same exception mentioned earlier. Can anyone offer any insight?

EDIT: Added UserProfile code

public class UserProfile
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.None)]
    public int UserId { get; set; }

    [Display(Name = "Username")]
    [Required(ErrorMessage="Username is required.")]
    public string UserName { get; set; }

    [Display(Name = "First name")]
    [Required(ErrorMessage = "First name is required.")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Account type is required.")]
    public AccountType AccountType;

    public virtual string AccountTypeString
    {
        get { return AccountType.ToString(); }
        set
        {
            AccountType newValue;
            if (Enum.TryParse(value, out newValue))
            { AccountType = newValue; }
        }
    }
}

EDIT: Added context code and ReferencePhoto code

public class LetLordContext : DbContext
{
    public DbSet<LetLord.Models.UserProfile> UserProfile { get; set; }                  // 1 DbSet for superclass UserProfile
    public DbSet<LetLord.Models.Image> Image { get; set; }                              // 1 DbSet for superclass Image
    public DbSet<LetLord.Models.ResidentialProperty> ResidentialProperty { get; set; }
    public DbSet<LetLord.Models.TenantGroupMember> TenantGroupMember { get; set; }
    public DbSet<LetLord.Models.Viewing> Viewing { get; set; }
    public DbSet<LetLord.Models.TenantPreferences> TenantPreferences { get; set; }
    public DbSet<LetLord.Models.LandlordPreferences> LandlordPreferences { get; set; }
    public DbSet<LetLord.Models.Address> Address { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

I'm using table-per-type inheritance hence only DbSets for base classes are implemented.

public class ReferencePhoto : Image
{
    // 1:many with Tenant
    public int UserId { get; set; }
    [ForeignKey("UserId")]
    public virtual Tenant Tenant { get; set; }
}

I can workaround this issue by not initialising the list in the object constructor or when creating a new entity. To achieve this I use a null coalescing operator in the GET ActionResult that returns a Tenant object in a partial view. In the partial, I check if Model.ReferencePhotos == null. If it is, a user can upload a photo. When a photo is uploaded, it is added to the list, I can confirm this by checking the database. However, when I login again, the if in the partial view mentioned previously throws the following exception:

Unable to set field/property ReferencePhotos on type System.Data.Entity.DynamicProxies.Tenant_...

with an inner exception:

Unable to cast object of type 'System.Data.Entity.DynamicProxies.ReferencePhoto_3DFB9F64061D55E5AF6718A74C97025F77EFB2BB9C2A6E43F5A6AF62A6A73E75' to type 'System.Collections.Generic.ICollection`1[LetLord.Models.ReferencePhoto]'."}

This may provide more insight.

MattSull
  • 5,514
  • 5
  • 46
  • 68
  • Can you inherit your class from ObservableCollection? This will make your class a list of items. So you will not need to have a list inside your class. This will not apply if you have more members in your class. – fhnaseer Mar 25 '13 at 12:25
  • I haven't come across ObservableCollections. There are other properties and ICollections in Tenant, so going on what you've said, I don't think it would apply. – MattSull Mar 25 '13 at 12:29
  • by your err it seems you're trying to cast `list` into `single instance` - I don't see that in your code - what's UserProfile also – NSGaga-mostly-inactive Mar 25 '13 at 12:46
  • @NSGaga please see edit; added UserProfile code. UserProfile is just a base class that represents common properties between Tenant and other classes such as Landlord. – MattSull Mar 25 '13 at 12:51
  • `type [Namespace.Models.ReferencePhoto]'` - do you have a 'single photo' assignment somewhere - that err is quite specific (unless you cut it off). And what's your fluent code, db setup. – NSGaga-mostly-inactive Mar 25 '13 at 13:00
  • I don't have a 'single photo' assignment. Image is extended twice to ReferencePhoto and PropertyPhoto. I'm not using any fluent code, all relations are handled by annotations. Please see edit, added more code. – MattSull Mar 25 '13 at 13:07
  • @NSGaga any more ideas? Please see edit as I've added some information that may be of help. – MattSull Mar 25 '13 at 14:08
  • I'm not here all the time :). Anyway, what you're having is TPH (if I'm reading your code in it's entirety), TPT needs to have `Table[]` on each or fluent code (not sure which version you have but that should be default)... – NSGaga-mostly-inactive Mar 25 '13 at 15:15
  • Try removing the `ForeignKey` that should be set on the `one` side (of one-to-many - sort of) - and that might as well be adding extra foreign key (as a 'single' vs collection) and making all the problems. I don't have much time now, just a tip, let me know and we'll figure this out, details. – NSGaga-mostly-inactive Mar 25 '13 at 15:16
  • Sorry! I'm actually using TPT, I do have each class annotated with [Table("...")], I just left it out to simplify the question - I should have put it in though. I'll try what you've suggested, thanks. – MattSull Mar 25 '13 at 15:29

1 Answers1

1

Try removing the...

[ForeignKey("ImageId")] // Id of parent class

ForeignKey that should be set on the 'one' side (of one-to-many - sort of), never on collections - and it's only defined on the ReferencePhoto (via UserId which you already have attributed).

...and that might as well be adding extra foreign key - and resulting in that single instance vs collection error.



Some more info on how ForeignKey should be defined (ForeignKey vs InverseProperty etc.), and some good advices from gurus :)

Entity Framework 4.1 InverseProperty Attribute

How Should I Declare Foreign Key Relationships Using Code First Entity Framework (4.1) in MVC3?


For more complex scenarios (with many-to-many and manually defining relations in fluent code - which I recommend) - take a look at these detailed examples I made a while ago - it has most of the mappings you may need.

Many to many (join table) relationship with the same entity with codefirst or fluent API?

Code First Fluent API and Navigation Properties in a Join Table

EF code-first many-to-many with additional data

Community
  • 1
  • 1
NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51
  • That seems to have done the trick, cheers. I can't remember if I saw in a tutorial or just did it myself - putting FK annotations on collections. I ran into one small problem with a many-to-many relationship (Tenant and Viewing) regarding what you suggested above; I dropped and recreated my database and EF gave an error "Foreign Key contraint may cause cycles or multiple cascade paths" in relation to table TenantViewing. I used modelBuilder.Conventions.Remove(); to resolve the issue. I only have one association table - could this be an issue? – MattSull Mar 25 '13 at 16:24
  • I just posted an edit - to add some links I made for many-to-many - has all to cover very complex cases. It's hard to say, that's a known error but can't recall just what it is that's triggering it. In my experience, it's all much easier, safer to control via fluent configuration - hence why I do it that way, there're always issues with attributes you can't entirely resolve unless you control all the fields and mappings. – NSGaga-mostly-inactive Mar 25 '13 at 16:49
  • 1
    you can always make, open another question with some details about it - and here we go again :) - just '@' reference me so that I know to take a look, or here – NSGaga-mostly-inactive Mar 25 '13 at 16:50