小巧的一对多关系

时间:2017-12-01 22:11:33

标签: c# asp.net-mvc asp.net-core asp.net-core-mvc dapper

我有以下列方式构建的3个表:

CREATE TABLE [User](
    Id int NOT NULL,
    Name varchar(50)
    PRIMARY KEY (Id)
)

CREATE TABLE [Role](
    Id int NOT NULL,
    UserId int NOT NULL,
    Name varchar(50),
    PRIMARY KEY (Id),
    FOREIGN KEY (UserId) REFERENCES [User](Id)
)


CREATE TABLE [Description](
    Id int NOT NULL,
    RoleId int NOT NULL,
    Name varchar(50)
    FOREIGN KEY (RoleId) REFERENCES [Role](Id)
)

正如你所看到的那样,它是嵌套两次的一对多关系。在代码中,我有以下类来表示它们:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IEnumerable<Role> Roles { get; set; }
}

public class Role
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public string Name { get; set; }

    public IEnumerable<Description> Descriptions { get; set; }
}

public class Description
{
    public int Id { get; set; }
    public int RoleId { get; set; }
    public string Name { get; set; }
}

现在我需要查询用户并获取随附的所有字段。我已经找到了使用QueryMultiple这样做的方法:

var queryOne = "SELECT Id, Name FROM [User] WHERE Id = 1";
var queryTwo = "SELECT r.Id, r.UserId, r.Name FROM  [User] u INNER JOIN [Role] r ON u.Id = r.UserId WHERE u.Id = 1";
var queryThree = "SELECT d.Id, d.RoleId, d.Name FROM  [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";

var conn = new SqlConnection();

using (var con = conn)
{
    var result = con.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
    var users = result.Read<User>().FirstOrDefault();
    var roles = result.Read<Role>();
    var descriptions = result.Read<Description>();
    if (users != null && roles != null)
    {
        users.Roles = roles;
        Console.WriteLine("User: " + users.Name);
        foreach (var role in users.Roles)
        {
            Console.WriteLine("Role: " + role.Name);
            if (descriptions != null)
            {
                role.Descriptions = descriptions.Where(d => d.RoleId == role.Id);
                foreach (var roleDescription in role.Descriptions)
                {
                    Console.WriteLine("Description: " + roleDescription.Name);
                }
            }
        }
    }
}

结果是:

  

用户:鲍勃   作用:测试仪
  描述:测试仪第一个描述
  描述:测试仪第二描述
  描述:测试仪第三描述
  职责:经理
  描述:经理第一描述
  说明:经理第二说明
  描述:经理第三描述
  作用:程序员
  描述:程序员第一描述
  描述:程序员第二描述
  描述:程序员第三个描述

主要问题: 虽然上面的代码工作,但感觉太乱了。我想知道是否有更好/更简单的方法来实现这一目标?

奖励积分: 还可以建议一种比使用内部联接更好的查询方法。我的目标是提高绩效。

编辑:

我也提出了选项二,但我再也认为它不是一个好的解决方案。使用选项2,我创建了一个第4个对象,它将包含3个对象的结果,例如:

public class Combination
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public int RoleId { get; set; }
    public string RoleName { get; set; }
    public int DescriptionId { get; set; }
    public string DescriptionName { get; set; }
}

然后我按照这样处理:

var queryFour = "SELECT u.Id as 'UserId', u.Name as 'UserName', r.Id as 'RoleId', r.Name as 'RoleName', d.Id as 'DescriptionId', d.Name as 'DescriptionName' FROM  [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";

var conn = new SqlConnection();
using (var con = conn)
{
    var myUser = new User();
    var result = con.Query<Combination>(queryFour);
    if (result != null)
    {
        var user = result.FirstOrDefault();
        myUser.Id = user.UserId;
        myUser.Name = user.UserName;
        var roles = result.GroupBy(x => x.RoleId).Select(x => x.FirstOrDefault());
        var myRoles = new List<Role>();
        if (roles != null)
        {
            foreach (var role in roles)
            {
                var myRole = new Role
                {
                    Id = role.RoleId,
                    Name = role.RoleName
                };

                var descriptions = result.Where(x => x.RoleId == myRole.Id);
                var descList = new List<Description>();
                foreach (var description in descriptions)
                {
                    var desc = new Description
                    {
                        Id = description.DescriptionId,
                        RoleId = description.RoleId,
                        Name = description.DescriptionName
                    };
                    descList.Add(desc);
                }
                myRole.Descriptions = descList;
                myRoles.Add(myRole);
            }
        }
        myUser.Roles = myRoles;
    }

    Console.WriteLine("User: " + myUser.Name);
    foreach (var myUserRole in myUser.Roles)
    {
        Console.WriteLine("Role: " + myUserRole.Name);
        foreach (var description in myUserRole.Descriptions)
        {
            Console.WriteLine("Description: " + description.Name);
        }
    }
}

