0

I have the following simple structure: Applicant Possition ApplicantPosition and ApplicantPositionHistory

The 3rd class has one reference with Applicant and one with Position. The 4th table has one reference with ApplicantPosition

In the razon page I am doing to show the history of an applicant per position, I want to show the name of the applicant for example

I have this in the html, however its showing me empty, it only shows me info for fields that are in the same object, for example comments and datemodified.

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.applicantPosition.Applicant.name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.applicantPosition.Position.name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.oldStatus.status)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.newStatus.status)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.comments)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.dateModified)
        </td>

My model is like this:

namespace Data.Model
{

    public class Position
    {
        [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]   
        public int PositionID { get; set; }

        [Required(ErrorMessage = "Position name is required.")]
        [StringLength(20, MinimumLength = 3, ErrorMessage = "Name should not be longer than 20 characters.")]
        [Display(Name = "Position name")]              
        public string name { get; set; }

        [Required(ErrorMessage = "Number of years is required")] 
        [Display(Name = "Number of years")]
        [YearsValidationAttribute(5, ErrorMessage = "{0} value must be greater than {1} years.")]        
        public int yearsExperienceRequired { get; set; }

        public virtual ICollection<ApplicantPosition> applicantPosition { get; set; }
    }

    public class Applicant
    {
        [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]      
        public int ApplicantID { get; set; }

        [Required(ErrorMessage = "Name is required")] 
        [StringLength(20, MinimumLength = 3, ErrorMessage="Name should not be longer than 20 characters.")]
        [Display(Name = "First and LastName")]
        public string name { get; set; }

        [Required(ErrorMessage = "Telephone number is required")] 
        [StringLength(20, MinimumLength = 3, ErrorMessage = "Telephone should not be longer than 20 characters.")]
        [Display(Name = "Telephone Number")]
        public string telephone { get; set; }

        [Required(ErrorMessage = "Skype username is required")] 
        [StringLength(20, MinimumLength = 3, ErrorMessage = "Skype user should not be longer than 20 characters.")]
        [Display(Name = "Skype Username")]
        public string skypeuser { get; set; }

        public byte[] photo { get; set; }

        public virtual ICollection<ApplicantPosition> applicantPosition { get; set; }


    }

    public class ApplicantPosition
    {
        [Key]
        [Column("ApplicantID", Order = 0)]
        public int ApplicantID { get; set; }

        [Key]
        [Column("PositionID", Order = 1)]
        public int PositionID { get; set; }

        public virtual Position Position { get; set; }

        public virtual Applicant Applicant { get; set; }

        [Required(ErrorMessage = "Applied date is required")] 
        [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
        [Display(Name = "Date applied")]     
        public DateTime appliedDate { get; set; }

        [Column("StatusID", Order = 0)]
        public int StatusID { get; set; }

        public Status Status { get; set; }

        //[NotMapped]
        //public int numberOfApplicantsApplied
        //{
        //    get
        //    {
        //        int query =
        //             (from ap in Position
        //              where ap.Status == (int)Status.Applied
        //              select ap
        //                  ).Count();
        //        return query;
        //    }
        //}
    }


    public class Address
    {
        [StringLength(20, MinimumLength = 3, ErrorMessage = "Country should not be longer than 20 characters.")]
        public string Country { get; set; }

        [StringLength(20, MinimumLength = 3, ErrorMessage = "City  should not be longer than 20 characters.")]
        public string City { get; set; }

        [StringLength(50, MinimumLength = 3, ErrorMessage = "Address  should not be longer than 50 characters.")]
        [Display(Name = "Address Line 1")]     
        public string AddressLine1 { get; set; }

        [Display(Name = "Address Line 2")]
        public string AddressLine2 { get; set; }   

    }



