Matthew
Matthew

Reputation: 9949

Entity Framework with Calculated Property

I am looking to determine the cleanest / best practice way to complete the following:

I am using EF, code first, repository pattern. I have an Entity called Project which in turn has a list of Tags or Keywords which describe the project:

public class Tag {
   public int TagID { get; set; }
   public int ProjectID { get; set; }
   public string TagValue { get; set; }
}  

I am using this data in a strongly typed Razor view, and am displaying the Tags below the Project title. When doing this I want each tag to indicate how many times it appears in the data base like:

Some Project Title

C# (5), Entity Framework (17)

It seemed to me the best plan would be to add a calculated property to the entity:

public class Tag {
   public int TagID { get; set; }
   public int ProjectID { get; set; }
   public string TagValue { get; set; }
   public int TagCount { get { _context.Tags("Some Filter on TagValue").Count() }}
}

Alternately, I was considering populating the TagCount property in the controller calling a method in the TagRepository like:

public int countTags(string TagValue);

Realizing I may be missing the bigger picture here altogether, my question is: Are either of these approaches on the right track?

Update: Adding the Project model as requested.

Abstract Project:

public abstract class Project {
    public int ProjectID { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Tag> Tags { get; set; }
}

Implemented Type of Project:

public class Scientific : Project {
    public int ScientificProjectNumber { get; set; }
    public string FileNumber { get; set; }
    public int Year { get; set; }
    public DateTime StartDate { get; set; }

}

UPDATE: My solution based on Brian Cauthon's answer was to do the following:

-Instead of creating a net-new class, I inherited the ones I was extending: Note: I had to create an additional property TagsView as I could not override the virtual property on the Scientific : Project class.

public class ScientificView : Scientific {
    public virtual ICollection<TagView> TagsView { get; set; }
}

-I did the same for TagView, extending the class rather than copying it - hopefully saving me some effort as these object evolve:

    public class TagView : Tag {
    public int TagCount { get; set; }
}

-Added the suggested TagRepository Method:

public Dictionary<string, int> countTags(IEnumerable<string> tags) {
        Dictionary<string, int> result = new Dictionary<string, int>();
        var query = from o in _context.Tags
                group o by o.TagValue into tagGroup
                select new {TagText = tagGroup.Key,
                    TagCount = tagGroup.Count()};

        foreach (var tagGroup in query) {
            result.Add(tagGroup.TagText, tagGroup.TagCount);
        }
        return result;
    }

-Updated executed all from the Controller. Thank you for the link to the excellent AutoMapper project:

public ViewResult Scientific(int projectID) {
        Scientific project= _scientificRepository.Scientific.FirstOrDefault(l => l.ProjectID == projectID);
        Dictionary<string, int> tagCounts = _tagRepository.countTags(project.Tags.Select(t => t.TagValue));

        Mapper.CreateMap<Scientific, ScientificView>();
        ScientificView sv = Mapper.Map<Scientific, ScientificView>(project);

        Mapper.CreateMap<Tag, TagView>();
        sv.TagsView = new List<TagView>();
        foreach (Tag t in project.Tags) {
            TagView tv = Mapper.Map<Tag, TagView>(t);
            tv.TagCount = tagCounts[tv.TagValue];
            sv.TagsView.Add(tv);
        }
        return View(sv);
    }

Upvotes: 1

Views: 1531

Answers (2)

Brian Cauthon
Brian Cauthon

Reputation: 5534

I would go with pulling the count from the tag repository. Since you are likely to be pulling counts for multiple tags at a time I would change your repository method to pull all at once.

I would create specific viewmodels for your view and then map your Project and Tag to them with Automapper.

public class ProjectView {
    public int ProjectID { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public virtual ICollection<TagView> Tags { get; set; }
}

public class ProjectTagView {
   public int TagID { get; set; }
   public string TagValue { get; set; }
   public int TagCount { get; set; }
}

Then in your controller you would have something like this.

var model = AutoMapper.Map<Project,ProjectView>(project);
Dictionary<string,int> tagCounts = tagRepository.getCounts(model.Tags.Select(t=>t.TagValue));
foreach(var t in model.Tags){
    t.TagCount = tagCounts[t.TagValue];
}

Upvotes: 1

dan richardson
dan richardson

Reputation: 3939

For the actual question, I would personally say it's not the business of the model to know how to implement the business logic, but have the method on the model do the business logic. If the tag count was calculated externally from the model (i.e from a stored procedure) then perhaps I would go down the route that you've started, but defining it as a computed column (discussed here)

However it maybe comes into question as to what you're trying to achieve as at the moment you could be calling the database hundreds of time for the same data _context.Tags("").Count() as each Tag that is loaded into each project will perform this call.

Upvotes: 1

Related Questions