Anzeigen von Daten aus einer Datenbank in einem Formular

87236
CAD

Ich bin ein Anfänger von .NET und könnte mich in die richtige Richtung lenken. Mein Problem basiert auf dem folgenden Code. Hier habe ich 4 Variationen derselben Methode und alle 4 Variationen funktionieren gut.

  1. Was ist der empfohlene oder übliche Weg, dies zu tun?

  2. Wenn keine dieser Methoden in Ordnung ist, schlagen Sie bitte eine mit Code vor?

Code-Erklärung:

Von einem Windows-Formular rufe ich eine viewAccount()Methode auf, die in der bankAccountKlasse ist. Der Zweck besteht darin, die relevanten Bankkontodaten eines Mitarbeiters aus der Datenbank abzurufen. Diese Angaben sollten dann in den Textfeldern des aufrufenden Formulars angezeigt werden.

Bitte beachten Sie auch, dass ich keine Zeilen reduziert habe, um sie lesbarer zu machen.

Beispiel 01 - Die Methode gibt ein bankAccountObjekt mit Feldern aus, die mit Daten aus der Datenbank gefüllt werden

class bankAccount
{
    //Member fields...
    string acNo;
    string acName;
    string bank;
    string acType;
    frmShowAccount form=new frmShowAccount();

    public bankAccount viewAccount( string acNo )
    {
        this.acNo = acNo;

        using (SqlConnection newCon = new SqlConnection(db.GetConnectionString))
        {
            SqlCommand newCmd = new SqlCommand("SELECT Employee.Name, BankAccount.ac_name, BankAccount.bank_name, BankAccount.ac_type FROM BankAccount INNER JOIN Employee ON BankAccount.emp_id = Employee.Emp_ID WHERE (BankAccount.ac_no = @bankAccount)", newCon);

            newCmd.Parameters.Add("@bankAccount", SqlDbType.Char).Value = acNo;
            newCon.Open();
            SqlDataReader rdr = newCmd.ExecuteReader();
            rdr.Read();

            form.txtEmpName.text = rdr.GetString(0); //EmpName is not a member of bankAccount class
            this.acName = rdr.GetString(1);
            this.bank = rdr.GetString(2);
            this.acType = rdr.GetString(3);

            return this;
        }
    }
}

// CALLING THE ABOVE METHOD...

bankAccount newBA = new bankAccount();
newBA = newBA.viewAccount(txtACNo.text);  // A reference is set to the instance returned
txtACName.text = newBA.acName;  // Get the value of instance field to text box

