【问题标题】:How should I design my object model so that my DAL can populate read-only fields?我应该如何设计我的对象模型,以便我的 DAL 可以填充只读字段?
【发布时间】:2010-12-08 04:45:01
【问题描述】:

为了分离关注点,在我当前的项目中,我决定将我的 DAL 和 BLL/Business 对象完全分离到单独的程序集中。我想将我的业务对象保持为简单的结构,没有任何逻辑,以使事情变得非常简单。我希望我也可以将我的业务逻辑与我的 DAL 分开。所以我的应用程序会告诉我的 DAL 加载我的对象,我的 DAL 将运行到数据库并获取数据,用数据填充对象,然后将其传递回我的 BLL。

问题 - 我如何将我的 DAL 放在单独的程序集中并将数据推送到只读字段中?

  • 如果我将 getter 设置为受保护,那么继承的对象可以访问它,这不是我真正想要的,因为我将返回继承的对象类型,而不是原始对象类型。
  • 如果我将 getter 设置为内部,那么我的 DAL 必须与我不想要的 BLL 驻留在同一个程序集中。
  • 如果我将 getter 设置为 public,那么任何人都可以在它应该是只读的时候读/写它。

编辑:我注意到我可以有一个 ObjectBase 的返回类型,但实际上是从 ObjectBase 派生的一个对象或对象集合返回给外部世界(在我的 DAL 之外)属性将是只读的,但我的派生类型(只能在我的 DAL 中访问)属性实际上是读/写的。

【问题讨论】:

  • 有一个原因,如果你想让你的业务对象的属性只读,你不只是通过构造函数设置它们吗?
  • 如果您要从头开始构建 DLL,我建议您阅读 Dino Esposito 和 Andrea Salterello amazon.com/… 撰写的“Microsoft .NET:为企业构建应用程序”
  • @CSharpAtl 我觉得这不是正确的方法。
  • @BobTheBuilder 通过构造函数设置业务对象值是如何创建不可变对象的更多主题:weblogs.asp.net/bleroy/archive/2008/01/16/…
  • 在阅读了您的问题和 cmets 之后,我认为您还没有接受没有完美的解决方案。您的设计要求已成为一种愿望——您将不得不以一种或另一种方式接受权衡。

标签: c# .net asp.net


【解决方案1】:

您可以通过构造函数设置只读属性。

【讨论】:

  • 这是我一直在想的,但我不得不说我也不喜欢这个。它只是在解决问题,而不是解决根本问题。
  • 不可变对象在没有好处的情况下使用起来会很痛苦。即在网络应用程序中使用它们。
  • @Charles Conway - 但不要忘记,业务逻辑层可能位于一个单独的 dll 中,它可能不仅用于 webapp。就我个人而言,当我创建 BLL 时,我也会在其他派生工具中使用它。
  • 看到下面 Ben 的回答后,我发现在受控设置中使用构造函数实际上非常有用。如果你在继承的对象中使用它,你实际上可以将构造函数封装在 DAL 中,这样 BLL 就看不到它。所以这可能真的有效。
  • @BobTheBuilder - 你看到的根本问题到底是什么?
【解决方案2】:

这是没有灵丹妙药的情况;最简单的选项受到限制或无法满足您的要求,而彻底的解决方案要么开始有异味,要么开始偏离简单。

也许最简单的选项是我在这里没有提到的:保持字段/属性私有并将它们作为 out / ByRef 参数传递给 DAL。虽然它不适用于大量字段,但对于少量字段来说很简单。

(我没有测试过,但我认为值得探索)。

public class MyObject()
{
    private int _Id;
    public int Id { get { return _Id; } } // Read-only

    public string Name { get; set; }

    // This method is essentially a more descriptive constructor, using the repository pattern for seperation of Domain and Persistance
    public static MyObject GetObjectFromRepo(IRepository repo)
    {
        MyObject result = new MyObject();
        return repo.BuildObject(result, out _Id);            
    }
}

public class MyRepo : IRepository
{
    public MyObject BuildObject(MyObject objectShell, out int id)
    {
        string objectName;
        int objectId;

        // Retrieve the Name and Value properties
        objectName = "Name from Database";
        objectId = 42;
        //

        objectShell.Name = objectName;
        Console.WriteLine(objectShell.Id); // <-- 0, as it hasn't been set yet
        id = objectId; // Setting this out parameter indirectly updates the value in the resulting object
        Console.WriteLine(objectShell.Id); // <-- Should now be 42
    }
}

还值得注意的是,尝试将您的域/业务对象保持在最低限度可能比您想象的要多。如果您打算对它们进行数据绑定,那么您需要实现 IPropertyNotifyChanged,这会阻止您使用自动实现的属性。您应该能够保持它相当干净,但您必须为基本功能做出一些牺牲。

