【问题标题】:Overly accessible and incredibly resource hungry relationships between business objects. How can I fix this?业务对象之间过度可访问且资源匮乏的关系。我怎样才能解决这个问题?
【发布时间】:2011-02-03 14:52:00
【问题描述】:

首先,这似乎是一个很长的问题。我不认为它是......代码只是我目前正在做的事情的概述。感觉不对,所以我正在寻找建设性的批评和警告,以了解我可以做什么的陷阱和建议。

我有一个包含业务对象的数据库。
我需要访问父对象的属性。
我需要通过业务对象维护某种状态。

如果您查看类,我认为访问修饰符不正确。我不认为它的结构很好。大多数关系都是用公共属性建模的。 SubAccount.Account.User.ID

有没有比这更好的方法来模拟类之间的关系,所以它不是那么“公开”?

这个问题的另一部分是关于资源的:

如果我要创建一个返回 List 的 User.GetUserList() 函数,并且我有 9000 个用户,那么当我调用 GetUsers 方法时,它将创建 9000 个 User 对象,其中将创建 9000 个新的 AccountCollection 对象。我该怎么做才能使这个项目不那么耗费资源?

请找到下面的代码并将其撕成碎片。

public class User {

   public string ID {get;set;}
   public string FirstName {get; set;}
   public string LastName {get; set;}
   public string PhoneNo {get; set;}

  public AccountCollection accounts {get; set;}

  public User {
     accounts = new AccountCollection(this);
  }

  public static List<Users> GetUsers() {
     return Data.GetUsers();
  }

}

public AccountCollection : IEnumerable<Account> {
  private User user;

  public AccountCollection(User user) {
     this.user = user;
  }

  public IEnumerable<Account> GetEnumerator() {
     return Data.GetAccounts(user);
  }
}


public class Account {

   public User User {get; set;}  //This is public so that the subaccount can access its Account's User's ID
   public int ID;
   public string Name;

   public Account(User user) {
      this.user = user;
   }

}

public SubAccountCollection : IEnumerable<SubAccount> {
  public Account account {get; set;}

  public SubAccountCollection(Account account) {
     this.account = account;
  }

  public IEnumerable<SubAccount> GetEnumerator() {
     return Data.GetSubAccounts(account);
  }
}


public class SubAccount {
   public Account account {get; set;}    //this is public so that my Data class can access the account, to get the account's user's ID.

   public SubAccount(Account account) {
      this.account = account;  
   }

   public Report GenerateReport() {
       Data.GetReport(this);
   }

}


public static class Data {

  public static List<Account> GetSubAccounts(Account account) {

      using (var dc = new databaseDataContext()) {
          List<SubAccount> query = (from a in dc.Accounts
                                where a.UserID == account.User.ID  //this is getting the account's user's ID
                                select new SubAccount(account) {
                                    ID = a.ID,
                                    Name = a.Name,
                                }).ToList();
      }

  }

  public static List<Account> GetAccounts(User user) {

     using (var dc = new databaseDataContext()) {
         List<Account> query = (from a in dc.Accounts
                               where a.UserID == User.ID  //this is getting the user's ID
                               select new Account(user) {
                                   ID = a.ID,
                                   Name = a.Name,
                               }).ToList();
     }
  }

  public static Report GetReport(SubAccount subAccount) {

     Report report = new Report();
     //database access code here
     //need to get the user id of the subaccount's account for data querying.
     //i've got the subaccount, but how should i get the user id.
     //i would imagine something like this:
     int accountID = subAccount.Account.User.ID;
     //but this would require the subaccount's Account property to be public.
     //i do not want this to be accessible from my other project (UI).
     //reading up on internal seems to do the trick, but within my code it still feels
     //public. I could restrict the property to read, and only private set.

     return report;
  }

  public static List<User> GetUsers() {

     using (var dc = new databaseDataContext()) {
         var query = (from u in dc.Users
                     select new User {
                       ID = u.ID,
                       FirstName = u.FirstName,
                       LastName = u.LastName,
                       PhoneNo = u.PhoneNo
                     }).ToList();

         return query;
     }
  }

}

