C# 和 Visual Studio 2005 中的程序集之间的循环引用

发布于 2024-07-22 14:10:44 字数 7993 浏览 5 评论 0原文

我正在努力标准化所有应用程序的分层/多层设计的单一方式。

我正在尝试将我的所有应用程序设为 5 层。

代码:


| 用户界面|

|

| 业务对象|

|

| 或映射器 |

|

| 数据访问|

|

| 关系型数据库管理系统

假设我正在开发一个具有用户登录/注销功能的应用程序。 我正在 VS2005 解决方案下创建 4 个项目。 每个项目都是针对上面 4 层之一。 我正在设计我的业务对象类,如下所示: -

public class User
{
    private string _username;
    public string Username
    {
        get { return _username; }
        set { _username = value; }
    }

    private string _password;
    public string Password
    {
        get { return _password; }
        set { _password = value; }
    }

    public User()
    {
    }

    public bool LogIn(String username, String password)
    {
        bool success = false;

        if (UserMapper.UsernameExists(username))
        {
            success = UserMapper.UsernamePasswordExists(username, password);
        }
        else
        {
            //do nothing
        }

        return success;
    }

    public bool LogOut()
    {
           bool success;
        //----some logic
           return success;
    }

    public static User GetUserByUsername(string username)
    {
        return UserMapper.GetUserByUsername(username);
    }

    public static UserCollection GetByUserTypeCode(string code)
    {
        return UserMapper.GetByUserTypeCode(code);
    }
}

这就是我为我的对象提供一些与现实世界场景相匹配的功能的方式。 这里 GetByUsername() 和 GetByUserTypeCode() 是 getter 函数。 这些函数与现实世界的逻辑不匹配。 因为在现实世界中,用户永远不会“通过用户名获取”或“通过用户类型代码获取”。 所以这些函数保持静态。

我的 OR Mapper 层的类如下:-

public static class UserMapper
{
    public static bool UsernameExists(String username)
    {
        bool exists = false;

        if (UserDA.CountUsername(username) == 1)
        {
            exists = true;
        }

        return exists;
    }

    public static bool UsernamePasswordExists(String username, String password)
    {
        bool exists = false;

        if (UserDA.CountUsernameAndPassword(username, password) == 1)
        {
            exists = true;
        }

        return exists;
    }
}

最后,DA 类如下:-

public static class UserDA
{
    public static int CountUsername(string username)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }  

    public static int CountUsernameAndPassword(string username, string password)
    {
        int count = 0;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name AND Pass_word = @Pass_word";
                command.Parameters.AddWithValue("@User_name", username);
                command.Parameters.AddWithValue("@Pass_word", password);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = 0;
            }
        }

        return count;
    }

    public static int InsertUser(params object[] objects)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"INSERT INTO User(ID, User_name, Pass_word, RegDate, UserTypeCode, ActualCodeOrRoll) 
                                                            VALUES(@ID, @User_name, @Pass_word, @RegDate, @UserTypeCode, @ActualCodeOrRoll)";
                command.Parameters.AddWithValue("@ID", objects[0]);
                command.Parameters.AddWithValue("@User_name", objects[1]);
                command.Parameters.AddWithValue("@Pass_word", objects[2]);
                command.Parameters.AddWithValue("@RegDate", objects[3]);
                command.Parameters.AddWithValue("@UserTypeCode", objects[4]);
                command.Parameters.AddWithValue("@ActualCodeOrRoll", objects[5]);

                command.Connection.Open();
                count = command.ExecuteNonQuery();
                command.Connection.Close();
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }

    public static SqlDataReader GetUserByUsername(string username)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }

    public static SqlDataReader GetUserByUserTypeCode(string userTypeCode)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE UserTypeCode = @UserTypeCode";
                command.Parameters.AddWithValue("@UserTypeCode", userTypeCode);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }
}

如果有人仔细检查这些类,他就会明白,OR Mapper 层需要 BusinessObject 层的引用。 BusinessObject层还需要OR Mapper层的引用。

这应该会创建一个循环依赖。

我怎样才能避免这个问题?