Beispiel 02 - Die Methode gibt einen Datenleser zurück und wird vom Formular zum Abrufen von Daten verwendet

    class bankAccount
    {
        string acNo;
        string acName;
        string bank;
        string acType;

        public SqlDataReader viewAccount( string acNo )
        {
            this.acNo = acNo;

            using (SqlConnection newCon = new SqlConnection(db.GetConnectionString))
            {
                SqlCommand newCmd = new SqlCommand("Same SELECT …”,newCon);

                newCmd.Parameters.Add()…
                newCon.Open();
                SqlDataReader rdr = newCmd.ExecuteReader();
                rdr.Read();

                return rdr;
            }
        }
    }

//CALLING THE ABOVE METHOD...
bankAccount newBA = new bankAccount();
SqlDataReader rdr = newBA.viewAccount(txtACNo.text) //A reference to hold the returning reader from the method call
txtACName.text = rdr.getString(1); //Get the value through the reader to text box

Beispiel 03 : Diese Methode möchte Rückgabewerte, weist den Textfeldern im Formular jedoch explizit Werte zu

  class bankAccount
  {
        string acNo;
        string acName;
        string bank;
        string acType;
        frmShowAccount form=new frmShowAccount();

        public void viewAccount( string acNo )
        {
            this.acNo = acNo;

            using (SqlConnection newCon = new SqlConnection(db.GetConnectionString))
            {
                SqlCommand newCmd = new SqlCommand("Same SELECT …", newCon);

                newCmd.Parameters.Add()…
                newCon.Open();
                SqlDataReader rdr = newCmd.ExecuteReader();
                rdr.Read();

                // Setting values to the text boxes in the current instance of form
                form.txtName.text=rdr[0];
                form.txtACName.text=rdr[1];
                form.txtBankName.text=rdr[2];
                form.txtACType.text=rdr[3];         
            }
        }
    }

//CALLING THE ABOVE METHOD
bankAccount newBA = new bankAccount();
newBA.form.this; // reference 'form' which is in the 'bankAccount' class is set to current instance of the form object.

Beispiel 04 : Diese Methode möchte einen beliebigen Wert zurückgeben. Es werden nur Instanzfelder der Klasse mit den Daten initialisiert

    class bankAccount
    {
        string acNo;
        string acName;
        string bank;
        string acType;
        frmShowAccount form=new frmShowAccount();

        public void viewAccount( string acNo )
        {
            this.acNo = acNo;

            using (SqlConnection newCon = new SqlConnection(db.GetConnectionString))
            {
                SqlCommand newCmd = new SqlCommand("Same SELECT …)", newCon);

                newCmd.Parameters.Add()…
                newCon.Open();
                SqlDataReader rdr = newCmd.ExecuteReader();
                rdr.Read();

                form.txtName.text=rdr[0];
                this.acName=rdr[1];
                this.bank=rdr[2];
                this.acType=rdr[3];
        }
    }


// CALLING THE ABOVE METHOD
bankAccount newBA = new bankAccount();
txtACName.text = newBA.acName; // Text boxes get the data from account object's instance fields (probably through a get property)
Antworten
18
Ich denke, Sie sollten diese in 4 getrennte Fragen aufteilen. und dann sehen Sie, welche Antworten Sie von jedem von ihnen erhalten, dann könnten Sie besser entscheiden, welche Ihnen am besten gefällt. Malachi vor 6 Jahren 0
@Malachi war auch mein erster / ursprünglicher Gedanke, aber es kann als Ganzes überprüft werden. Mathieu Guindon vor 6 Jahren 1
@ Mat'sMug, ich habe ein paar gelesen und beschlossen zu antworten ... lol Malachi vor 6 Jahren 0
Ich mag es, dass Sie parametrisierte Abfragen verwenden. Mathieu Guindon vor 6 Jahren 0
Erwägen Sie die Verwendung eines ORM wie Entity Framework. svick vor 6 Jahren 1

2 Antworten auf die Frage

20
Mathieu Guindon

None of the Above.

You've shown 4 different ways of mixing up presentation and data concerns; the 4 approaches only differ in minute implementation details and all suffer the same flaws - what you have here is a God class that knows everything about everything there is to know.

Let's think differently.


This answer uses the names I would have used instead of yours.


We have at least 3 distinct, separate things:

  • We have a Form that knows about its textboxes.

  • We have a BankAccountInfo that holds an AccountNumber, an AccountName, an AccountType the BankName and an EmployeeName.

  • We have a way to fetch data from the database, here ADO.NET.

Each of these 3 concepts is its own class - you have the Form and the BankAccount, but you've turned the bank account into some "coordinator" that knows about the Form and deals with fetching the data from the database.

Leaving the form aside, BankAccountInfo should look like this:

public class BankAccountInfo
{
    public string AccountNumber { get; set; }
    public string AccountName { get; set; }
    public string AccountType { get; set; }
    public string BankName { get; set; }
    public string EmployeeName { get; set; }
}

Where's the code? Elsewhere. It's not the role of this object to know about textboxes, nor about some SQL backend. It has only one, simple goal: expose data. Not fetch data - that's the role of a service class:

public class BankAccountDataService
{
    private readonly string _connectionString;
    public BankAccountDataService(string connectionString)
    {
        _connectionString = connectionString;
    }

    public BankAccountInfo GetByAccountNumber(string accountNumber)
    {
        using (var connection = new SqlConnection(_connectionString))
        {
            var sql = "SELECT ...";
            connection.Open();
            using (var command = new SqlCommand(sql, connection))
            {
                command.Parameters.AddWithValue(...);
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        return new BankAccountInfo
                                   {
                                       EmployeeName = reader[0].ToString(),
                                       AccountNumber = reader[1].ToString(),
                                       AccountName = reader[2].ToString(),
                                       BankName = reader[3].ToString(),
                                       AccountType = reader[4].ToString()
                                   }; 
                    }
                }
            }
        }

        return null;
    }
}

