пятница, 27 августа 2010 г.

Реализация программы 'Журнал успеваемости'

Техническое задание

Это аналог обычного классного журнала, слева фамилии студентов, сверху предметы на пересечении оценки - например:

clip_image002

только данные хранятся в базе данных :)

Варианты использования

1. Преподаватель авторизовывается, выставлеят студенту оценку по предмету.

2. Преподаватель авторизовывается, добавляет новый предмет в список предметов.

3. Преподаватель авторизовывается, добавляет нового студента в список студентов.

Функциональность

1. Проверка прав доступа: к системе имеют доступ только преподаватели. У каждого преподавателя есть свой логин и пароль.

2. Заведение студентов - каждый преподаватель может завести студета/ов.

3. Заведение предметов - каждый преподаватель может завести предмет/ы.

4. Выставление оценок - каждый преподаватель может выставить оценку по существующему предмету или изменить уже выставленную.

5. Исправление (переименование) предметов / студентов.

Доп функциональность

1. Удаление предметов / студентов / оценок

Датагрид использовать желательно, но необязательно. Можно ListView ну итд. На отображении особо не заморачивайтесь, но должно быть 'красиво' !

И самое главное:

Использовать датасет. Обеспечить корректность одновременных изменний данных.

Также должна быть обеспечена корректная работа в следующей ситуации

u1 взял данные на редактирование ( Fill )

u2 взял данные на редактирование ( Fill )

u2 сохранил данные (Update)

u1 успешно сохранил данные, имея возможность выбрать между данными u2 и cвоими, указав, какие из них попадут в базу.

Немного теории

Всегда может быть такая ситуация, когда в одном соединении мы прочитали какие-то записи, а затем попытались их обновить. Но за момент, пока мы их редактировали, а затем попытались сохранить, в другом соединении эти же записи уже были обновлены. Иначе говоря, первый процесс читает данные, после чего те же данные читает второй процесс, и второй процесс обновляет эти же данные до того, как это сможет сделать первый процесс, то возникнет конфликт, когда первый процесс попытается обновить эти данные.

Если к базе данных обращаться из нескольких соединений и проводить изменения, то возникновение конфликтов — это лишь вопрос времени и везения.

Приложение само должно решать, какие действия ему необходимо сделать, чтобы решить этот конфликт. Например, ситуация может быть такая: администратор сайта зашел на страницу, отображающую данные обычного пользователя (администратор имеет возможность обновлять эти данные).Если после того, как страница администратора прочитает пользовательские данные из базы, и обычный пользователь обратиться к странице, отображающую его пользовательские данных, и внесет измения, то возникнет конфликт, когда администратор сохранит свои изменения. Если же конфликт не возникнет — то изменения обычного пользователя будут перекрыты и потеряны. Может быть и иначе — изменения администратора потеряны. Какое поведение должно быть верным в каждом конкретном случае — это и есть сложная проблема. Первый шаг — обнаружить её. Второй — разрешить.

Реализация

В описанном ниже классе используется метод EndEdit (). Во время вызова метода EndEdit к базовому источнику данных применяются все незавершенные изменения.

Этот метод действует только в том случае, если объекты, содержащиеся в источнике данных, реализуют интерфейс IEditableObject.Если объекты не реализуют интерфейс IEditableObject, изменения в данных копируются в базовый источник данных немедленно после их внесения.Этот метод вызывает событие ListChanged.

Особенности реализации, я не буду описывать подробно, так как написала в коде комментарии.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

namespace Journal