【问题讨论】:

    标签: c# business-objects data-access


    【解决方案1】:

    延迟加载 - 不要在用户中创建 AccountCollection 对象,除非它被访问。或者,您可以让它与用户同时检索帐户集合,避免 1 + 9000 次数据库访问。

    有更多选择性的集合检索方法(即,您打算如何处理 9000 个用户?如果您需要他们的所有信息,那也不算浪费)。

    拥有较小的“摘要”对象,用于下拉列表和查找等长列表 - 这些对象是简单的只读业务对象 - 通常只包含少量信息,可用于检索完全展开的对象。

    如果进行分页,是用户列表加载一次并存储在网络服务器中,然后从缓存副本中分页,还是每次从数据库加载集合并由控件完成分页?

    【讨论】:

    • 我怎么能在这里使用延迟加载?你能举个例子吗?或将我链接到一个例子?我将只使用名字、姓氏和用户 ID 作为可以选择的用户列表。 (对于用户列表)我将为 ASP.NET 客户端应用程序上的列表使用一些分页。使用带有分页控件的 ListView 组件,我相信 ListView 需要将整个集合进行数据绑定,然后分页控件完成其余的工作。
    • 列表加载一次,然后从缓存的副本中进行分页。位缓存的副本长 9000 条记录。
    • @Mike - 我编辑了一些答案 - 您还想避免那些公共设置属性 - 这可以允许您的对象模型在其对象的控制之外进行更改。能够在对象图周围“点”是很好的,但您通常不希望设置东西(如果这样做,您通常会自己编写 set 方法)。
    • 所以你是说,当我创建用户时让 AccountCollection 为空,但是当我访问 Account 集合时,检查它是否为空,然后创建一个新的帐户集合,否则只需使用帐户集合?
    • 是的,这正是我所关心的。因为它们是公开的,所以可以在我想要的范围之外进行编辑。我不知道如何使它们更私密但对我的 Data 类可访问。
    【解决方案2】:

    这个答案最终包含了很多流行语标题。希望我能解释每一个以及为什么它适用于此。我认为我在下面介绍的每个概念都值得考虑 - 它们并不总是适用,但我发现它们都是我个人在考虑系统结构时认为有价值的东西。

    单一职责

    首先考虑每个对象的责任 - 它的工作是什么?通常,一旦您为每个班级确定了一项工作,您就会找到更好的设计。目前你的很多类都做的太多了,持有应该作为服务真正存在的逻辑。

    上面的第一个例子是你的 User 类:

    public class User { 
    
       public string ID {get;set;} 
       public string FirstName {get; set;} 
       public string LastName {get; set;} 
       public string PhoneNo {get; set;} 
    
      public AccountCollection accounts {get; set;} 
    
      public User { 
         accounts = new AccountCollection(this); 
      } 
    
      public static List<Users> GetUsers() { 
         return Data.GetUsers(); 
      } 
    
    } 
    

    为什么这提供了一种从数据源中检索用户的方法?该功能应该移出到用户服务中。

    另一个关键示例是 SubAccount 上的 GenerateReport 方法 - 不要让您的报告生成逻辑与 SubAccount 对象紧密相关。将其拆分将为您提供更大的灵活性,并减少对您的子帐户的更改破坏报告逻辑。

    延迟加载

    再次查看您的 User 类 - 为什么它会在实例化时加载所有用户帐户?每次与用户一起工作时,是否总是会使用这些对象?

    引入延迟加载通常会更好 - 仅在需要时检索帐户。当然,有时您需要预先加载(如果您知道很快就会想要该对象,因此不想减少数据库访问),但您应该能够针对这些异常进行设计。

    依赖注入

    这种类型从延迟加载点和单一责任点开始。你有很多硬编码的引用,比如你的 Data 类。这使您的设计更加僵化 - 重构您的数据访问以引入延迟加载,或者由于许多类都直接访问数据访问逻辑,因此更改检索用户记录的方式要困难得多。

    摘要对象

    给 Cade Roux 的提示 - 我从未听说过 Digest 对象这个术语,通常称它们为轻量级 DTO。

    正如 Cade 所说,如果您所做的只是显示绑定到唯一 ID 的用户名组合框,则没有理由检索包含功能齐全的用户对象的丰富列表。

    引入一个只存储非常基本的用户信息的轻量级用户对象。

    这又是引入服务/存储库抽象和某种依赖注入的另一个原因。如果您已将数据检索与实际对象封装在一起,并且没有与数据访问实现紧密绑定,那么更改从数据存储中检索的对象类型会变得容易得多。

    得墨忒耳法则

    您的对象对彼此的内部结构了解太多。允许从用户向下钻取到帐户然后到子帐户会混淆每个对象的责任。您可以从用户对象设置子帐户信息,而您可能不应该这样做。

    我一直在纠结这个原则,因为钻取层次结构似乎很方便。问题是它会阻止你对每个对象的封装和角色进行深入思考。

    也许不要向用户公开您的 Account 对象 - 而是尝试引入公开 Account 对象相关成员的属性和方法。查看 GetAccount 方法而不是 Account 属性,这样您就可以强迫自己使用帐户对象,而不是将其视为 User 的属性。

    【讨论】:

    • 我在上面的问题中提出的示例是一个更大项目的更小版本。我想我只是想在它失控之前修复它。使用子帐户对象上的 GetReport - 此报告专门仅用于子帐户。除子账户外,不会生成其他报告。我认为它适合那里。是的,GetUsers 方法可能应该在 UsersService 中。您认为我应该使用方法而不是属性来获取关系吗?有趣的。你认为我还应该将这些关系存储在对象中吗?
    • 我仍然不太确定您的回复中的依赖注入部分是什么意思。依赖注入在这里如何工作?
    • 我的意思是,根据我对依赖注入的理解,它通过对象的构造函数用自身初始化一个对象。就像我现在正在做的那样。每个用户至少有一个帐户,因此 AccountsCollection 将独立于每个用户,但在需要访问之前可能不需要。
    【解决方案3】:

    好的,只需对您目前拥有的代码进行快速评论。

    public class SubAccount {
       public Account account {get; set;}    //this is public so that my Data class can access the account, to get the account's user's ID.
    
        public SubAccount(Account account) {
            this.account = account;  
        }
        [snip]
    }
    

    如果 Account 属性通过 ctor 传递然后分配给支持字段,则您甚至不需要设置器。

    通过在 ctor 上传递属性来设置属性可能是一个非常好的做法,但是如果您的数据对象要被序列化,即如果它被检索或发送到 Web 服务,它就会失败 - 在这个如果您需要在 Account 属性上至少有一个 internal 设置器。

    编辑:您仍然可以在构造函数上使用这种 DI 类型行为,但问题是当对象从序列化形式重新水化时(即当它已经传递到/从 Web 服务)。因此,如果您要使用 Web 服务,您还需要有一个无参数构造函数,以及 Account 属性上的公共或内部设置器(如果您要使用内部设置器,那么您还需要指定 InternalsVisibleToAttribute在您的 AssemblyInfo.cs 文件中。

    【讨论】:

    • 但这不就是依赖注入的工作原理吗?我将来可能需要使用网络服务。
    猜你喜欢
    • 2013-10-24
    • 1970-01-01
    • 2019-04-10
    • 1970-01-01
    • 2012-03-16
    • 2013-05-19
    • 2020-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多