Now you have a class whose role is specifically to interact with the database; its interface gives you a GetByAccountNumber method that lets you fetch everything there is to know about a BankAccountInfo by passing in an account number. It knows nothing of a form, and it doesn't care how the returned object is being used, it's none of its concern.

This is where you have a design decision to make: you need to somehow "connect" these pieces together. Let's see:

  • the form really only needs to know about BankAccountInfo.
  • the BankAccountInfo doesn't need to know about BankAccountDataService.
  • the BankAccountDataService needs to know about BankAccountInfo (and any other type it might return).

We're missing something else.

public class BankAccountPresenter
{
    private BankAccountView _form;
    private BankAccountDataService _service;

    public BankAccountPresenter(BankAccountView form, BankAccountDataService service)
    {
        _form = form;
        _form.OnShowAccountInfo += View_OnShowAccountInfo;

        _service = service;
    }

    public void Show()
    {
        _form.ShowDialog();
    }

    private View_OnShowAccountInfo(object sender, EventArgs e)
    {
        var info = _service.GetByAccountNumber(_form.AccountNumber);
        if (info == null)
        {
            MessageBox.Show("Could not find an account for specified number.");
            return;
        }

        _form.AccountName = info.AccountName;
        _form.AccountType = info.AccountType;
        _form.BankName = info.BankName;
    }
}

The above assumes that there's another class that's calling the presenter's Show method (that class will own the form and the service instances), and that the BankAccountView class looks something like this:

public partial class BankAccountView : Form
{
    public event EventHandler OnShowAccountInfo;

    public string AccountNumber
    {
        get { return AccountNumberTextBox.Text; }
        set { AccountNumberTextBox.Text = value; }
    }

    public string AccountName
    {
        get { return AccountNameTextBox.Text; }
        set { AccountNameTextBox.Text = value; }
    }

    public string AccountType
    {
        get { return AccountTypeTextBox.Text; }
        set { AccountTypeTextBox.Text = value; }
    }

    public string BankName
    { 
        get { return BankNameTextBox.Text; } 
        set { BankNameTextBox.Text = value; }
    }

    public BankAccountView()
    {
        InitializeComponents();
    }

    private void GetAccountInfoButton_Click(object sender, EventArgs e)
    {
        if (OnShowAccountInfo != null)
        {
            OnShowAccountInfo(this, EventArgs.Empty);
        }
    }
}

What's going on here? The view does nothing other than displaying the data and notifying the presenter whenever it needs something to happen - here, the user clicked a button: the presenter does all the work, fetching the required model from the database using an instance of a service class. Everyone plays its own little part, and everything is properly encapsulated - you don't expose the form's TextBox members, rather, you expose a string that represents that textbox' value.


Your Code

I already commented on that, but I'll reiterate it: I like that you're using parameters. Lots of beginners will just concatenate them into the command string, and that tends to make my eyes bleed that's rather ugly.

Here's a brain-dump of what jumped at me while I was reviewing your code:

  • Java-style naming. camelCase in C# is for locals (parameters, etc.) - class, method and property names use PascalCase.
  • Hungarian Notation and Disemvoweling. Don't chop off vowels in your identifiers. Use names that you can pronounce. Since this is WinForms there's some kind of a historical consensus about the accepted semi-Hungarian naming style for controls though.
  • Undisposed disposables. You're using using blocks and that's great, however you're not consistently disposing all objects that implement IDisposable, that's less great.
  • Misleading names. The ViewAccount(string) method doesn't actually display anything, it fetches information. Do what you say, say what you do.
