如何在C#linq asp.net MVC中使用逗号分隔值列连接两个表

时间:2018-12-03 11:14:10

标签: c# asp.net-mvc linq model-view-controller

我有两个表ProfileCourseList

Profile表:

ID   | Name   | CourseId
-----+--------+----------
 1   | Zishan |  1,2                                          
 2   | Ellen  |  2,3,4 

CourseList表:

courseid | coursename 
---------+--------------
   1     |  java
   2     |  C++
   3     |  oracle
   4     |  dot net

在这里,我要加入两个表并获取结果,然后在“查看”页面中显示特定ID,例如...

如果在我的视图中呼叫ID = 1:

名称:紫山,

课程名称:java,C ++

如果我认为ID = 2:

名称:Ellen,

课程名称:java,C ++,oracle。

因此,我在控制器中编写了此Linq查询:

var account = db.Profile.Where(x => x.PId == pid).FirstOrDefault();

string  CourseIDS = string.Join(",",account.CourceId);

var courselist = (from p in db.profile
                  join co in db.CourceList on p.CourceId equals co.courseId 
                  .ToString()  into co1 from co2 in co1.Where(x => 
                   LanguageIDS.Contains(x.courseId.ToString()))
            select new ViewProfileVM
            {
               courseName = string.Join(",", co2.courseName)
            }).FirstOrDefault();

ViewBag.courselist = courselist;

然后我通过ViewBag来查看和显示结果.....

请帮助我linq查询无法正常工作,将来我想像加入CourceList一样添加join LanguagesList和CountryList,所以请为我建议一个简单的解决方案。

谢谢

2 个答案:

答案 0 :(得分:1)

我仅次于John M的评论,在关系数据库中不建议使用逗号分隔值。但是,既然有了它,就可以使用以下方法获得预期的结果。

var profileList = profiles.SelectMany(x=> x.CourseId.Split(',')
                        .Select(c=> 
                        new 
                        {
                            Name= x.Name, 
                            CourseId = int.Parse(c) 
                        }));
var result = profileList.Join(courses,
                p=>p.CourseId,
                c=>c.Id,
                (p,c)=>new {Name = p.Name, Courses= c.CourseName})
                .GroupBy(x=>x.Name)
                .Select(x=>new 
                {
                Name = x.Key, 
                Courses = string.Join(",",x.ToList().Select(k=>k.Courses))
                });

配置文件和课程是数据结构中代表类的集合。

public class Profile
{
public int Id{get;set;}
public string Name{get;set;}
public string CourseId{get;set;}
}


public class Coursename
{
public int Id{get;set;}
public string CourseName{get;set;}
}

这将为您提供预期的输出

名称:紫山 课程:Java,C ++

名称:Ellen 课程:C ++,Oracle,点网

以下是我用于测试的示例数据。

var profiles = new Profile[]
                {
                    new Profile{Id = 1, Name = "Zishan", CourseId = "1,2"},
                    new Profile{Id = 2, Name = "Ellen", CourseId = "2,3,4"}
                };
var courses = new Coursename[]
                {
                    new Coursename{Id=1, CourseName = "Java"},
                    new Coursename{Id=2, CourseName = "C++"},
                    new Coursename{Id=3, CourseName = "Oracle"},
                    new Coursename {Id=4, CourseName = "Dot Net"}
                };

希望有帮助

答案 1 :(得分:1)

It seems that every Profile has zero or more Courses, and every Course belongs to zero or more Profiles: a straightforward many-to-many relationship.

Normally in a relational database, this would be implemented using a junction table: a separate table, with two columns that act as foreign keys: ProfileId and CourseId. In your example:

  • For Zishan, who has Id 1 we would have [1, 1] and [1, 2], indicating that Zishan attends Courses with Id 1 and 2;
  • For Ellen with Id = 2, we would have [2, 2] [2, 3] [2,4].

Alas, your database designer chose not to follow this standard database pattern. And you are stuck with the problems.