两种方法的结果输出相同,第二种方法使用1个查询而不是3个。

编辑2:需要考虑的事项,这3张表的数据经常更新。

编辑3:

private static void SqlTest()
{
    using (IDbConnection connection = new SqlConnection())
    {
        var queryOne = "SELECT Id FROM [TestTable] With(nolock) WHERE Id = 1";
        var queryTwo = "SELECT B.Id, B.TestTableId FROM [TestTable] A With(nolock) INNER JOIN [TestTable2] B With(nolock) ON A.Id = B.TestTableId WHERE A.Id = 1";
        var queryThree = "SELECT C.Id, C.TestTable2Id FROM [TestTable3] C With(nolock) INNER JOIN [TestTable2] B With(nolock) ON B.Id = C.TestTable2Id INNER JOIN [TestTable] A With(nolock) ON A.Id = B.TestTableId WHERE A.Id = 1";

            var gridReader = connection.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
            var user = gridReader.Read<Class1>().FirstOrDefault();

            var roles = gridReader.Read<Class2>().ToList();

            var descriptions = gridReader.Read<Class3>().ToLookup(d => d.Id);

            user.Roles= roles;

            user.Roles.ForEach(r => r.Properties = descriptions[r.Id].ToList());
    }
}

3 个答案:

答案 0 :(得分:1)

另一种方法(使用.Net Core的工作代码段):

  1. 为什么不创建数据库这样的视图 -
  2.   

    创建视图myview       如       选择u.Id作为&#39; UserId&#39;,u.Name as&#39; UserName&#39;,r.Id as&#39; RoleId&#39;,       r.Name as&#39; RoleName&#39;,d .Id as&#39; DescriptionId&#39;,d.Name as&#39; DescriptionName&#39;       来自用户u INNER JOIN角色r ON u.Id = r.UserId       INNER JOIN描述d ON r.Id = d.RoleId;

    1. 然后您可以使用清洁查询,如下所示:

          public List<Combination> GetData(int userId)
          {
              String query = "select * from myview" + " where userId = " + userId + ";";
      
              using (System.Data.Common.DbConnection _Connection = database.Connection)
              {
                  _Connection.Open();
                  return _Connection.Query<Combination>(query).ToList();
              }
          }
      
    2. 您的处理代码如下所示:

    3. [注意:这可以进一步增强。]

              public static void process (List<Combination> list)
              {
                  User myUser = new User(); myUser.Id = list[0].UserId; myUser.Name = list[0].UserName;
                  var myroles = new List<Role>(); var r = new Role(); string currentRole = list[0].RoleName;
                  var descList = new List<Description>(); var d = new Description();
      
                  // All stuff done in a single loop.
                  foreach (var v in list)
                  {
                      d = new Description() { Id = v.DescriptionId, RoleId = v.RoleId, Name = v.DescriptionName };
                      if (currentRole == v.RoleName)
                      {
                          r = new Role() { Id = v.RoleId, Name = v.RoleName, UserId = v.UserId, Descriptions = descList };                    
                          descList.Add(d);
                      }
                      else
                      {
                          myroles.Add(r);
                          descList = new List<Description>(); descList.Add(d);
                          currentRole = v.RoleName;
                      }
                  }
                  myroles.Add(r);
                  myUser.Roles = myroles;
      
                  Console.WriteLine("User: " + myUser.Name);
                  foreach (var myUserRole in myUser.Roles)
                  {
                      Console.WriteLine("Role: " + myUserRole.Name);
                      foreach (var description in myUserRole.Descriptions)
                      {
                          Console.WriteLine("Description: " + description.Name);
                      }
                  }
              }
      

      从性能角度来看,Inner Joins比其他形式的查询更好,如子查询,Co相关子查询等。但最终所有内容都归结为SQL执行计划。

