0

I have an application that creates courses for a university and adds students to the created courses. I pass the course info from my frontend using a dto for data security. The code in my service, using a foreach works, but the one with the linq does not. The code for my entities, which are students and courses, and my service is below:

    public class Student
{
    public string studentName {get; set;}
    public int idCourse {get; set;}
}


public class Course
{
    public Course()
    {
        Students = new List<Student>();
    }
    public int id {get; set;}
    public string courseName {get; set;}
    public ICollection<Student> Students {get; set;}
    
}

public class CourseDTO
{
    public string courseNameDTO {get; set;}
    public List<string> StudentsFromDTO {get; set;}
}

public class Service(CourseDTO dto)
{
    Course createdCourse = new Course
    {
        courseName = dto.courseNameDTO;
    }
    
    foreach(var studentToAdd in dto.StudentsFromDTO)
    {
        var student = new Student
        {
            idCourse = createdCourse.id;
            studentName = studentToAdd;
        }
        
        createdCourse.Students.Add(student);
    }
    
    // add the created course in the database
}

Basically, a course has many students, which are added after the new course is created. In my service I loop through the students names and add each of the individually which is not the most efficient, so I tried to write a linq, but it doesn't work:

createdCourse =  createdCourse.AddRange(dto.StudentsFromDTO.Select(s => new Student { idCourse = createdCourse.id;
        studentName = s; }));

Any way to refactor the code to be fewer lines or a better linq? Cheers!

  • *In my service I loop through the students names and add each of the individually which is not the most efficient* -- why do you believe it is not the most efficient? Have you profiled? Is there some performance problem? Your are mapping a collection of one type to a collection of another type using a linear algorithm, why should there be anything more efficient than that? – dbc Jun 20 '23 at 17:16
  • Because it doesn't work...the students name never gets added to my course – DS Assignment Jun 20 '23 at 17:19
  • 1
    Or by *efficient* did you actually mean **concise**? Are you looking to do the same thing with less code? – dbc Jun 20 '23 at 17:19
  • If your current code doesn't actually work, please [edit] your question to clarify how to reproduce the problem, and ideally share a [mcve]. Your current code doesn't compile by the way, see https://dotnetfiddle.net/IOmrMw. – dbc Jun 20 '23 at 17:22
  • I tried to fix the syntax of your code so it compiled. When I did, the method to create a `Course` from a `CourseDTO` seems to work. See https://dotnetfiddle.net/a9jHCX. Please [edit] your question to clarify your problem, and see [ask]. – dbc Jun 20 '23 at 17:27
  • You declared `Students` as a `ICollection`. `ICollection` does not have a method `AddRange()`, as `AddRange()` is specific to `List`. So you have to loop, and add. – dbc Jun 20 '23 at 17:30
  • Yes, the method in my service works, but I want to create a course using linq like this: createdCourse = createdCourse.AddRange(dto.StudentsFromDTO.Select(s => new Student { idCourse = createdCourse.id; studentName = s; })); – DS Assignment Jun 20 '23 at 17:31
  • Then you need to introduce your own extension method `AddRange(this ICollection destination, IEnumerable source)` such as the one from [AddRange to a Collection](https://stackoverflow.com/q/1474863). – dbc Jun 20 '23 at 17:34
  • If you are working in C# 9.0 or later, you could simplify your code with a [target-typed `new()` operator](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/new-operator#target-typed-new), e.g. `createdCourse.Students.Add(new() { idCourse = createdCourse.id, studentName = studentToAdd });`. See https://dotnetfiddle.net/s8nCIJ. – dbc Jun 20 '23 at 17:36

1 Answers1

0

Your code does not compile because:

  1. You declare Students as an ICollection<Student>, but ICollection<T> does not have a method AddRange(). Only the concrete implementation List<T> has AddRange().

    See AddRange to a Collection for details.

    And of course Course does not have AddRange() because you never implemented such a method, so neither createdCourse.AddRange(...) nor createdCourse.Students.AddRange(..) will compile.

  2. You are using semicolons in your object initializers:

    new Student { idCourse = createdCourse.id; studentName = s; }
    

    You must use commas.

If you are looking for a one-line method to add all the students to Course.Students, you could use List<T>.Foreach():

public static Course FromDTO(CourseDTO dto)
{
    // How is Course.id set?
    Course createdCourse = new () { courseName = dto.courseNameDTO };
    dto.StudentsFromDTO.ForEach(name => createdCourse.Students.Add(new() { idCourse = createdCourse.id, studentName = name }));
    
    return createdCourse;
}

Note the use of the target-typed new() operator (introduced in C# 9.0) to simplify the syntax for object creation when the type can be inferred from the context.

Demo #1 here.

Alternatively, you could introduce the extension method by TrueWill from AddRange to a Collection:

public static class CollectionHelpers
{
    //https://stackoverflow.com/questions/1474863/addrange-to-a-collection
    public static void AddRange<T>(this ICollection<T> destination,
                                   IEnumerable<T> source)
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

And do:

public static Course FromDTO(CourseDTO dto)
{
    // How is Course.id set?
    Course createdCourse = new () { courseName = dto.courseNameDTO };
    createdCourse.Students.AddRange(dto.StudentsFromDTO.Select(name => new Student { idCourse = createdCourse.id, studentName = name }));
    
    return createdCourse;
}

You can't use a target-typed new() for Student in this case.

Demo #2 here.

While both these approaches are more concise than your current code, there is no reason to believe they are more efficient. Your current code and my alternatives all map a collection of one type to a collection of another type in linear time, which would seem to be as efficient as possible.

dbc
  • 104,963
  • 20
  • 228
  • 340