2

I am working on an ASP.NET MVC web application. And I have mapped my database tables using ADO.NET Entity Framework, which generated a .edmx file. Now one of the limitations of the generated .edmx file, is that all the collections which represent a parent-child relation, will be defined as ICollection. For example I have this Question model class:-

public Question()
        {
            this.Anwers = new HashSet<Anwer>();
        }

        public int Id { get; set; }
        public string QuestionDesc { get; set; }
        public int Order { get; set; }
        public bool Active { get; set; }

        public virtual ICollection<Anwer> Answers { get; set; }

Here is the answer model:-

  public partial class Anwer
    {
        public Anwer()
        {
            this.UserFormsAnswers = new HashSet<UserFormsAnswer>();
        }

        public int Id { get; set; }
        public string AnwerDesc { get; set; }
        public int QuestionID { get; set; }
        public bool Correct { get; set; }

        public virtual Question Question { get; set; }
        public virtual ICollection<UserFormsAnswer> UserFormsAnswers { get; set; }
    }

Now the problem with the ICollection compared to IList<> is that I can not use indexer to get the values of the Answers related to a question. While if I have IList<> instead of ICollection<> I can indexing the Answers for a Question.

So my question is what is the best appraoch to override my public virtual ICollection<Anwer> Answers { get; set; } to be public virtual IList<Anwer> Answers { get; set; }? Of course modifying the automatically generated .edmx file should be avoided as my modifications will be overridden if I remap my database tables..

umasankar
  • 599
  • 1
  • 9
  • 28