有人建议使用普通数据传输对象(DTO)。 但是,据我所知,根据 OOP,现实世界对象的属性和功能应该组合在一起作为一个类。 如果我使用 DTO 那么如何将功能封装到类中? 此外,我正在创建另一个没有任何属性(BO)的类。 对我来说,这在两个方面都违反了面向对象编程。 如果我这样做了,那么 OOP 在这个世界上还有什么用呢? 相同的答案可以应用于“UserManager”类。

我找到了一个博客

它讨论了接口的实现。 定义一个单独的接口,在 BusinessObject 的数据类中实现它,并根据 BusinessObject 和 OR-Mapper 层中的接口进行编程。

但我做不到。

谁能用一个实际的例子告诉我这一点?

I am working hard to standardize one single way of Layered/n-Tiered design of my all applications.

I am trying to make all my applications 5 tiered.

Code:


| UI |

|

| Business Object |

|

| OR-Mapper |

|

| Data Access |

|

| RDBMS |

Suppose I am developing an application with a log-in/log-out capability for users. I am creating 4 projects under a VS2005 solution. Each project is for one of the upper 4 layers.
I am designing my Business Object class as follows:-

public class User
{
    private string _username;
    public string Username
    {
        get { return _username; }
        set { _username = value; }
    }

    private string _password;
    public string Password
    {
        get { return _password; }
        set { _password = value; }
    }

    public User()
    {
    }

    public bool LogIn(String username, String password)
    {
        bool success = false;

        if (UserMapper.UsernameExists(username))
        {
            success = UserMapper.UsernamePasswordExists(username, password);
        }
        else
        {
            //do nothing
        }

        return success;
    }

    public bool LogOut()
    {
           bool success;
        //----some logic
           return success;
    }

    public static User GetUserByUsername(string username)
    {
        return UserMapper.GetUserByUsername(username);
    }

    public static UserCollection GetByUserTypeCode(string code)
    {
        return UserMapper.GetByUserTypeCode(code);
    }
}

This is how I am giving my objects some functionality that matches the real-world scenario. Here GetByUsername() and GetByUserTypeCode() are getter functions. These functions does't match a real-world logic. Coz, in real-world, a User never "Gets by Username" or "Gets by UserTypeCode". So these functions are kept static.

My class for O-R Mapper layer is as follows:-

public static class UserMapper
{
    public static bool UsernameExists(String username)
    {
        bool exists = false;

        if (UserDA.CountUsername(username) == 1)
        {
            exists = true;
        }

        return exists;
    }

    public static bool UsernamePasswordExists(String username, String password)
    {
        bool exists = false;

        if (UserDA.CountUsernameAndPassword(username, password) == 1)
        {
            exists = true;
        }

        return exists;
    }
}

And finally, the DA class is as follows:-

public static class UserDA
{
    public static int CountUsername(string username)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }  

    public static int CountUsernameAndPassword(string username, string password)
    {
        int count = 0;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name AND Pass_word = @Pass_word";
                command.Parameters.AddWithValue("@User_name", username);
                command.Parameters.AddWithValue("@Pass_word", password);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = 0;
            }
        }

        return count;
    }

    public static int InsertUser(params object[] objects)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"INSERT INTO User(ID, User_name, Pass_word, RegDate, UserTypeCode, ActualCodeOrRoll) 
                                                            VALUES(@ID, @User_name, @Pass_word, @RegDate, @UserTypeCode, @ActualCodeOrRoll)";
                command.Parameters.AddWithValue("@ID", objects[0]);
                command.Parameters.AddWithValue("@User_name", objects[1]);
                command.Parameters.AddWithValue("@Pass_word", objects[2]);
                command.Parameters.AddWithValue("@RegDate", objects[3]);
                command.Parameters.AddWithValue("@UserTypeCode", objects[4]);
                command.Parameters.AddWithValue("@ActualCodeOrRoll", objects[5]);

                command.Connection.Open();
                count = command.ExecuteNonQuery();
                command.Connection.Close();
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }

    public static SqlDataReader GetUserByUsername(string username)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }

    public static SqlDataReader GetUserByUserTypeCode(string userTypeCode)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE UserTypeCode = @UserTypeCode";
                command.Parameters.AddWithValue("@UserTypeCode", userTypeCode);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }
}