{

/// <summary>

/// Основной класс, содержащий журнал успеваемости учеников

/// </summary>

public partial class Journal : Form

{

/// <summary>

/// Конструктор

/// </summary>

public Journal()

{

InitializeComponent();

}

/// <summary>

/// Сохранение изменений

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void studentBindingNavigatorSaveItem_Click(object sender, EventArgs e)

{

this.Validate();

this.studentBindingSource.EndEdit();

this.studentTableAdapter.Update(this.journalDataSet.Student);

}

/// <summary>

/// Загрузка данных в таблице

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void Journal_Load(object sender, EventArgs e)

{

// TODO: This line of code loads data into the 'journalDataSet.ConnectedTable' table. You can move, or remove it, as needed.

this.connectedTableTableAdapter.Fill(this.journalDataSet.ConnectedTable);

// TODO: This line of code loads data into the 'journalDataSet.Subject' table. You can move, or remove it, as needed.

this.subjectTableAdapter.Fill(this.journalDataSet.Subject);

// TODO: This line of code loads data into the 'journalDataSet.ConnectedTable' table. You can move, or remove it, as needed.

this.connectedTableTableAdapter.Fill(this.journalDataSet.ConnectedTable);

// TODO: This line of code loads data into the 'journalDataSet.Student' table. You can move, or remove it, as needed.

this.studentTableAdapter.Fill(this.journalDataSet.Student);

JournalDataSetTableAdapters.SubjectTableAdapter subjectTableAdapter = new global::Journal.JournalDataSetTableAdapters.SubjectTableAdapter();

subjectTableAdapter.Fill(this.journalDataSet.Subject);

for (int j = 0; j < connectedTableDataGridView.Rows.Count - 1; j++)

{

int idSubject = (int)connectedTableDataGridView.Rows[j].Cells[0].Value;

for (int i = 0; i < journalDataSet.Subject.Rows.Count; i++)

{

if ((int)journalDataSet.Subject.Rows[i]["id"] == idSubject)

{

connectedTableDataGridView.Rows[j].Cells["subject"].Value = journalDataSet.Subject.Rows[i]["subject"];

}

}

}

foreach (JournalDataSet.StudentRow row in journalDataSet.Student.Rows)

{

cb_Name.Items.Add(row["Name"].ToString());

}

foreach (JournalDataSet.SubjectRow row in journalDataSet.Subject.Rows)

{

cb_Subject.Items.Add(row["subject"].ToString());

}

}

/// <summary>

/// Заполнение connectedTableDataGridView при перерисовке

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void connectedTableDataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)

{

JournalDataSetTableAdapters.SubjectTableAdapter subjectTableAdapter = new global::Journal.JournalDataSetTableAdapters.SubjectTableAdapter();

subjectTableAdapter.Fill(this.journalDataSet.Subject);

for (int j = 0; j < connectedTableDataGridView.Rows.Count - 1; j++)

{

int idSubject = (int)connectedTableDataGridView.Rows[j].Cells[0].Value;

for (int i = 0; i < journalDataSet.Subject.Rows.Count; i++)

{

if ((int)journalDataSet.Subject.Rows[i]["id"] == idSubject)

{

connectedTableDataGridView.Rows[j].Cells["subject"].Value = journalDataSet.Subject.Rows[i]["subject"];

}

}

}

}

/// <summary>

/// Добавление оценки студенту по предмету, если уже есть, то замена

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void bt_AddMark_Click(object sender, EventArgs e)

{

int idStudent = 0;

int idSubject = 0; ;

int mark = 0;

try

{

for (int j = 0; j < journalDataSet.Student.Rows.Count - 1; j++)

{

if (journalDataSet.Student.Rows[j]["Name"].ToString() == cb_Name.SelectedItem.ToString())

{

idStudent = (int)journalDataSet.Student.Rows[j]["id"];

break;

}

}

for (int j = 0; j < journalDataSet.Subject.Rows.Count - 1; j++)

{

if (journalDataSet.Subject.Rows[j]["subject"].ToString() == cb_Subject.SelectedItem.ToString())

{

idSubject = (int)journalDataSet.Subject.Rows[j]["id"];

break;

}

}

mark = Int32.Parse(label2.Text);

for (int j = 0; j < journalDataSet.ConnectedTable.Rows.Count - 1; j++)

{

if ((int)journalDataSet.ConnectedTable.Rows[j]["id_subject"] == idSubject)

{

if ((int)journalDataSet.ConnectedTable.Rows[j]["id_student"] == idStudent)

{

label1.Text = cb_Name.SelectedItem.ToString() + " исправил оценку " + journalDataSet.ConnectedTable.Rows[j]["mark"].ToString() + " по " + cb_Subject.SelectedItem.ToString() + " на " + mark.ToString();

journalDataSet.ConnectedTable.Rows[j]["mark"] = mark;

label1.Refresh();

return;

}

}

}

JournalDataSet.ConnectedTableRow newRow;

newRow = journalDataSet.ConnectedTable.NewConnectedTableRow();

newRow.id_student = idStudent;

newRow.id_subject = idSubject;

newRow.mark = mark;

journalDataSet.ConnectedTable.AddConnectedTableRow(newRow);

label1.Text = cb_Name.SelectedItem.ToString() + " получил " + mark.ToString() + " по " + cb_Subject.SelectedItem.ToString();

//journalDataSet.ConnectedTable.AcceptChanges();

}

finally

{

this.connectedTableTableAdapter.Update(this.journalDataSet.ConnectedTable);

}

}

/// <summary>

/// Обработка события смены значений radiobutton

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void radioButton3_CheckedChanged(object sender, EventArgs e)

{

RadioButton rb = (RadioButton)sender;

label2.Text = rb.Text;

}

/// <summary>

/// Кнопка выполняет Refresh

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void toolStripButton1_Click(object sender, EventArgs e)

{

Journal_Load(this, new EventArgs());

}

private void toolStripButton16_Click(object sender, EventArgs e)

{

this.Validate();

this.studentBindingSource.EndEdit();

this.studentTableAdapter.Update(this.journalDataSet.Student);

}

private void toolStripButton24_Click(object sender, EventArgs e)

{

this.Validate();

this.subjectBindingSource.EndEdit();

this.subjectTableAdapter.Update(this.journalDataSet.Subject);

}

}

}

Опишем форму ввода логина и пароля.

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Data.SqlClient;

using System.Data.OleDb;

using System.Configuration;

namespace Journal

{

public partial class Connection : Form

{

/// <summary>

/// Класс проверки пользователя и пороля и подключения к базе

/// </summary>

public Connection()

{

InitializeComponent();

}

/// <summary>

/// Пробуем подключиться к SQLServer

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void bt_Connection_Click(object sender, EventArgs e)

{

// Create a SQL Connection and DataAdapter

SqlConnection conn = new SqlConnection(ConfigurationSettings.AppSettings["Sql_ConnectString"]);

SqlCommand cm = new SqlCommand();

// call provider independent function to retrieve data

GetData(conn, cm);

}

/// <summary>

/// Пробуем подключиться к БД

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void GetData(IDbConnection conn, IDbCommand cm)

{

try

{

conn.Open();

// create the command and assign it to the IDbDataAdapter interface

IDbCommand cmd = conn.CreateCommand();

int count = 0;

cmd.CommandText = "SELECT Count(*) FROM ConnectLogin WHERE Login = '" + tb_Login.Text + "' AND Password = '" + tb_Password.Text + "'";

count = (int)cmd.ExecuteScalar();

if (count > 0)

{

this.Close();

Program.isConnected = true;

}

else

MessageBox.Show("Не удалось подключиться к базе данных, не верная пара логин-пороль");

}

catch

{

MessageBox.Show("Не верно указано имя сервера");

}

finally

{

conn.Close();

}

}

}

}

Также приведу схему базы данных, используемую в приложении.

clip_image004

Рис 1. Схема БД приложения 'Журнал успеваемости'

1 комментарий: