首页 > 解决方案 > 从复杂的数据库功能构建复杂对象的良好设计模式是什么?

问题描述

我有一个复杂的结构,需要使用从数据库读取的数据来构建,我需要知道在哪里放置我的数据库访问逻辑。

我阅读了存储库设计模式,但我的数据库操作不是简单的 CRUD,它们不仅仅是返回简单的值。

我正在连接到 PostgreSQL 并调用经常将数据作为一组游标返回的函数。

这是我的代码的一个非常简化的部分......我省略了一些细节

class Topic
{
    public string TopicLabel { get; set; }

    public int TopicCode { get; set; }

    List<Topic> parentTopics;

    public Topic(int topicCode , string topicLabel)
    {
        ...
    }

}       


class InitialTopic
{
    Topic initialTopic;

    public int TopicCode { get { return initialTopic.TopicCode; } }            
    Dictionary<int, float> similarityValues;

    public InitialTopic( Topic topic)
    {
        initialTopic = topic;
        similarityValues = new Dictionary<int, float>();
    }

}



class TopicsDictionary
{
    Dictionary<int, Topic> topics;

    public TopicsDictionary()
    {
        topics = new Dictionary<int, Topic>();
    }     

    public Topic this[int topicCode]
    {
        get
        {
            Topic t = null;
            if (topics.ContainsKey(topicCode))
            {
                t = topics[topicCode];                    
            }
            else
            {
                t = new Topic(topicCode);
                topics.Add(topicCode, t);
            }
            return t;
        }
    }
}

     .
     .
     .  

public static void GetData(InitialTopic initialTopic)
{

     using (var conn = new NpgsqlConnection(connString))
    {
        conn.Open();
        NpgsqlTransaction tran = conn.BeginTransaction();

        NpgsqlCommand command = new NpgsqlCommand("public.\"GetStuff\"", conn);
        .
        .

        string cursor1, cursor2;
        using (var dr = command.ExecuteReader())
        {
            dr.Read();
            cursor1 = dr[0].ToString();
            dr.Read();
            cursor2 = dr[0].ToString();
        }                   

        using (var resultSet1 = new NpgsqlCommand())
        {
            resultSet1.CommandText = $@"FETCH ALL FROM ""{cursor1}""";
            resultSet1.Connection = conn;

            using (var reader = resultSet1.ExecuteReader())
            {
                // here read values, create Topic objects,
                // add them to TopicsDictionary and link them using parentTopics list 
                // to reflect parent-child relation
            }
        }           

        using (var resultSet2 = new NpgsqlCommand())
        {
            resultSet2.CommandText = $@"FETCH ALL FROM ""{cursor2}""";
            resultSet2.Connection = conn;

            using (var reader = resultSet2.ExecuteReader())
            {
                // here read values and fill similarityValues 
                // dictionary in InitialTopic object
            }
        }

        tran.Commit();
        conn.Close();

    }
}   

我应该将数据库操作与对象的实际构建(主题对象及其成员列表和字典)分开吗?我该怎么做?这种情况是否有合适的设计模式?

标签: design-patternsdata-access-layer

解决方案


我应该将数据库操作与对象的实际构建(主题对象及其成员列表和字典)分开吗?

肯定是的。你里面的两个代码块using (var reader =足够复杂,以至于(1)你的GetData方法看起来很丑,(2)你想对这些代码进行单元测试,(3)你想在切换到另一个数据库系统时重用这些代码,例如 MySQL .

我该怎么做?这种情况是否有合适的设计模式?

只需将方法中的两个代码块提取GetData到其他地方即可。因为你的InitialTopic课很干净,你可以搬到这里。但这取决于你。

现在唯一的挑战是InitialTopic类如何从两个读者那里接收数据。当然,我们会将读者传递给initialTopic对象。但是我们不应该让InitialTopic类依赖于数据库类(你的读者的类型)。

通常为了解决这个问题,我们应用依赖倒置原则。引入了一个新的接口来抽象读者的操作:

interface MyReader {
    // hasNext() and next() methods, right?
}

我们将让InitialTopic类依赖于MyReader接口。

现在我们编写一个适配器来适配 Npgsql 阅读器(中的阅读器using (var reader =):

class MyNpgsqlReader : MyReader {
    // use Dependency Injection to inject the Npgsql reader
}

最后,GetData方法中的两个代码块变为:

using (var reader = resultSet1.ExecuteReader()) {
    initialTopic.method1(new MyNpgsqlReader(reader));
}
...
using (var reader = resultSet2.ExecuteReader()) {
    initialTopic.method2(new MyNpgsqlReader(reader));
}

推荐阅读