0

I currently have a data model where a property can have multiple property images:

public class Property
{
    public int ID { get; set; }
    public string Name { get; set; }
    public Guid PrimaryImageID { get; set; }

    public ICollection<PropertyImage> Images { get; set; }
}

public class PropertyImage
{
    [Key]
    public Guid ID { get; set; }
    public int PropertyID { get; set; }

    public virtual Property Property { get; set; }
}

However, as you can see, i also want to enable a relationship so that a property can have ONE of those images assigned as a primary image.

I found an article here, that seems to use the Fluent API to configure it, but that's all fairly new to me, so i was wondering if it was possible to do this purely using Entity Framework?

What i REALLY want to achieve, is so that i can just call...

property.primaryimage.url

for example. If a user then wanted to change the primage image of a property, then i just change the PrimaryImageId field to the Guid of a different image

Many thanks

Community
  • 1
  • 1
Gavin5511
  • 791
  • 5
  • 31

4 Answers4

3

Personally, I wouldn't be messing around with EF to do this, the answer in the link you shared would pretty much agree with me. I would simply add another field to the PropertyImage class

public bool IsPrimaryImage {get;set;}

and just find the image based on the value set in that.

Sometimes the simplest solution is the best. You could end up with a convoluted solution in EF that does what you want but at the end of the day, would it really be better than just assigning true or false to a field?

Ian Murray
  • 339
  • 1
  • 12
  • It might do, in this case I don't think it would have that much of an effect on maintainability. He's wanting one single image to be a primary image. I can't think how that could really make his code unmanageable sometime in the future – Ian Murray Jan 10 '17 at 16:20
  • The problem is though, there will me multiple properties, and each can only have a primary image set for an image with a matching propertyID. So i'm not sure the IsPrimaryImage would work? – Gavin5511 Jan 10 '17 at 16:27
  • I don't see why not, the image is associated with the property. You'll only be pulling images when you are looking for or viewing that associated property. Setting a flag to IsPrimaryImage wouldn't cause any issues with other images for other properties, regardless of how many properties you have in your database. – Ian Murray Jan 10 '17 at 16:36
  • But if a user wanted to change the primary images, it becomes more complex (set old image to false, then set new image to false). What i really want to be able to do is just call something like (property.primaryimage.url) . Is that not possible? – Gavin5511 Jan 11 '17 at 12:44
  • 1
    I think you're overthinking this way too much. Calling property.primaryimage.url is just calling a method. Inside that same method you could unset the current primary image. It's relatively straightforward. – Ian Murray Jan 11 '17 at 13:48
1

First, you will add a "PrimaryImage" property to your Property class:

public class Property
{
    public int ID { get; set; }
    public string Name { get; set; }
    public Guid PrimaryImageID { get; set; }
    public virtual PropertyImage PrimaryImage { get; set; }

    public ICollection<PropertyImage> Images { get; set; }
}

In your class where you inherit Entity's framwork DbContext, you can override the method OnModelCreating, which will lead you to:

protected override OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
}

Then, after the line base.OnModelCreating(modelBuilder), you can write:

modelBuilder.Entity<Property>().
    .HasRequired(x => x.PrimaryImage)
    .WithRequiredPrincipal();

modelBuilder.Entity<Property>().
    .HasMany(x => x.Images)
    .WithRequired(x => x.Property);

If this is what you want, then I believe this code allows you to have this property you need. Hope it helps!

Mari Faleiros
  • 898
  • 9
  • 24
  • Unfortunately, the assumption isn't correct. A property will only have a primary image set as as image with same PropertyID? – Gavin5511 Jan 10 '17 at 16:29
  • Oh, yes! Sorry, I made a confusion. I just made a code edit, please check if it helps – Mari Faleiros Jan 10 '17 at 17:45
  • Will this allow me to call something like 'property.primaryimage.url' to get the URL for the primary image out of the image table? – Gavin5511 Jan 11 '17 at 12:49
  • Yes, however you need to perform an Include when fetching Property data from the database. Something like: var property = db.Properties.Include(p => p.PrimaryImage).FirstOrDefault(...); var url = property.PrimaryImage.Url; – Mari Faleiros Jan 11 '17 at 13:21