John John
  • 1
  • 72
  • 238
  • 501
  • 1
    can you share your Answer model – hasan Jun 09 '17 at 14:51
  • @hasan ok updated. – John John Jun 09 '17 at 15:26
  • you can implement ToList() onto ,why do you want to do that? – hasan Jun 09 '17 at 17:30
  • When you say indexer, do you mean something like Answers[5]? – Necoras Jun 09 '17 at 21:51
  • @Necoras yes now let say i want to show all the answers as a radio button for a question.. so if i define the following inside my view:- `@model QuestionsDemo.Models.Question @for (int j = 0; j < Model.Anwers.Count; j++) { @Html.HiddenFor(m => m.Anwers[j].Id) @Html.HiddenFor(m => m.Anwers[j].AnwerDesc) @Html.HiddenFor(m => m.Anwers[j].QuestionID) }` and i am using Icollection i will get this errror:- `Cannot apply indexing with [] to an expression of type 'System.Collections.Generic.ICollection'` – John John Jun 12 '17 at 12:34
  • 1
    The need of indexing is still questionable. In your example, `@for` can easily be replaced with `@foreah`, thus eliminating the need of indexing. EF model collection navigation properties are unordered by definition, hence `ICollection` better fits than `IList` (as you can see from the generated code, it allows the implementation to use the more efficient `Add` / `Remove` / `Contains` implementation provided by `HashSet` class, which will not be possible if it needs to expose `IList`). – Ivan Stoev Jun 15 '17 at 07:25
  • 2
    Your editing data so you should never be using your data model in the view. Rule 1: Always use a view model (and the view model contains the `IList` properties). Note also that you can use a custom `EditorTemplate` which works with `IEnumerable` and does not require `IList` - refer [this answer](http://stackoverflow.com/questions/30094047/html-table-to-ado-net-datatable/30094943#30094943) but you cannot use a `foreach` loop as suggested by @IvanStoev –  Jun 15 '17 at 09:49
  • 1
    Let forget the discussion *why and if you need to expose `IList` instead of `ICollection`* for a while. The direct solution of your question is provided in the first part of the current answer - **modify T4 templates* used to generate the entity model classes from `edmx`. What else do you need - how exactly to modify the `.tt` file, which `.tt` file to modify or? – Ivan Stoev Jun 20 '17 at 15:57

3 Answers3

7

If you truly need to modify all of your ICollection objects into IList objects then the correct place to do that is in your T4 templates. They're the ".tt" files inside of your edmx. Those are the templates which are used to generate the code files that the rest of your project uses. Any modifications made to those templates will be propagated through your project any time you update your edmx.

That said, I'd question whether or not you actually need to make this change. You're much better off keeping the return type from your database as generic as possible, and then narrowing it down to a List (or whatever) where you're going to use it.

Necoras
  • 6,743
  • 3
  • 24
  • 45
  • @Nesoras Now as i mentioned that using `ICollection` to implement the foreign key, will not allow me to apply indexing.. while using code first appraoch will implement the foreign key relationship as `List` or `ILIST` on the parent object .. so i do not think it is wrong to use `LIST` instead of `ICollection` since in code first appraoch ,, all the FK will be implemented as `ILIST` inside the parent object.. – John John Jun 12 '17 at 12:42
  • can you advice on this please? – John John Jun 15 '17 at 00:49
  • 3
    @johnG The first part of the answer is covering *how you can do what you want*. I don't see what else do you need. – Ivan Stoev Jun 15 '17 at 07:16
  • 1
    @johnG As I said in my initial answer, you can modify your T4 templates if you want the return types to be permanently different. As for why you should keep it as an `ICollection`, consider the possibility that you you want to use the data returned by EF in a `Queue`. Since `Queue` implements `ICollection` but not `IList` you would be unable to do so after making your changes to the T4 template. It would be better to leave the data EF returns as an `ICollection` and then call .ToList() on that collection where you need to use it, say in a view model on a front end as you reference above. – Necoras Jun 15 '17 at 15:08
  • @Necoras but if i issue a `tolist` on my `ICollection` i will not be able to use indexing... now having `ILIST` will ease posting back a list of objects and will make my code at the view cleaner. becuase using `IColelction` i will have to use magic strings. so instead of using this clean code with `ILIST` inside my view `@for (var i = 0; i < 4; i++){@Html.EditorFor(m => m.Anwers[j].Id)}` ... – John John Jun 20 '17 at 00:23
  • i will end up using `TextBox` instead of `EditorFor`,, and also i will be using this dirty code inside my view @for (var i = 0; i < 4; i++) {@Html.TextBox("Answers[" + i.ToString() + "].Id",new { @class = "form-control", rows=10} )}` .. i hope this makes it clearer why i want to have ILIST instead of IColelction.. – John John Jun 20 '17 at 00:23
  • 1
    It sounds like you're binding your view directly to the model objects created by your edmx. That's where the root of your problem is. Instead you should have an `Question` object which is created by your edmx which has an `ICollection` on it. Then in your front end project you should have a `QuestionViewModel` object which has an `IList` on it. You would then project the database model onto the viewmodel and use the viewmodel data types at the presentation layer, rather than polluting your data layer with presentation concerns. – Necoras Jun 20 '17 at 14:52
  • @Necoras so you mean it is better to leave the .edmx classes as is. then i can create a view model for example named `QuestionAnswers` which have the `Question` class and `ILIST` ?? i am not sure why you mentioned (in your above comment) to have `IList` do you mean `ILIST` as i am not sure what should the `AnswerViewModel`contain? – John John Jun 20 '17 at 19:48
  • @johnG Yes. The structure and data formats of your database level objects should not be in any way tied to concerns relating to how they're displayed. That's what the front end view model classes are for. And that's where it's appropriate to be concerned with an indexable collection. – Necoras Jun 20 '17 at 19:51
1

ToList your icollection and use List.. which implements IList and has an indexer method []

for example

@for (var i = 0; i < m.Answers.Count; i++){
     @Html.EditorFor(m => m.Anwers.ToList()[i].Id)
 }

Ofcourse this example is silly because you will be casting to list each time your for loops. But you can easily make answers a list before the for loop. The best would be to use a view modal. I can post code of what that would look like. you could do it in razor before hand something like this

 @{List<Answers> answers =  m.Anwers.ToList()}
 @for (var i = 0; i < answers.Count; i++){
         @Html.EditorFor(answers => answers[i].Id)
  }

by the way you can definitely do the same thing with a foreach loop and the iCollection.

Harry
  • 3,930
  • 2
  • 14
  • 31
1

In the IList is the Derived From ICollection. In a EDMX File Generated Model first We Change

public Question()
    {
        this.Anwers = new HashSet<Anwer>();
    }
    public int Id { get; set; }
    public string QuestionDesc { get; set; }
    public int Order { get; set; }
    public bool Active { get; set; }
    public virtual ICollection<Answer> Answers { get; set; }

In First We need to Change the Hashset<Answer> to List<Answer>() and

public virtual ICollection<Answer> Answers { get; set; } to

public virtual IList<Answer> Answers { get; set; }

If We Changed the than it's Working Fine For

Sample Image With ICollection to IList Change

Sample Image for Fetching Record With Indexer with out Exception

In NHibernate ORM Using IList Alternative Of ICollection For Relationship, Check Before Use below Link

Why use ICollection and not IEnumerable or List on many-many/one-many relationships?

Update Model.tt file as like below

Changed ICollection to IList

navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("IList<" + endType + ">") : endType, 

Changed HashSet to List

this.<#=code.Escape(navigationProperty)#> = new List<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>(); 

Once you have updated the model.tt file like above it won't give any problems while remapping of EDMX update from DB.

umasankar
  • 599
  • 1
  • 9
  • 28