If anyone closely examine the these classes he can understand that, O-R Mapper layer needs the reference of BusinessObject-layer. BusinessObject- layer also needs a reference of O-R Mapper-layer.

This should create a circular dependency.

How can I avoid this problem?

Someone suggested using plain Data Transfer Objects (DTO). But, as far as I know, according to OOP, attributes and functionality of a real-world object should be grouped together as a class. If I use DTO then how can I encapsulate functionality into a class? Moreover I am creating another class without any attribute (BO). To me that is breach of OOP in both ways. If I do so, then what is OOP for in this world? The same answer can be applied for "UserManager" classes.

I found a blog.

It discusses about implementing interfaces. Define a separate interface, implement it in your data class in the BusinessObject and program against your interface in the BusinessObject and in the OR-Mapper layer.

But I could not do this.

Can anyone show me that with a practical example?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

一场春暖 2024-07-29 14:10:45

我认为您可以做一些事情,这些事情一起可以帮助您的设计。 我还认为您可能想阅读依赖注入,因为也许提供了更好的设计模式为了你想做的事。

假设您只想使您所拥有的功能正常工作:

  • 首先,从您的 User 类中删除 static 方法,因为它们“创建”用户,因此最好留在 UserMapper 上。

  • 之后,仍然有许多方法可能使用 User 类中的 UserMapper 功能。 创建一个支持 UserNameExistsUserNamePasswordExists 方法的接口 IUserLookup (或其他东西); 将此接口与 User 类放在同一个项目中。

  • UserMapper 类上实现 IUserLookup,然后将其“注入”到它使用静态方法创建的 User 类实例中一个构造函数,所以基本上,当 UserMapper 创建 User 对象时,它会为它们提供对其自身实现的 IUserLookup 接口的引用。

这样,User仅使用IUserLookup上的方法,该方法位于同一解决方案中,因此不需要引用。 而UserMapper引用了这个解决方案,因此它可以创建User对象并实现IUserLookup接口。

I think there are a few things you can do that together could help with your design. I also think that you might want to read up on Dependency Injection as perhaps providing a better design pattern for what you want to do.

Assuming you just want to make what you have work though:

  • First, remove the static methods from your User class, since they 'create' users, and therefore are best just left on the UserMapper.

  • After that, there will still be a number of methods potentially that use UserMapper functionality from the User class. Create an interface IUserLookup (or something) that supports the UserNameExists and UserNamePasswordExists methods; put this interface in the same project as the User class.

  • Implement the IUserLookup on the UserMapper class, and then 'inject' it into the User class instances it creates with the static methods through a constructor, so basically, as the UserMapper creates User objects, it gives them a reference to the IUserLookup interface that it implements itself.

In this way, User only uses methods on IUserLookup, which is in the same solution, so no reference needed. And UserMapper references this solution, so it can create User objects and implement the IUserLookup interface.

猫烠⑼条掵仅有一顆心 2024-07-29 14:10:45

如果 OR 映射器实际上在执行 OR,那么它可能不需要对 BL 的引用 - 它只需要知道类型(是) ) 涉及。 但这是一个附带问题...

此类问题的主要答案是“控制反转”/“依赖注入”,大概会剪切 BL 下的所有内容 - 因此 BL 只依赖于一个接口(在基础程序集中定义) ),但不知道具体的 OR/DA/RDBMS(它们由 IoC/DI 提供)。

这是一个很大的话题,所以我故意含糊其辞。 我个人喜欢 StructureMap,但是有很多 IoC/DI 工具可用。

请注意,从技术上讲,创建循环装配引用是可能的; 不过,这是一个非常坏主意 - 而且这些工具会(故意)在每一步与您作斗争。

If the OR Mapper is actually doing OR, then it probably doesn't need a reference to the BL - it just needs to know the Type(s) that is (are) involved. But that is a side issue...

The main answer to this type of issue is "Inversion of Control" / "Dependency Injection", presumably snipping everything under the BL - so the BL depends only on an interface (defined in a base assembly), but doesn't know about the concrete OR/DA/RDBMS (they are supplied by the IoC/DI).