1

If you need a Property to have a primary PropertyImage that can only be an image that is applicable to that Property, the emphasis needs to be switched:

You cannot set the primary Image for a Property until the images are entered and related to the Property to begin with.

You can't add the images unless the Property exists to relate to.

So, you would need to have the PrimaryImage property nullable until later set.

While a PropertyImage relies on a Property, a Property does not rely on a PropertyImage, and so should not be a foreign key in it's record.

This means that the flag (boolean value) for PrimaryImage needs to be stored with the PropertyImage indicating which one of the images is the primary one.

Remove the PrimaryImageId from Property and place a property on the PropertyImage (IsPrimaryImage) to allow selection of the primary one.

You can handle the unique selection either via the UI or more properly with a Unique Constraint on the table.

public class Property
{
    public int ID { get; set; }
    public string Name { get; set; }

    public ICollection<PropertyImage> Images { get; set; }
}

public class PropertyImage
{
    [Key]
    public Guid ID { get; set; }
    public int PropertyID { get; set; }
    public bool IsPrimaryImage { get;set; }

    public virtual Property Property { get; set; }
}

It isn't good practice to try to structure the data and its relationships around the way you'd like to call a method in code.

You can still call the method the way you want and encapsulate any logic you may need inside.

Think along the lines of if there was a cascade delete applicable here to remove items that no longer have a parent item to relate to:

If you delete a Property, all related PropertyImages would be removed too - correctly so because they relied on that record existing.

If you delete the primary PropertyImage, then the Property would have to be deleted because the record it relates to no longer exists...

So to have your method call the way you would like, do something similar to this:

private void UpdatePrimaryImage(PropertyImage oldImage, PropertyImage newImage)
{
    // Pass in the original primary PropertyImage and the new one obtained from the UI.

    // Check that we do not have the same image, otherwise no change needs to be made:
    if(oldImage.IsPrimary != newImage.IsPrimary)
    {
        oldImage.IsPrimary = false;
        newImage.IsPrimary = true;
        Update(oldImage);
        Update(newImage);
        SaveChanges;
    }
}

And to retrieve the current primary image:

Property.PropertyImages.Where(p => p.IsPrimaryImage).Url
Steve Padmore
  • 1,710
  • 1
  • 12
  • 18
  • Thanks Steve. However, it feels a little complicated by keeping the IsPrimaryImage in the image table? if a user wants to change the primary image for a property, then i'll first have to set the old primary image to 'false' before setting the new one to 'true'. What i really want to achieve, is just calling something like (property.primaryimage.url) to get the URL for the image. If a user changes the primary image of a property, then it'd be easy to just update the primaryimageId guid for the property – Gavin5511 Jan 11 '17 at 12:47
  • @Gavin5511, seeing that you are focussed on how you would retrieve the data, I've added the way to do this the way that you would like, with a bit more of an explanation as to why the foriegn key shouldn't be in the parent table - hope this helps a bit more. – Steve Padmore Jan 11 '17 at 20:51
0

Try this:

public class Property
{
  public int ID { get; set; }
  public string Name { get; set; }
  public Guid PrimaryImageID { get; set; }
  [ForeignKey("PrimaryImageID")]
  public virtual PropertyImage PrimaryImage { get; set; }

  public ICollection<PropertyImage> Images { get; set; }
}

public class PropertyImage
{
  [Key]
  public Guid ID { get; set; }
  public int PropertyID { get; set; }

  [ForeignKey("PropertyID")]
  public virtual Property Property { get; set; }
}
SirBirne
  • 297
  • 2
  • 11