答案 1 :(得分:1)

您的第一个选项可以简化为以下内容。它删除了深层控制结构嵌套。您可以将其推广到更深的嵌套,而无需添加更多嵌套/复杂性。

var queryOne = "SELECT Id, Name FROM [User] WHERE Id = 1";
var queryTwo = "SELECT r.Id, r.UserId, r.Name FROM  [User] u INNER JOIN [Role] r ON u.Id = r.UserId WHERE u.Id = 1";
var queryThree = "SELECT d.Id, d.RoleId, d.Name FROM  [User] u INNER JOIN [Role] r ON u.Id = r.UserId INNER JOIN [Description] d ON r.Id = d.RoleId WHERE u.Id = 1";

var conn = new SqlConnection();

using (var con = conn)
{
    var gridReader = con.QueryMultiple(queryOne + " " + queryTwo + " " + queryThree);
    var user = gridReader.Read<User>().FirstOrDefault();
    if (user == null)
    {
        return;
    }

    var roles = gridReader.Read<Role>().ToList();
    var descriptions = gridReader.Read<Description>().ToLookup(d => d.RoleId);

    user.Roles = roles;
    roles.ForEach(r => r.Descriptions = descriptions[r.Id]);
}

在性能方面,它的行为与您的第一个选项相同。

我不会选择您的第二个选项(或类似的基于一个选项):如果您有R角色,并且每个角色平均有D个描述,您将查询6 * R * D单元而不是2 + 3 * R + 3 * d。如果R和D很高,那么您将查询更多数据,与运行3个查询而不是1相比,反序列化的成本会更高。

答案 2 :(得分:0)

我回答你的问题:

摘要。你应该把事情分解成他们做的不同工作。 C#为您提供了函数和类,它们可以帮助您清理很多代码。

如果你可以查看一大块代码然后说&#34;这个块基本上是拿东西并用它来做一些结果...&#34;然后创建一个名为SomeResult DoBlah(SomeThing thing)的函数

不要让更多的代码吓到你,这可能会使大局看起来更加复杂乍一看。但它会使小块代码更容易理解,这使得大局更容易理解。

我的完整答案:

你可以做的一些事情会让这个更干净。您可以为自己做的最大的事情可能是分离出一些责任/担忧。你有很多不同的事情发生在一起,所有这些都在一个地方,依赖于一切都像它一样,永远。如果你改变某些东西,你将不得不改变所有地方的东西,以确保一切正常。这被称为&#34;耦合&#34;,耦合是坏的...嗯 - 凯。

在最广泛的层面上,您有三种不同的事情:1。您正在尝试将逻辑应用于用户和角色(即您希望获取角色中所有用户的信息。)2。您正在尝试从数据库中提取信息,并将其放入用户,角色和描述对象中。 3.您正在尝试显示有关用户,角色和描述的信息。

所以如果我是你,那我至少会有3门课。 \

  • Program这是您的应用程序的驱动程序。它应该有一个主要功能,应该使用其他3个类。

  • DisplayManager可以负责将信息写入控制台。它应该使用IEnumerable<User>构造,并且应该有一个公共方法public void DisplayInfo(),它将其用户列表写入控制台。

  • UserDataAccess,可用于从数据库中提取用户列表。我希望它为public IEnumerable<User> GetUsersByRoleID(int roleID)

  • 公开一个公共方法

这样,您的主程序看起来像这样:

public static void Main(String[] args)
{
    int roleID = 6;

    UserDataAccess dataAccess = new UserDataAccess();
    IEnumerable<User> usersInRole = dataAccess.GetUsersByRoleID(roleID);

    DisplayManager displayManager = new DisplayManager(usersInRole);
    displayManager.DisplayInfo();
}

老实说,这是我要做的简化版本,如果问题有更多功能,那么只需获取一个特定的信息。您应该研究SOLID原则。特别是&#34;单一责任&#34;和&#34;依赖倒置&#34;,这些原则可以真正清理一些&#34;凌乱&#34; /&#34;臭&#34;代码。

接下来,就实际数据访问而言,由于您使用的是dapper,因此您应该能够使用内置dapper功能来映射嵌套对象。我相信this link在这方面对你有所帮助。