【讨论】:

  • 考虑到这一点,我不确定我是否喜欢在这种情况下传递 ref 属性,这感觉很混乱并且可能令人困惑——即使我理解你的代码的意思,它补充说对可能维护此代码的未来开发人员造成不必要的混淆。
  • 是的,ref 属性总是有一点味道,而且从不直观。然而,我一直在评估这个确切问题的解决方案,这是我发现明确阻止外部对象设置值并且不会留下任何对特定存储库的依赖的唯一方法。所有其他解决方案要么在编译时不强制执行(例如在对象上设置“IsLocked”属性并在锁定对象后尝试设置属性时抛出异常),或者他们只是放弃要求.
【解决方案3】:

这可以很好地保持您的 SoC 模型,不会增加太多复杂性,它会阻止写入只读字段,并且您可以使用非常相似的模型来解决序列化问题。您的 DAL 仍然可以写入您的只读字段,如果以类似方式使用您的序列化程序也可以写入 - 这意味着开发人员必须有意识地写入只读字段以防止无意误用。

模型项目

namespace Model
{
    public class DataObject
    {
        public int id { get; protected set; }
        public string name { get; set; }
    }   
}

数据项目

namespace Data
{
    class DALDataObject : DataObject
    {
        public DALDataObject(int id, string name)
        {
            this.id = id;
            this.name = name;
        }
    }
    public class Connector
    {
        public static DataObject LoadDataObject(int objectId)
        {
            return new DALDataObject(objectId, string.Format("Dummy object {0}", objectId));
        }
        public static IEnumerable<DataObject> LoadDataObjects(int startRange, int endRange)
        {
            var list = new List<DataObject>();
            for (var i = startRange; i < endRange; i++)
                list.Add(new DALDataObject(i, string.Format("Dummy object {0}", i)));

            return list;
        }
    }
}

【讨论】:

  • 嗯,我仍然不确定我是否喜欢您使用的构造函数,但是,它出于所有密集目的将构造函数从外部世界隐藏,所以我想我喜欢这种方法。我喜欢这个模型解决序列化并保留 SoC 的事实,我喜欢只读字段实际上是这样保留的。谢谢+1
  • @BobTheBuilder - 如果您可以覆盖基础对象中的受保护设置器,那就太好了,但不幸的是,这是不可能的。因此,我只知道设置属性的两种方法:通过构造函数或方法。最简洁的方式是构造函数。
  • @BobTheBuilder - 至少这种方法意味着您必须有意识地决定我知道这些属性受到保护并且我必须这样做才能写入它们。因此它可以防止意外错误。
【解决方案4】:

忍受它怎么样?

按照这些准则实施,但不要在模型中添加这样的硬约束。假设你这样做了,但随后又出现了另一个需要序列化它或做其他事情的请求,然后你就被它捆绑了。

正如你在其他评论中所说,你想要可互换的部分......所以,基本上你不想要与特定关系相关的东西。


更新 1: 或许“只是忍受它”过于简单,但我仍然必须强调,您不应该对这些事情过于深入。使用简单的指导方针,保持你的代码干净和可靠,这是你一开始可以做的最好的事情。它不会妨碍进度,而当一切都更稳定时重构并不难。

别搞错了,我根本不是一个不考虑就写代码的人。但是,我采用了这样的方法,并且只有在少数情况下它们会得到回报——没有任何迹象表明你不会通过简单和进化得到类似的结果。

恕我直言,这不适合需要在一开始就解决的重要架构问题。

先发制人的跟进:如果您不能信任您的团队遵循简单的指导方针,请当心。还要确保从一些结构开始,选择几个场景,用真实的东西设置结构,当有一些简单的东西时,团队会更好地了解他们的方式。

【讨论】:

  • 因为“只是忍受它”是错误的答案。您不应该能够根据数据库中的计算字段设置字段值。我确实了解序列化问题,但这些场景必须有模式。对不起,但我将不得不对“忍受它”投反对票,这不是我对编程的态度。我宁愿做正确的事,也不愿忍受它。如果无法做到这一点,也许我的 SoC 模型需要审查。
  • 我可以看到正反两面...有时“理想”设计过于极端,在许多情况下,一个良好沟通的约定就足够了。话虽如此——只要 DAL 遵循存储库模式——找到一种方法将属性的 set 限制为存储库似乎是完全合理的。
【解决方案5】:

在我看来,处理此问题的最佳方法是让业务对象和 DAL 在同一个程序集中,并由命名空间分隔。这在逻辑上分离了关注点,并允许您使用内部设置器。我想不出将它们分成自己的程序集有什么好处,因为一个没有另一个是无用的。

【讨论】:

  • 我想这是一种观点。但是,我认为逻辑层不应该需要数据层才能发挥作用。数据层不应该要求逻辑层有用;对象层不应该需要逻辑层或数据层,因为它应该只是数据;但是数据层和逻辑层都需要对象层,因为它们需要对象才能工作。因此对象应该在自己的程序集中,逻辑应该在自己的程序集中,数据层应该在自己的程序集中。
  • 将这些部分放在一起,您就构建了一个希望有用的应用程序。通过命名空间在逻辑上分离片段实现了关注点分离。按组装分离会大大增加构建应用程序的工作量,并且没有任何实际好处。
  • 在我看来,DAL 应该是一个完全独立的程序集,这样当您的数据存储需要被换出时,您只需要替换一个 DLL,您不需要重新编译任何东西否则。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多