    public class ApplicationPositionHistory
    {
        [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
        public int ApplicationPositionHistoryID { get; set; }

        public ApplicantPosition applicantPosition { get; set; }

        [Column("oldStatusID")]
        public int oldStatusID { get; set; }

        [Column("newStatusID")]
        public int newStatusID { get; set; }

        [ForeignKey("oldStatusID")]
        public Status oldStatus { get; set; }

        [ForeignKey("newStatusID")]
        public Status newStatus { get; set; }

        [StringLength(500, MinimumLength = 3, ErrorMessage = "Comments  should not be longer than 500 characters.")]
        [Display(Name = "Comments")]
        public string comments { get; set; }

        [DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
        [Display(Name = "Date")]     
        public DateTime dateModified { get; set; }
    }

    public class Status
    {
        [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
        public int StatusID { get; set; }

        [StringLength(40, MinimumLength = 3, ErrorMessage = "Status  should not be longer than 20 characters.")]
        [Display(Name = "Status")]
        public string status { get; set; }

    }



}

Controller action

public ViewResult History(int applicantId, int positionId)
        {
            var history= unitOfWork.ApplicantPositionHistoryRepository.Find(d => d.applicantPosition.ApplicantID == applicantId && d.applicantPosition.PositionID == positionId);
            return View(history);
        }

EDIT UnitofWork.cs

public class UnitOfWork { private HRContext context = new HRContext();

    private BaseRepository<Position> positiontRepository;
    private BaseRepository<ApplicantPosition> applicantpositiontRepository;
    private BaseRepository<Applicant> applicantRepository;
    private BaseRepository<Status> statusRepository;
    private BaseRepository<ApplicationPositionHistory> applicantPositionHistoryRepository;


    public BaseRepository<ApplicationPositionHistory> ApplicantPositionHistoryRepository
    {
        get
        {

            if (this.applicantPositionHistoryRepository == null)
            {
                this.applicantPositionHistoryRepository = new BaseRepository<ApplicationPositionHistory>(context);
            }
            return applicantPositionHistoryRepository;
        }
    }

    public BaseRepository<Status> StatusRepository
    {
        get
        {

            if (this.statusRepository == null)
            {
                this.statusRepository = new BaseRepository<Status>(context);
            }
            return statusRepository;
        }
    }

    public BaseRepository<Applicant> ApplicantRepository
    {
        get
        {

            if (this.applicantRepository == null)
            {
                this.applicantRepository = new BaseRepository<Applicant>(context);
            }
            return applicantRepository;
        }
    }

    public BaseRepository<Position> PositionRepository
    {
        get
        {

            if (this.positiontRepository == null)
            {
                this.positiontRepository = new BaseRepository<Position>(context);
            }
            return positiontRepository;
        }
    }

    public BaseRepository<ApplicantPosition> ApplicantPositionRepository
    {
        get
        {

            if (this.applicantpositiontRepository == null)
            {
                this.applicantpositiontRepository = new BaseRepository<ApplicantPosition>(context);
            }
            return applicantpositiontRepository;
        }
    }

    public void Save()
    {
        context.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                context.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

HRContext.cs

 public class HRContext : DbContext
    {
        public DbSet<Position> Positions { get; set; }
        public DbSet<Applicant> Applicants { get; set; }
        public DbSet<ApplicantPosition> ApplicantsPositions { get; set; }
        public DbSet<ApplicationPositionHistory> ApplicationsPositionHistory { get; set; }
        public DbSet<Status> Status { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Position>().ToTable("Position");
            modelBuilder.Entity<Applicant>().ToTable("Applicant");
            modelBuilder.Entity<ApplicantPosition>().ToTable("ApplicantPosition");
            modelBuilder.Entity<ApplicationPositionHistory>().ToTable("ApplicationsPositionHistory");
            modelBuilder.Entity<Status>().ToTable("Status");

            modelBuilder.Entity<Position>().Property(c => c.name).IsRequired();
            modelBuilder.Entity<Applicant>().Property(c => c.name).IsRequired();
            modelBuilder.Entity<ApplicantPosition>().Property(c => c.appliedDate).IsRequired();
            modelBuilder.Entity<ApplicationPositionHistory>().Property(c => c.ApplicationPositionHistoryID).IsRequired();
            modelBuilder.Entity<Status>().Property(c => c.StatusID).IsRequired();

            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
            base.OnModelCreating(modelBuilder);
        }
    }

BaseRepository.cs

public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        internal HRContext context;
        internal DbSet<TEntity> dbSet;

        public BaseRepository(HRContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }

        public virtual TEntity GetByID(object id)
        {
            return dbSet.Find(id);
        }


        public virtual void Insert(TEntity entity)
        {
            dbSet.Add(entity);

        }

        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void DeleteAll(List<TEntity> entities)
        {
            foreach (var entity in entities)
            {
                this.Delete(entity);
            }
        }

        public virtual List<TEntity> GetAll()
        {
            return context.Set<TEntity>().ToList();
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }

        public IQueryable<TEntity> Find(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate)
        {
            return dbSet.Where(predicate);
        }
    }

IRepository.cs

public interface IRepository<TEntity>
    {
        IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);

        void Insert(TEntity entity);

        void Delete(TEntity entity);

        void DeleteAll(List<TEntity> entities);
    }

I changed the generic repository and changed the controller action method with this:

public ViewResult History(int applicantId, int positionId)
{
    //var history= unitOfWork.ApplicantPositionHistoryRepository.Find(d => d.applicantPosition.ApplicantID == applicantId && d.applicantPosition.PositionID == positionId);
     var history= db.ApplicationsPositionHistory.Include("ApplicantPosition").SingleOrDefault(d => d.applicantPosition.ApplicantID == applicantId && d.applicantPosition.PositionID == positionId);

    return View(history);
}

However I am getting this exception:

The model item passed into the dictionary is of type 'Data.Model.ApplicationPositionHistory', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Data.Model.ApplicationPositionHistory]'.

Luis Valencia
  • 32,619
  • 93
  • 286
  • 506

2 Answers2

2

How is the Find method of your ApplicantPositionHistoryRepository implemented ? If it is something like

return entities.ApplicantPosition.SingleOrDefault(expression)

then you must enable eager loading like this :

return entities.ApplicantPosition.Include("Applicant").SingleOrDefault(expression)

Btw this is the reason why I personally dont like these "generic" repositories - there are always situations where you need eager load somethins, and also situations when it is just wasting of resources. In "non-generic" repository you would just create two methods

GetApplicantPositionWithApplicant(int id)

GetApplicantPosition(int id)

and also your expression (which are in fact query logic if you think about it) stays in model (repository) instead of controller.

EDIT (answering comments)

About the "pain" of generic repositories : Generic repository is in general good idea only if you are building some really BIG application, where you want to separate things in more layers, typically - data-access layer (generic repositories), business layer (business logic, workflows, advanced validation etc.), and then control and presentation layer (controller+view in MVC). In this scenario, generic repositories only encapsulate trivial CRUD logic and are used only by business layer, not controllers.

If you dont have some heavy stuff going on (non-trivial workflows, validation requiring external services etc.), then you have no real need for business layer, and you can "merge" it in data-access layer (or if you want, merge data-access into busines layer, they become one body one soul :)

So, if you dont have some real business layer, you can only end up with some business logic in controller, because your generic repository is not really well suited for that. Non-generic repositories can contain your trivial business processing, and also you can implement custom CRUD for different scenarios - very good example is eager loading of things only when you know they will be needed).

So decision of rewriting is really up to you and depends on your architecture and needs.

About your exception :

With SingleOrDefault, your variable history contains just one ApplicationPositionHistory object, and your view expects enumeration of ApplicationPositionHistory objects. Encapsulate this data retrieval in repository call with clear return value, you will prevent this type of errors.

rouen
  • 5,003
  • 2
  • 25
  • 48
  • its implemented this way: public IQueryable Find(System.Linq.Expressions.Expression> predicate) { return dbSet.Where(predicate); } – Luis Valencia Oct 26 '11 at 11:28
  • looks like my entire generic repository is a pain and needs to be rewritten then, or is there another approach to make it work with the generic repository? – Luis Valencia Oct 26 '11 at 11:44
  • can you please check the last part of my edit, I tried to change it as you say but I am having that exception. – Luis Valencia Oct 26 '11 at 12:14
0

Can you show the code where you get the List of items from the database?

Did you try to debug that code? Were everything you want to show on the page loaded from the database (because EF does not load associated entities by default)?

Mark Vincze
  • 7,737
  • 8
  • 42
  • 81
  • I just edited the question and posted the controller action, so how can I make it load the associated entities in the above code? – Luis Valencia Oct 26 '11 at 10:36
  • Could you post the code of the `ApplicantPositionHistoryRepository` class as well (that is where the EF code is, right)? To make the EF load associations, you have to call Include on your query. (But I am not sure that is the issue, so please post the code where you acces the ObjectContext or DbContext) – Mark Vincze Oct 26 '11 at 11:05
  • I used the Repository and unit of work pattern, I just pasted the entire code of the context, repository and unit of work. – Luis Valencia Oct 26 '11 at 11:14