Object oriented n-level design. Am I abstracting too much? Or not enough?

I am creating my first enterprise level solution (at least I'm trying to make it generally accepted). I'm trying to follow best practice design patterns, but I'm starting to worry that I might go too far with abstraction.

I am trying to create an asp.net webforms application (in C #) as an n-level application. I created a data access layer using a strongly typed XSD dataset that interacts with the SQL server backend. I access DAL through some business-level objects that I created on a 1: 1 basis for data in a data set (for example, for the UsersBLL class for data, data in a data set). I do checks inside the BLL to make sure that the data passed to the DAL complies with the business rules of the application. This is all good and good. Where I am stuck is the point where I connect the BLL to the presentation layer. For example, my UsersBLL class works mainly with integer data since it interacts with DAL. Should I now create a separate class "User" (Singular), which displays the properties of one user, and not multiple users? This way, I do not need to search through datatables in the view level, since I could use the properties created in the User class. Or would it be better to somehow try to handle this inside UserBLL?

Sorry if this sounds a little complicated ... Below is the code from UserBLL:

using System; using System.Data; using PedChallenge.DAL.PedDataSetTableAdapters; [System.ComponentModel.DataObject] public class UsersBLL { private UsersTableAdapter _UsersAdapter = null; protected UsersTableAdapter Adapter { get { if (_UsersAdapter == null) _UsersAdapter = new UsersTableAdapter(); return _UsersAdapter; } } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, true)] public PedChallenge.DAL.PedDataSet.UsersDataTable GetUsers() { return Adapter.GetUsers(); } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, false)] public PedChallenge.DAL.PedDataSet.UsersDataTable GetUserByUserID(int userID) { return Adapter.GetUserByUserID(userID); } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, false)] public PedChallenge.DAL.PedDataSet.UsersDataTable GetUsersByTeamID(int teamID) { return Adapter.GetUsersByTeamID(teamID); } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, false)] public PedChallenge.DAL.PedDataSet.UsersDataTable GetUsersByEmail(string Email) { return Adapter.GetUserByEmail(Email); } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Insert, true)] public bool AddUser(int? teamID, string FirstName, string LastName, string Email, string Role, int LocationID) { // Create a new UsersRow instance PedChallenge.DAL.PedDataSet.UsersDataTable Users = new PedChallenge.DAL.PedDataSet.UsersDataTable(); PedChallenge.DAL.PedDataSet.UsersRow user = Users.NewUsersRow(); if (UserExists(Users, Email) == true) return false; if (teamID == null) user.SetTeamIDNull(); else user.TeamID = teamID.Value; user.FirstName = FirstName; user.LastName = LastName; user.Email = Email; user.Role = Role; user.LocationID = LocationID; // Add the new user Users.AddUsersRow(user); int rowsAffected = Adapter.Update(Users); // Return true if precisely one row was inserted, // otherwise false return rowsAffected == 1; } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Update, true)] public bool UpdateUser(int userID, int? teamID, string FirstName, string LastName, string Email, string Role, int LocationID) { PedChallenge.DAL.PedDataSet.UsersDataTable Users = Adapter.GetUserByUserID(userID); if (Users.Count == 0) // no matching record found, return false return false; PedChallenge.DAL.PedDataSet.UsersRow user = Users[0]; if (teamID == null) user.SetTeamIDNull(); else user.TeamID = teamID.Value; user.FirstName = FirstName; user.LastName = LastName; user.Email = Email; user.Role = Role; user.LocationID = LocationID; // Update the product record int rowsAffected = Adapter.Update(user); // Return true if precisely one row was updated, // otherwise false return rowsAffected == 1; } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Delete, true)] public bool DeleteUser(int userID) { int rowsAffected = Adapter.Delete(userID); // Return true if precisely one row was deleted, // otherwise false return rowsAffected == 1; } private bool UserExists(PedChallenge.DAL.PedDataSet.UsersDataTable users, string email) { // Check if user email already exists foreach (PedChallenge.DAL.PedDataSet.UsersRow userRow in users) { if (userRow.Email == email) return true; } return false; } } 

It would be helpful to evaluate some recommendations in the right direction!

Thanks everyone!

Max

+7
c # oop class-design
source share
2 answers

The type of breakdown you are trying to use usually involves moving from the DataTable approach to what the instance uses for (approximately) each row in the database. In other words, DAL will return either a single user or a collection of users, depending on which static boot method you call. This means that all methods that take a bunch of parameters to represent the user instead accept a Custom DTO.

DAL for users will look something like this:

 public static class UserDal { public static User Load(int id) { } public static User Save(User user) } { } public static IEnumerable<User> LoadByDiv(int divId) { } } 
  • It is static because it has no state. (He may have a database as his state, but this is not a good idea in most cases, and the connection pool removes any benefits. Others may have a singleton pattern.)

  • It works at the DTO user level, not a DataTable or any other abstraction for a particular database. Perhaps the database is used in the implementation, perhaps it uses LINQ: the caller should not know in any case. Notice how it returns IEnumerable instead of a special kind of collection.

  • It concerns only access to data, not business rules. Therefore, it should only be called from within the business logical class that applies to users. such a class can decide what level of access the caller is allowed to have, if any.

  • DTO means a data transfer object, which usually makes up a class containing only public properties. It may have a dirty flag, which is automatically set when the properties are changed. There may be a way to explicitly set a dirty flag, but there is no public way to clean it. In addition, the identifier is usually read-only (therefore, that it can only be filled with deserialization).

  • DTO intentionally does not contain business logic that attempts to ensure correctness; instead, the appropriate business logic class is what contextually applies the rules. The business logic of the change is, therefore, if the DTO or DAL were burdened with this, a violation of the single responsibility principle will lead to disasters, such as the inability to deserialize the object, since its values ​​are no longer considered legal.

  • The presentation level can create an instance of the user object, fill it in and ask the level of business logic to call the Save method in DAL. If the BLL decides to do this, it will fill in the identifier and clear the dirty flag. Using this identifier, the BLL can then retrieve the stored instances by calling the DAL Load-by-ID Method.

  • DAL always has a Save method and a Load-by-ID method, but it can have query-based loading methods, such as the LoadByDiv example. He must offer any methods that BLL requires for effective surgery.

  • Implementing DAL is a secret until BLL and above. If support is a database, routines corresponding to various DAL methods will usually be stored, but this is an implementation detail. Similarly, the type of caching.

+4
source share

To simplify the design, you definitely do not want to distract entire data tables and look for them in the presentation level. The beauty of a database is that it is indexed to facilitate quick querying of row-level data (i.e. get a row with an indexed identifier).

Your DAL should set a method such as GetUserByUserID (int userID). Then you must expose this method through the BLL, applying any necessary business logic.

In addition, I would get rid of datatype sets and consider an ORM tool like Entity Framework.

+3
source share

All Articles