【问题标题】:Circular reference between Assemblies in C# and Visual Studio 2005C# 和 Visual Studio 2005 中的程序集之间的循环引用
【发布时间】:2009-05-29 04:28:05
【问题描述】:

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

我正在尝试将我的所有应用程序设为 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 函数。这些功能与现实世界的逻辑不匹配。因为,在现实世界中,用户永远不会“通过用户名获取”或“通过 UserTypeCode 获取”。所以这些函数都是静态的。

我的 O-R 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;
    }
}

如果有人仔细研究这些类就可以理解,O-R Mapper 层需要BusinessObject-layer 的引用。 BusinessObject-layer 也需要 O-R Mapper-layer 的引用。

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

如何避免这个问题?

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

我找到了blog

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

但我做不到。

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

【问题讨论】:

    标签: c# visual-studio-2005 n-tier-architecture


    【解决方案1】:

    我认为您可以一起做一些事情来帮助您的设计。我还认为您可能想阅读Dependency Injection,因为它可能为您想要做的事情提供更好的设计模式。

    假设你只是想让你拥有的东西工作:

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

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

    • UserMapper 类上实现IUserLookup,然后将其“注入”到它通过构造函数使用静态方法创建的User 类实例中,所以基本上,正如UserMapper 创建的那样User 对象,它为它们提供了对它自己实现的IUserLookup 接口的引用。

    这样User只使用了IUserLookup上的方法,在同一个解决方案中,所以不需要引用。而UserMapper引用了这个方案,所以可以创建User对象,实现IUserLookup接口。

    【讨论】:

    • 在 c# 和 VS2005 中实现“接口-实现”的想法后,我发现实现接口解决了问题,但没有实现我的目标。我的目标是只使用来自 UI 层/程序集的 BO 层/程序集。这样我就可以保持干净的层到层参考。因为我不想在 UI 层/程序集中为 BO 层/程序集和 ORMapper-层/程序集添加引用。我只想在 UI 层/程序集中使用 BO 层/程序集。同时有人建议我,只能使用反射,而不是 DI。
    • 本质上,您正在使您的 ORM 层事实上成为 BO 层的一部分。 ORM/BO 的想法是让 BO 层负责业务对象体现的功能,但 ORM 层负责物化和存储对象。将 ORM 隐藏在 BO 后面将打破 ORM/BO 拆分应该用于的关注点分离。
    【解决方案2】:

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

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

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

    请注意,技术上可以创建循环程序集引用;不过,这真的是个坏主意 - 而且这些工具会(故意)在每一步都与您抗衡。

    【讨论】:

      【解决方案3】:

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

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

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

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

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

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

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

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

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

      【讨论】:

      • 实现接口解决了我的问题,但没有实现我的目标。我的目标是只使用来自 UI 层/程序集的 BO 层/程序集。这样我就可以保持干净的层到层参考。因为我不想在 UI 层/程序集中为 BO 层/程序集和 ORMapper-层/程序集添加引用。我只想在 UI 层/程序集中使用 BO 层/程序集。同时有人建议我,只能使用反射,而不是 DI。这是真的吗?
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-10
        • 2021-01-04
        • 2010-09-12
        • 1970-01-01
        相关资源
        最近更新 更多