【问题标题】:how to join two tables with comma separated values column in c# linq asp.net mvc如何在c#linq asp.net mvc中使用逗号分隔值列连接两个表
【发布时间】:2019-05-04 16:21:22
【问题描述】:

我有两张桌子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:

姓名:艾伦,

课程名称: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 一样添加加入 LanguagesList 和 CountryList 所以请建议我为这个问题提供一个简单的解决方案..

谢谢,

【问题讨论】:

  • 首先,这是对关系数据库的不正确使用 - 您的个人资料表中不应该有一个逗号分隔的课程列表,而是有一个包含个人资料 ID 和课程 ID 的链接表.此外,“不工作”可能意味着一百万件事 - 请更具体。
  • 您的LanguagesListCountryList 将如何与Profile 相关联?就像你的 ProfileCourseListCourseId 相关...
  • 在我的应用程序中,用户需要从下拉列表中选择一些课程列表和语言列表,我保存在数据库中的所选列表以逗号分隔,如 1,2,3...等
  • @Haree,我想你不明白我的意思......让我们说 -> 如果你从下拉列表中选择国家,那么这些值将与配置文件的国家列表逗号分隔值匹配.然后,如果您从下拉列表中选择语言,那么这些值将在配置文件中匹配。因为 profile 只包含 countryids 而没有 languageid 列

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


【解决方案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++

姓名:艾伦 课程:C++,Oracle,Dot Net

以下是我用于测试的样本数据。

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"}
                };

希望有帮助

【讨论】:

  • 我在 SelectMany(x=> x.CourseId.Split(',') "LINQ to Entities 无法识别方法 'System.String[] Split(Char[])' 时遇到错误方法,并且该方法不能翻译成商店表达式。”。
  • String.Split 不受 Linq To Sql 支持,一种快速的解决方法是将您的数据读取到内存中的集合并按照上述示例代码中的操作对其运行查询。
【解决方案2】:

似乎每个Profile 都有零个或多个Courses,每个Course 都属于零个或多个Profiles:一个简单的多对多关系。

通常在关系数据库中,这将使用联结表实现:一个单独的表,有两列充当外键:ProfileId 和 CourseId。在您的示例中:

  • 对于 ID 为 1 的紫山,我们将有 [1, 1] 和 [1, 2],表示紫山参加 ID 为 1 和 2 的课程;
  • 对于 ID = 2 的 Ellen,我们将有 [2, 2] [2, 3] [2,4]。

唉,你的数据库设计者选择不遵循这个标准的数据库模式。而你被问题困住了。

最佳解决方案在一定程度上取决于您将在此数据库中停留多长时间、多久更改一次、您多久会要求“Profiles with their Courses”和“Courses with their assistants”。

最佳解决方案:向数据库添加联结表

最好的解决方案是一次性迁移数据库:创建联结表并将[profile.Id, Course.Id] 组合添加到此列表中。之后删除 CourseId 列。

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,
    });

这只能执行一次。将来类似的查询将是相当标准的。每个精通 LINQer 的人都会知道如何实现查询。缺点:数据库变化。

次优解决方案:模仿联结表

人们通常在用例和实际数据库之间有一个中间(适配器)层。这样您就可以更改数据库而无需更改用户,反之亦然。此适配器层的模式通常称为存储库模式。

想法是您创建一个 Repository 类,将您的表公开为 IEnumerable 项目序列。联结表不是真正的表,它会在需要时创建。

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;
         }
    }
}

这样,每次创建存储库时,您的联结表只会创建一次。它只会在使用时创建。

此解决方案不是最理想的,因为每次在新存储库中使用联结表时都需要重新创建它。此外,您不能使用此 Repository 类添加配置文件或课程。如果要创建将配置文件和课程添加/删除到您的存储库的功能将需要做很多工作。重新创建存储库可能更容易。

您将通过询问存储库而不是您的数据库来得到答案

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(),
    });

仅针对此问题的解决方案

如果您决定只为这个问题制定解决方案,您可以将SelectManyJoin 组合成一个大的LINQ 语句。

优点:快速解决;缺点:难以阅读、测试和维护。未来类似的问题会有类似的次优解,

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(),
    });

【讨论】:

  • 我会试试这个解决方案。但是将来我想添加和加入 LanguagesList、CountryList、EducationList、OccupationList..etc.. 就像 CourseList 一样,所以请建议我为这个问题提供一个简单的解决方案..
  • 唯一合适的解决方案是规范化您的数据库。您等待的时间越长,您将来会看到越多的问题:它会更慢、容易出错、难以更改……您必须让您的项目负责人相信一次性迁移是唯一正确的方法
猜你喜欢
  • 1970-01-01
  • 2011-10-15
  • 2013-10-06
  • 1970-01-01
  • 1970-01-01
  • 2023-04-02
  • 2020-03-05
  • 2021-10-13
相关资源
最近更新 更多