Access many to one Entity Framework model properties in RenderAction view using lambda expressions

75 Views Asked by At

I tried to access the property of model (FilePath) in my Render action, but without success.

I have these models:

public class Offer
{
 public int OfferID { get; set; }
 public string Reference { get; set; }
 public string Title { get; set; }

 public virtual ICollection<FilePath> FilePaths { get; set; }
}

public class FilePath
{
 public int FilePathId { get; set; }
 public string FileName { get; set; }
 public virtual Offer Offer { get; set; }
}

In controller offer:

public PartialViewResult Category()
{
 var offers = db.Offers
 .Include(i => i.FilePaths)
 // i tried with .Include(“FilePaths”) with no sucess
 .ToList();
  return PartialView("_OfferBoxList", offers);
}

Showing with:

@{Html.RenderAction("Category", "Offer");}

The problem is in partial action view:

@foreach (var item in Model)
{
 @item.Title // This work
 @item.FilePath.FileName // This NOT work
}

Output error:

'System.Data.Entity.DynamicProxies.Offer_278BF6A1F89FB9514329AC922E992AEBD19368D66A4460B6BEBA1BB2256CAFC3' does not contain a definition for 'FilePath'

Thanks for help.

2

There are 2 best solutions below

0
ken2k On

Each Offer has a list of FilePath instances (ICollection<FilePath> FilePaths), so you can't just access the Offer.FilePath.FileName property, you have to get for instance the first one (depending on what you need), using something like:

@foreach (var item in Model)
{
     @item.Title // This work
     @item.FilePaths.First().FileName // Take the first FilePath object from the collection
}
2
Brett Caswell On

You should really not be loading your entities into views..

I generally create Data Transfer Objects (DTOs) for a multitude of purposes. Not only does it help me shape the query expression (IQueryable<T>), but I also use so that I have concert types (instead of generated dynamic proxy class types - like the type you're seeing in your runtime exception) in control properties, like dataItem and dataSource, which are often used in runtime bindable dynamic/reflective controls (grids, listviews)..

Note It is possible to disable the generated dynamic proxy types by declaring and passing your own DbContextConfiguration instance to EntityFramework; However, doing so will affect EntityFramework supporting features.


public class OfferDTO
{
    public int OfferID { get; set; }
    public string Reference { get; set; }
    public string Title { get; set; }
    public IEnumerable<string> FileNames { get; set; }
}

you controller function would then look like:

public PartialViewResult Category()
{
    var offers = db.Offers.Select<Offer, OfferDTO>( (entity) => new OfferDTO() {
        OfferID = entity.OfferID,
        Reference = entity.Reference,
        Title = entity.Title,
        FileNames = entity.FilePaths.Select<FilePath, string>( filePath => filePath.FileName).AsEnumerable()
    });

    return PartialView("_OfferBoxList", offers.ToList());
}

then,

@{Html.RenderAction("Category", "OfferDTO");}

@foreach (var item in Model) // Model is IEnumerable<OfferDTO>
{
    @item.Title 
    @item.FileNames.First()
}

Additionally,

you can create an IQueryable<IQueryable<TEntityDTO>> to suit your purposes and perform a SelectMany on it to flatten the results.

public class OfferTitleFilenameDTO
{
    public string Title {get;set;}
    public string Filename {get;set;}
}

public PartialViewResult Category()
{
    var offers = db.Offers.Select<Offer, IQueryable<OfferTitleFilenameDTO>>( (entity) => entity.FilePaths.Select<FilePath, OfferTitleFilenameDTO>(filePath => new OfferTitleFilenameDTO() {
            Filename = filePath.FileName,
            Title = entity.Title
        })
    });

    return PartialView("_OfferBoxList", offers.SelectMany(dtos => dtos));
}

then,

@{Html.RenderAction("Category", "OfferTitleFilenameDTO");}

@foreach (var item in Model) // Model is IEnumerable<OfferTitleFilenameDTO>
{
    @item.Title 
    @item.Filename 
}