Ihre Antwort ist sehr hilfreich. Bevor ich es akzeptiere, muss ich eines wissen. In meinem System habe ich mehrere Entitätsklassen wie Mitarbeiter, Kunden usw., die mit db interagieren müssen. Also sollte ich für jede eine eigene DataService- und Presenter-Klasse schreiben? CAD vor 6 Jahren 0
Ich würde wahrscheinlich einen Präsentator pro Formular erstellen und die Serviceklasse alles anzeigen lassen, was ein Formular verwenden muss. Wenn sich die Dinge verwickeln, würde ich die Klassen nach Bedarf umgestalten / extrahieren, um die Verantwortlichkeiten klar zu halten. Beachten Sie, dass es hier keine Employee-Klasse gibt. `BankAccountInfo` ist nicht an eine einzige Datentabelle gebunden. Es handelt sich hierbei um ein 'Modell', das die Ansicht verwendet. Die Serviceklasse kann erkennen, welche Informationen aus diesem "Modell" in welche Datenbank-Tabelle gehören. Schauen Sie sich das 'Model-View-Presenter'-Muster an (sorry, kein Link, Telefonpost!);) Mathieu Guindon vor 6 Jahren 1
Sehr gute Antwort, das Einzige, was ich hinzufügen würde, ist, dass das System nach und nach mehr von diesen Services (oder Repositorys, wie sie allgemein genannt werden) benötigt, und Sie andere Services dazwischen haben, wenn Sie Komplexe abstrahlen müssen Kombinationen von Repositories. Mitchell Lee vor 6 Jahren 2
Das Präsentieren eines Modells von der Datenbank zur Benutzeroberfläche ist jetzt klar. Aber wenn wir etwas mit bankAccountInfo zu tun haben, bevor Sie es der UI präsentieren (eine Geschäftslogik), wo soll der Code abgelegt werden? In der bankAccountInfo-Klasse? CAD vor 6 Jahren 0
Könnten Sie bitte klarstellen, wann der Konstruktor der BankAccountPresenter-Klasse im obigen Beispiel aufgerufen wird? (öffentlicher BankAccountPresenter (BankAccountView-Formular, BankAccountDataService-Dienst)) CAD vor 6 Jahren 0
@Chathur, das kann von vielen Dingen abhängen, aber Sie möchten wahrscheinlich `Program.cs` betrachten. Beachten Sie, wie ich keine neuen Dinge aufstelle, sondern sie vom Konstruktor erhalte. Dies wird als * Konstruktorinjektion * bezeichnet. Suchen Sie nach "Abhängigkeitsinjektion", wenn Sie diesen Ansatz mögen. Grundsätzlich werden Sie alle diese Objekte in der Nähe des Einstiegspunkts der Anwendung neu erstellen. Was die Geschäftslogik angeht, kann sie in die Presenter-Klasse gehen und bei Bedarf in ihre eigene Klasse umgestaltet werden. Ich hoffe es hilft! Mathieu Guindon vor 6 Jahren 1
@ Mat's Mug, ich habe den Punkt über DI verstanden. Aber noch nicht klar: "Im Grunde werden Sie alle diese Objekte in der Nähe des Einstiegspunkts der Anwendung neu erstellen." Sagen wir für Ex. Ich habe ein MDI-Formular und es gibt 10 Formulare unter dem MDI. Jedes Formular hat einen Moderator. Wenn der Benutzer das nächste Formular anzeigen möchte, klickt er einfach auf eine bestimmte Schaltfläche / ein Menü im MDI. Wie heißt in diesem Fall der Konstruktor des entsprechenden Formulardarstellers? (wie das nächste Formular von den Präsentatoren angezeigt wird) .ShowDialog ()) CAD vor 6 Jahren 0
6
Malachi

with all of the methods you should add another using statement for your Readers as well.

right now you aren't closing your reader, I am not sure that the reader is closed inside the using block of the connection, but I would imagine it should be, logically you can't have the reader after the connection is closed so I would think it would close the reader automatically. The only thing with that is you don't see it in the code so you can't be sure that is what it is doing.

If I were you I would just wrap it in a using statement.


you should also use a using statement with

SqlCommand newCmd = new SqlCommand("Same SELECT …)", newCon);

like this

using (SqlConnection newCon = new SqlConnection(db.GetConnectionString)) {
    using (SqlCommand newCmd = new SqlCommand("Same SELECT …)", newCon) {
        newCmd.Parameters.Add()…
        newCon.Open();
        Using (SqlDataReader rdr = newCmd.ExecuteReader()) {
            rdr.Read();

            form.txtName.text=rdr[0];
            this.acName=rdr[1];
            this.bank=rdr[2];
            this.acType=rdr[3];
        }
    }
}