This is a big topic, so I'm being intentionally vague. Personally I like StructureMap, but there are lots of IoC/DI tools available.

Note that technically it is possible to create circular assembly references; it is a really bad idea, though - and the tools will (intentionally) fight you at every step.

治碍 2024-07-29 14:10:45

在您上面提交的代码中,没有循环依赖的证据。

当您的调用从上层传输到底层时...您的对象转换为适合每一层的专业化(但是在您的情况下,您正在处理每一层上的基元...至少在提交的代码中)...并且当您的调用返回时,它应该从专业化转向泛化......

这可能反之亦然,并且如果以这种方式观察到单个路径,则不存在循环依赖问题。 然而,如果在任何分层中,您尝试为两侧信息传输路径实现规范化场景,那么我们就会遇到问题,因为每个层都将依赖并需要其封闭层的引用。

但在您的代码中没有这种循环依赖的证据。 但是,如果存在这种情况,可以通过实现接口层或适配器模式(接口层是适配器模式)来避免这种情况。

例如,我们有一个 Layer InformationTravel (IT)...(好吧,我明白这听起来不太好)

| 用户界面|
|
| 资讯旅游 | **
|
| 业务对象|
|
| 或映射器 |
|
| 数据访问|
|
| 关系型数据库管理系统

您所做的就是为您的用户业务对象声明一个接口 IUser 并在用户业务对象中实现它......

然后 BO 就有了 IT 的引用。 对象创建应该只在 BO 层实现 IT 接口。这是完全可以的。 当您需要将此对象传递给 ORM 时,您可以通过将其切片到 IT 中实现的接口来传递它,当您收到它时,您在进行必要的修改后再次返回相同的对象。

但这再次暗示您无法在多个层创建 BO,因为只有 BO 具有接口的实现。 但是,如果您确实无法避免这种情况,那么您必须在多个位置提供实现或使用适配器模式(将您的对象封装到客户端期望的另一个对象中)。

In the code you have submitted above, there's no evidence of circular dependency.

When your call travels from upper layers to bottom layers... your object transformed into specialization which is appropriate to each layer (However in your case you are dealing with primitives on each layer... atleast in the submitted code)... And when your call returns it should be from specialization towards generalization....

This could be vice versa and there is no circular dependency issues if a single path is observed in such a way. However if with in any layered you try to implement Specilization scenario for both side information traveling path then we've a problem since each layer will depend and required the reference of its enclosing layer.

But in your code there's no such evidence of circular dependency. However if there's such a case this can be avoided by implementing interface layer or Adapter Pattern (interface layer is an adapter pattern).

For instance we have a Layer InformationTravel (IT)...(OK I understand this not sounds too good)

| UI |
|
| InformationTravel | **
|
| Business Object |
|
| OR-Mapper |
|
| Data Access |
|
| RDBMS |

What you do is for your User Business Object, declare an interface IUser and implement it in User business object....

Then BO have a reference of IT. Object creation should only be in BO layer which implements an interface from IT.. This is perfectly OK. When you need to pass this object to your ORM, you pass it by slicing it to an interface implemented in IT and when you receive it back you again return the same object after doing necessary modification.

But this again impose an implication that you can't create your BO at multiple layers since only BO have the implementation of the interface. However if you really can't avoid this then you have to provide the implementation at multiple location or use Adapter pattern (Encapsulate your object into another object which is expected by the client).

三月梨花 2024-07-29 14:10:45

为了防止程序集之间的循环引用,您应该使用接口。例如,如果您的 OR-Mapper 需要调用 BL 的某些成员,您应该识别这些成员并将它们放入一个或多个接口中,然后将它们放入程序集中(例如接口)或者在您的 OR-Mapper 程序集中,让您的 BL 对象实现它们,然后就不需要在您的 OR-Mapper 程序集中引用您的 BL。

To prevent circular reference between assemblies you should use interfaces .For example if your OR-Mapper needs to invoke some members of BL you should recognize these members and put them in one or several interfaces and put them either in an assembly (Interfaces for example) or in your OR-Mapper assembly and let your BL objects implement them then there will be no need to reference your BL in your OR-Mapper assembly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文