简体   繁体   中英

C# Populate nested class using foreign key

I need to pull back information from two separate Database calls into one object.

Consider the following classes

public class Status
{
    public int StatusId { get; set; }
    public string Name { get; set; }
}
public class Person
{
    public int PersonId { get; set; }
    public Status PersonStatus { get; set; }
}

These represent two database tables Persons and Status that are related by the StatusId in the Persons table.

CREATE TABLE [dbo].[Status](
    [StatusId] [INT] NOT NULL PRIMARY KEY,
    [Name] [NVARCHAR](50) NOT NULL )

CREATE TABLE [dbo].[Persons](
    [PersonId] [int] NOT NULL PRIMARY KEY,
    [StatusId] [int] NOT NULL REFERENCES dbo.Status (StatusId) )

Here is the procedure to get People:

CREATE PROCEDURE dbo.spGetPersons
AS
BEGIN
    SELECT  PersonId ,
            StatusId
    FROM    dbo.Persons
END

I then read the data from the SqlDataReader , construct a Person object, and return an IList<Person> . In order to populate the PersonStatus property, currently I pass the StatusId into a secondary procedure which makes another round trip to the database.

IList<Person> People = new List<Person>();
while (reader.Read())
{
    People.Add(new Person()
    {
        PersonId = (int)reader["PersonId"],
        //This causes the extra round trip to the database
        StatusId = new StatusDao().GetStatus((int)reader["StatusId"])
    });
}

StatusDao().GetStatus() just executes the following procedure and constructs a Status object.

CREATE PROCEDURE dbo.spGetStatus
    @StatusId INT = NULL
AS
    BEGIN
        SELECT  StatusId ,
                Name
        FROM    dbo.Status
        WHERE   @StatusId IS NULL
                OR @StatusId = StatusId
    END

What is the proper way to populate the PersonStatus property? Obviously I shouldn't do an additional round trip to the database for each Person . Let's assume I can't edit the procedure and just add Name to the SQL query.

I did see this question that suggested using an enumerator. That's a good option in some cases, but let's just assume that the Status data changes regularly. That makes this no longer an option.

I am not currently using an ORM.

I can't add a new Stored Procedure, nor can I edit the existing stored procedure. I'm also not allowed to use Inline SQL.

Since you have a rather odd requirement of not being able to add a new Stored Procedure or Using Inline SQL; it's going to take two trips to the database no matter what (Unless, again, Status is something that can be captured in an Enum).

If you have a Status object and a Person Object and you want to put them together, you can use the following method:

public static List<Person> GetStatusForPerson(List<Person> people, List<Status> statuses)
{
    List<Person> peopleWithStatuses = new List<Person>();
    foreach (var p in people) 
    {
        Person person = new Person();
        person.PersonId = p.PersonId;
        person.Status = statuses.Where(s => s.StatusId == person.StatusId).FirstOrDefault());

        peopleWithStatuses.Add(person);
    }
    return peopleWithStatuses;
}

To get this information in one round-trip to the Database (for anyone who has this issue without a weird requirement):

var persons = new List<Person>();
using (SqlConnection connection = new SqlConnection(connectionString)) {
    string sqlText = "Select p.PersonId, ps.Name, ps.StatusId FROM Person p INNER JOIN PersonStatus ps on p.StatusID = ps.StatusId WHERE p.PersonId = @personId";
    SqlCommand command = new SqlCommand();
    command.Parameters.Add("@personId", SqlDbType.Int);
    command.Parameters["@personId"].Value = personId;
    connection.Open();
    using (SqlDataReader reader = command.ExecuteReader()) {
        while (reader.Read()) 
        {
            var person = new Person();
            var status = new PersonStatus();
            person.PersonID = reader["PersonId"]; 
            status.StatusId = reader["StatusId"];
            status.Name = reader["Name"];
            person.Status = status;
            persons.Add(person);
        }
    }
}
return persons;

Use Enums

If Status is a series of integer constants (much like an enum ) then you could simply use an enum and store the possible states.

If Status dynamically adds or removes values, then you have to resort to the normal method of pulling data back when you're not using an ORM: A DataReader .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM