Home > Software design >  C# retrieve data from SQL Server using SqlDataReader to Master Detail List
C# retrieve data from SQL Server using SqlDataReader to Master Detail List

Time:10-29

I have Master and Detail classes:

class Master
{
    public int ID { get; set; }
    public string Name { get; set; }
    public List<Detail> Details { get; set; }
}

class Detail
{
    public Description { get; set; }
    public Amount { get; set; }
}

I use below approach and working fine now.

List<Master> result = new List<Master>();

// SQL Connection 

string sqlCommand = "SELECT * FROM Master LEFT JOIN Detail on Master.ID = Detail.ID";

using (System.Data.SqlClient.SqlDataReader dr = db.DbDataReader as System.Data.SqlClient.SqlDataReader)
{
    if (dr.HasRows)
    {
        Master LastMaster = null;

        while (dr.Read())
        {
            if (LastMaster == null || Convert.ToInt(dr["ID"]) != LastMaster.ID)
            {
                Master h = new Master();
                h.ID = Convert.ToInt(dr["ID"]);
                h.Name = Convert.ToString(dr["Name"]);
                result.Add(h);
                
                LastMaster = h;
            }           
            
            if (dr["Description"] == DBNull.Value)
                continue;
                
            if (h.Detail == null)
                h.Detail = new List<Detail>();
            
            Detail d = new Detail();
            d.Description = dr["Description"] as string;
            d.Amount = Convert.ToDouble(dr["Amount"]);
            
            LastMaster.Detail.Add(d);
            ......

        }
    }       
    .....
}

Is there any better approach to fill list of list objects in C# ? I appreciate any suggestion. Thanks.

CodePudding user response:

You can use Dapper (a micro ORM) for your scenario. Below is a sample code

const string createSql = @"
create table #Users (Id int, Name varchar(20))
create table #Posts (Id int, OwnerId int, Content varchar(20))

insert #Users values(99, 'Sam')
insert #Users values(2, 'I am')

insert #Posts values(1, 99, 'Sams Post1')
insert #Posts values(2, 99, 'Sams Post2')
insert #Posts values(3, null, 'no ones post')";

using(var connection = new SqlConnection("database connection string"))
{
    
    connection.Execute(createSql);
    try
    {
        const string sql =@"select * from #Posts p 
                                left join #Users u on u.Id = p.OwnerId 
                            Order by p.Id";

        var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post; }).ToList();
    }
    catch(Exception ex){}
}

CodePudding user response:

Ibram commented about EF and Dapper and Abu gave an example for Dapper (but I'm not sure it demos generating a graph with a single master and multiple detail per master, as you have - dapper can do so if you want to explore it)

In EF we could do something like:

  • install EF core power tools - as you have a db already we will use it to generate classes from. This operation can just be done with the command line but EFCPT makes a lot of operations easier
  • right click your project, choose EF Core Power Tools .. Reverse Engineer
  • fill in a new connection string detail
  • choose the database objects you wish to turn into classes
  • set other options as appropriate (you can find out more about them later, maybe only use the pluralize one for now, if your db tables are like Orders, Customers, Companies and you want your classes called Order/Customer/Company (classes should not have plural names). Tick on "put connectionstring in code" for now- you can remove it to config file later
  • finish. Eventually you'll get some classes and a context that has a load of code in OnModelCreating that lays out a description of everything in the tables, the columns, keys, relationships..

Now you can run some query like:

var c = new YourContext();
var ms = c.Masters.Include(m => m.Details).ToList();

That's basically the equivalent of what you posted

You can get more trick by shaping a more involved linq query:

var q = c.Masters.Include(m => m.Details)
  .Where(m => m.Name.StartsWith("Smith"))
  .OrderBy(m => m.Name);

var ms = q.ToList();

It will be translated into something like

SELECT * FROM master join detail on ...
WHERE name LIKE 'Smith%'
ORDER BY m.name

You can see the generated query if you inspect the DebugView property of q

You could make changes:

ms[0].Details.Clear(); //causes delete of all details for this master
ms[1].Details.Add(new Detail { someprop = some value}); //causes insert of new details for this master
ms[2].Name = "Hello"; //causes update of this master name
c.SaveChanges(); //carries out the above, in sql, to affect the db

When you manipulate the returned objects and save, EF will delete/insert/update as appropriate to sync the db to what happened to the objects. It is important that you understand that EF tracks what happens to all the objects it creates, so that it can do this


When would you use EF and when would you use Dapper? Well, it doesn't have to be mutually exclusive; you can use them in the same project. Generally I'd say use EF (or some other ORM like it - nHibernate is another popular one, works on a similar concept of translating linq expressions to sql and tracking the data back into an object) for stuff where the sql is so simple that it's a productivity boost to not have to write it, track it, and write the changes back. What it is not intended for, is forming as hoc queries that don't map well to client side objects. For that you can use Dapper, or you could form client side objects and add them to EF's model and then run raw sql that populates them. Dapper is fast, because it doesn't do any of that tracking changes, mapping or wiring up complex object graphs; you do all that manually. Dapper makes a convenient abstraction over raw sql and creates classes, but EF goes much further; it comes at a cost - EF is highly convenient but much more heavy weight.

  • Related