【问题标题】:How to store a list in SQL Server database如何在 SQL Server 数据库中存储列表
【发布时间】:2021-07-31 00:59:30
【问题描述】:

我对数据库很陌生,我一直在用 C# 编写一个学校的 windows forms .NET 项目,但遇到了一个问题。

我在 SQL Server 数据库中有一个包含患者(动物)的表,我需要添加每个患者接种的疫苗剂量列表(长度未知)。每只动物的疫苗列表中都有不同的长度和值。

谁能告诉我如何将列表存储在数据库列中?

【问题讨论】:

  • 创建存储疫苗的第二个表和存储关系动物的第三个表 - 疫苗。不在列中存储列表。
  • 如果你给每个病人一个唯一的 ID,你可以有一个包含 ID、日期、疫苗、剂量列的表格。只需根据需要添加尽可能多的条目 - 不要试图将整个列表挤成一行。
  • 是的,我知道我不应该将整个列表放在一个列中,但我不知道该怎么做。非常感谢,我现在就试试!
  • 这样一个存储在数据库中的列表称为Lookup Table:How important are lookup tables?。因此,疫苗剂量将是一个表格,例如(ID, Dosage),任何其他需要剂量值的表格都将有一个foreign key
  • SQL Server 中需要存储 多个值 的所有内容都必须是一个表 - 其他所有内容都是杂物,不利于性能和可维护性,并且违背了基本原则适当的关系数据库设计

标签: c# sql-server database winforms relational-model


【解决方案1】:

关系数据库设计的核心原则 (database normalization process) 是列应该包含原子数据。

与其在列中存储多个值(疫苗剂量),不如将它们作为行存储在单独的相关表中(动物外键)。这将固有地提供一个可变且无限大小的列表。

如果您有不同类型的疫苗,您可能还应该有额外的表格。