The best solution depends a bit on how long you will be stuck with this database, how often it will change, how often you will ask for "Profiles with their Courses" and "Courses with their attendants".

Best Solution: add a junction table to the database

The best solution would be a one-time migration of your database: create the junction table and add the [profile.Id, Course.Id] combinations to this list. After that remove the CourseId column.

class ProfileCourse
{
    public int ProfileId {get; set;}
    public int CourseId {get; set;}
}

var ProfileIdCourseIdCombinations = db.Profiles
    .SelectMany(profile => profile.CourseIds.Split(',", StringSplitOptions.RemoveEmptyEntries),

    (profile, splitCourseId) => new ProfileCourse
    {
         ProfileId = profile.Id,
         CourseId = splitCourseId,
    });

This has to be done only once. Similar queries in future will be fairly standard. Everyone who is a fluent LINQer, will know how to implement the queries. Disadvantage: the database change.

Sub-optimal solution: mimic the junction table

Quite often people have an intermediate (adapter) layer between the use cases and the actual database. This way you can change the database without having to change the users, or vice versa. The pattern for this adapter layer is quite often called the repository pattern.

The Idea is that you create a Repository class that exposes your tables as IEnumerable sequence of items. The junction table will not be a real table, it will be created whenever it is asked for.

class Repository
{
    public Respository(MyDbContext db)
    {
        this.db = db;
    }
    private readonly MyDbContext db;
    private IReadonlyCollection<ProfileCourse> profilesCourses = null;

    public IEnumerable<Profile> Profiles => this.db.Profiles;
    public IEnumerable<Course> Courses => this.db.Courses;

    public IEnumerable<ProfileCourse> ProfilesCourses
    {
         get
         {
             if (this.profilesCourses == null
             {
                 this.profilesCourses = ... // the SelectMany from above
                     .ToList();
             }
             return this.profilesCourses;
         }
    }
}

This way your junction table will be created only once per time you create your Repository. It will only be created when used.

This solution is sub-optimal, because the junction table needs to be recreated every time you'll use it in a new Repository. Furthermore, you can't add Profiles nor Courses using this Repository class. If will be quite some work to create functions to Add / Remove profiles and courses to your Repository. It is possibly easier to recreate the repository.

You'll get your answer by asking the repository instead of your db

var repository = new Repository(db);
var profilesWithTheirCourses = repository.Profiles.GroupJoin(repository.ProfilesCourses,

    profile => profile.Id,                       // from every profile take the Id
    profileCourse => profileCourse.ProfileId,    // from every profileCourse take the ProfileId,

    (profile, profileCourses) => new     // take every profile with all its matching
    {                                    // profileCourses, to make a new obhect
        // Profile properties; add only those you plan to use:
        Id = profile.Id,
        Name = profile.Name,

        Courses = profileCourses.Join(repository.Courses, // join profileCourse with courses
           profileCourse => profileCourse.CourseId,       // from profileCourse take CourseId
           course => course.Id,                            // from course take Id
           (profileCourse, course) => new                  // when they match make a new
           {   // again: only the properties you actually plan to use
               Id = course.Id,
               Name = course.Name,
           })
           .ToList(),
    });

Solution for this problem only

If you decide to make a solution for this problem only, you can combine the SelectMany and the Join into one big LINQ statement.

Advantage: quick solution; Disadvantage: difficult to read, to test, and to maintain. Similar problems in future will have a similar sub-optimal solution,

var profilesWithTheirCourses = db.Profiles.SelectMany(
    profile => profile.CourseIds.Split(',", StringSplitOptions.RemoveEmptyEntries),
    (profile, splitCourseIds) => new
    {
        Id = profile.Id,
        Name = profile.Name,

        Courses = splitCourseIds.Join(db.Courses,
           courseId => courseId,
           course => course.Id,
           (courseId, course) => new
           {
               Id = course.Id,
               Name = course.Name,
           })
           .ToList(),
    });