【讨论】:

    【解决方案2】:

    您不想在一列中存储多个值。这不是 SQLish 做事的方式。

    相反,您需要多个表。可能是这样的:

    create table animals (
        animal_id int identity(1, 1) primary key,
        . . .    -- other information about the animal
    );
    
    create table vaccinations v (
        vaccination_id int identity(1, 1) primary key,
        animal_id int not null references animals(animal_id),
        vaccination_name varchar(255),
        dosage varchar(255),
        given_at datetime,
        . . .   -- perhaps other information
    );
    

    请注意,疫苗接种列表可能存储在单独的表格中,可能每个剂量都有单独的行。您的问题没有足够的信息来确定是否是这种情况。

    另请注意,给定疫苗接种有多项信息,例如给定的日期/时间、接种疫苗的人员等。

    【讨论】:

      【解决方案3】:

      动物桌

      此表将存储有关动物的数据

      ID animal_Name
      1 Simba
      2 mando

      疫苗表

      此表将存储有关疫苗的数据

      ID Vaccine_Name
      1 vaccine1
      2 vaccine2
      3 vaccine3

      动物疫苗表

      此关系表将存储所有疫苗已给予每只动物

      animal_ID vaccine_ID given_At dosage
      1 1 15/5/2012 5-Mg
      1 2 5/9/2020 9-Mg
      2 1 6/1/2021 20-Mg
      1 1 6/4/2021 6-Mg

      从这张表你可以看到辛巴已经打过两次疫苗1,也只打过一次疫苗2,而曼多只打过一次疫苗1

      SQL

      create table animals (
          ID int identity(1, 1) primary key,
          animal_Name varchar(200) not null
      );
      
      create table vaccines (
          ID int identity(1, 1) primary key,
          Vaccine_Name varchar(200)
      );
      
      create table Animal-vaccin(
         animal_ID int not null references animals(ID),
         vaccine_ID int not null references vaccines(ID),
         dosage varchar(200),
         given_At datetime
      
      );
      

      【讨论】:

        【解决方案4】:

        所以你有一个Animals 的表和一个Vaccines 的表

        class Animal
        {
            public int Id {get; set;}
            public string Name {get; set;}
        
            ... // other properties, like BirthDate
        }
        
        class Vaccine
        {
            public int Id {get; set;}
            public string Name {get; set;}
        
            ... // other properties
        }
        

        您给动物注射一剂疫苗。要记住哪个动物得到了哪个剂量,您需要一张表格VacineDosages。在此表中,您可以看到哪种动物获得了哪种疫苗的剂量。您可能想知道动物在哪一天服用了剂量,可能还有剂量。

        Animals 和 VaccineDosages 之间存在一对多的关系:每个 Animals 获得零个或多个 VaccineDosages,每个 VaccineDosage 都被给予一个 Animals

        类似地,疫苗和疫苗剂量之间也存在一对多的关系:每个疫苗在一个疫苗剂量中被使用了零次或多次,每个疫苗剂量都是一个疫苗的剂量。

        当使用关系数据库时,一对多关系是使用外键实现的。 “多”端的项目获得它所属的“一”端项目的外键。

        每一种疫苗剂量都用于为一只动物接种疫苗;剂量“属于”这个动物,因此它得到了这个动物的外键。类似地,一个 VaccineDosage 属于一个 Vaccine,因此有一个外键

        class VaccineDosage
        {
            public int Id {get; set;}
        
            // foreign keys
            public int AnimalId {get; set;}
            public int VaccineId {get; set;}
        
            ... // other properties, like Vaccination Date and Amount
        }
        

        现在要添加 VaccineDosages,您需要知道 VaccineDosage 是针对哪种动物给予的,以及使用哪种疫苗。好吧,您不需要知道 Animal 和 Vaccine,只要它们的 Id 就可以了。

        所以假设您有一系列 VaccineDosages 尚未添加到数据库中,因此它们还没有 Id。

        Id  AnimalId VaccineId  Amount     Date
         0     10       23        10    2021-05-10
         0     10       24         5    2021-05-10
         0     12       23        10    2021-05-09
         0     13       23        10    2021-05-10
         etc.
        

        或者,在 C# 类中:

        IEnumerable<VaccineDosage> vaccineDosages = new []
        {
            new VaccineDosage
            {
                 AnimalId = 10,
                 VaccineId = 23,
                 Amount = 10,
                 Date = new DateTime(2021,05,10),
            },
            new VaccineDosage
            {
                 AnimalId = 10,
                 VaccineId = 24,
                 Amount = 5,
                 Date = new DateTime(2021, 05, 10),
            },
            ... etc
        }
        

        现在如何将此列表添加到数据库中?方法取决于你使用什么方法与数据库通信:

        • LOW 级别:使用 SQL,并逐个添加 values 参数
        • 中级:使用 nuget 包 DAPPER:您只需要提供 SQL 和对象
        • 高级:使用实体框架。

        当然,作为一个优秀的程序员,你想隐藏你保存数据的位置和方式:它可以在数据库中,但也许你想在 XML 文件中进行单元测试,CSV,Json?

        用户想知道的一切:我想将项目放入一个对象中,然后,即使在计算机重新启动后,我也可以再次从同一个对象中获取项目。

        这个“仓库”的东西,通常被称为存储库:

        class Repository
        {
            public int AddAnimal(Animal animal} {...}
            public int AddVaccine(Vaccine vaccine {...}
            public int AddDosage(VaccineDosage dosage) {...}
        

        这些方法返回新创建的添加项的Id,所以稍后你可以通过Id获取它们:

            public Animal FetchAnimal(int animalId) {...}
        

        您可以根据需要向存储库添加方法:

            public IEnumerable<Animal> FetchAllAnimals() {...}
            public IEnumerable<Animal> FetchAnimalsOfOwner(int ownerId) {...}
            public IEnumerable<Animal> FetchUnvaccinateAnimals(int vaccineId) {...}
        

        等等

        在您的情况下,您还需要一种方法来添加一系列 VaccineDosages。

        public void AddDosages(IEnumerable<VaccineDosage> dosages) {...}
        

        也许所有的 VaccineDosages 都被插入到同一个 Animal 中,或者在同一个日期,如果是这样的话,考虑创建超载:

        public void AddDosages(int animalId, IEnumerable<Vaccine> vaccins, ...)
        

        为了让您的生活更轻松,这些重载可以调用您的原始版本。您可能不会每秒添加 1000 个疫苗。

        存储库的好处是您隐藏了数据的保存方式。对于小型项目和单元测试,您可以将其保存为 CSV 文件或 XML。稍后您可以使用 SQL 安全地将其更改为保存在数据库中,您可以更改数据库的类型,或用于与 DBMS 通信的方法。您的存储库的用户无需更改,因此无需再次测试。

        LOW 级别:带参数的 SQL

        如果您仍在学习数据库,最好从低级别开始,这样您就可以在使用高级方法时感觉到哪些代码是“在后台”完成的

        在与数据库进行低级别通信时,您会创建一个数据库连接。您要求连接创建命令,然后用 SQL 文本和参数填充命令。最后执行命令。

        通常您不希望长时间打开数据库连接。您为一个操作创建一个打开和关闭数据库连接的过程:添加一个 VaccineDosage,或一个相当小的 VaccinedDosage 序列。

        如果您需要在一次调用中将一百万个疫苗剂量添加到数据库中,那么您就是在谈论批量使用。这可能需要不同的方法。这超出了您的问题范围。

        一个数据库连接有一个连接字符串。这取决于您使用的数据库管理系统 (DBMS),是否需要提供此连接字符串。如果您不提供,应用程序将使用文件 app.config 中提供的连接字符串。

        要隐藏您使用的实际 DBMS,请考虑创建一个工厂方法来为您创建正确的数据库连接:

        public DbConnection CreateDatabaseConnection()
        {
            // for example: use SQLight as database:
            string dbConnectionString = this.CreateDbConnectionString();
            return new System.Data.SQLite.SQLiteConnection(dbConnectionString);
        }
        

        如果以后您决定使用不同的 DBMS,您只需更改此过程。

        隐藏表的名称,以便以后更改它们:

        private const string tableNameAnimals = "Animals";
        private const string tableNameVaccines = "Vaccines";
        private const string tableNameVaccineDosages = "VaccineDosages";
        
        public void Add(VaccineDosage dosage)
        {
            const string sqlText = @"Insert into table " + tableNameVaccineDosages
                + " (AnimalId, VaccineId, Amount, Date)"
                + " Values(@AnimalId, @VaccineId, @Amount, @Date);"
        
            using (var dbConnection = this.CreateDatabaseConnection())
            {
                dbConnection.Open();
                using (var dbCommand = dbConnection.CreateDbCommand())
                {
                    dbCommand.CommandText = sqlText;
        
                    // add all parameters:
                    dbCommand.Parameters.AddWithValue("@AnimalId, dosage.AnimalId);
                    dbCommand.Parameters.AddWithValue("@VaccineId, dosage.VaccineId);
                    ... // add the other parameters
        
                    // Execute the command:
                    dbCommand.ExecuteNonQuery();
                }
            }
        }
        

        using 声明断言所有物品一旦不再使用就会正确关闭和丢弃

        如果你想添加一堆VaccineDosages,你可以多次调用这个方法。或者稍微优化一下,重用dbCommand

        public void Add(IEnumerable<VaccineDosage> dosages)
        {
            const string sqlText = @"Insert into table " + tableNameVaccineDosages
                + " (AnimalId, VaccineId, Amount, Date)"
                + " Values(@AnimalId, @VaccineId, @Amount, @Date);"
        
            using (var dbConnection = this.CreateDatabaseConnection())
            {
                dbConnection.Open();         
                foreach (var dosage in dosages)
                {
                    using (var dbCommand = dbConnection.CreateDbCommand())
                    {
                         ... etc.
                    }
                }
            }
        }
        

        当然,您可以省略使用参数并创建一个包含完整 sql 命令的 sql 文本,但这可能很危险:人们可能会破坏您的数据库。见SQL Injection attack

        始终尝试使用常量 sql 字符串作为文本。不要编辑 sql 字符串,要为参数添加值,请始终使用Parameters.AddWithValue

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-08-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-06
          • 2021-12-12
          • 1970-01-01
          相关资源
          最近更新 更多