使用LINQ2SQL限制对特定字段的更改时的最佳实践

时间:2009-09-01 23:52:50

标签: asp.net-mvc linq-to-sql design-patterns repository

我正在阅读Steven Sanderson的书Pro ASP.NET MVC Framework,他建议使用存储库模式:

public interface IProductsRepository
{
    IQueryable<Product> Products { get; }
    void SaveProduct(Product product);
}

他直接从他的控制器访问产品存储库,但由于我将同时拥有一个网页和Web服务,我想添加一个“服务层”,由控制器和Web服务调用:

public class ProductService
{
    private IProductsRepository productsRepsitory;

    public ProductService(IProductsRepository productsRepository)
    {
        this.productsRepsitory = productsRepository;
    }

    public Product GetProductById(int id)
    {
        return (from p in productsRepsitory.Products
                where p.ProductID == id
                select p).First();
    }

    // more methods
}

这似乎很好,但我的问题是我不能使用他的SaveProduct(产品产品),因为:

1)我想只允许在Product表中更改某些字段

2)我想保留对Product表的每个字段所做的每个更改的审核日志,因此我必须为每个允许更新的字段提供方法。

我最初的计划是在ProductService中使用这样的方法:

public void ChangeProductName(Product product, string newProductName);

然后调用IProductsRepository.SaveProduct(Product)

但是我看到了一些问题:

1)传递这样的Product对象不是非常“OO”吗?但是,我无法看到这个代码如何进入Product类,因为它应该只是一个愚蠢的数据对象。我可以看到为部分类添加验证,但不是这个。

2)在保留更改之前,如何确保没有人更改除Product之外的任何其他字段?

我基本上都被撕裂了,因为我无法将审计/更新代码放在Product中,而ProductService类的更新方法看似不自然(但是,GetProductById对我来说似乎很自然)。

我认为即使我没有审核要求,我仍然会遇到这些问题。无论哪种方式,我想限制在一个类中可以更改哪些字段,而不是在网站和Web服务中复制逻辑。

我的设计模式首先是不是很糟糕,还是我可以以某种方式使这项工作干净利落?

非常感谢任何见解。

3 个答案:

答案 0 :(得分:1)

我将存储库拆分为两个接口,一个用于读取,另一个用于写入。

该读取实现了IDisposeable,并在其生命周期内重用相同的数据上下文。它将linq生成的实体对象返回给SQL。例如,它可能看起来像:

interface Reader : IDisposeable
{
    IQueryable<Product> Products;
    IQueryable<Order> Orders;
    IQueryable<Customer> Customers;
}

iQueryable非常重要,因此我得到了linq2sql的延迟评估优势。这很容易使用DataContext实现,并且很容易伪造。请注意,当我使用此接口时,我从不将相关行的自动生成字段使用(即,使用order.Aroducts不直接,调用必须连接到相应的ID列)。这是一个限制,我不介意考虑更容易使伪装读取存储库进行单元测试。

写入操作每次写入操作使用单独的datacontext,因此它不实现IDisposeable。它不接受实体对象作为输入,也不取出每个写操作所需的特定字段。

当我编写测试代码时,我可以将可读接口替换为使用我手动填充的一堆List&lt;&gt;的虚假实现。我使用mocks作为写接口。到目前为止,这就像一个魅力。

不要养成传递实体对象的习惯,它们必然会受到datacontext的生命周期的影响,并导致存储库与其客户端之间的不幸耦合。

答案 1 :(得分:0)

为了满足您对变更审核/记录的需求,我今天就我建议您考虑的系统进行了最后的修改。我们的想法是序列化(如果您正在使用LTS实体对象并通过DataContractSerializer的魔力)在对象的“之前”和“之后”状态下轻松完成,然后将这些保存到日志记录表中。

我的日志记录表包含日期,用户名,受影响实体的外键以及操作的标题/快速摘要列,例如“产品已更新”。还有一个用于存储更改本身的列,这是用于存储“之前和之后”状态的迷你XML表示的通用字段。例如,这是我正在记录的内容:

<ProductUpdated>
    <Deleted><Product ... /></Deleted>
    <Inserted><Product ... /></Inserted>
</ProductUpdated>

这是我使用的通用“序列化器”:

public string SerializeObject(object obj)
{
    // See http://msdn.microsoft.com/en-us/library/bb546184.aspx :
    Type t = obj.GetType();
    DataContractSerializer dcs = new DataContractSerializer(t);
    StringBuilder sb = new StringBuilder();
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.OmitXmlDeclaration = true;
    XmlWriter writer = XmlWriter.Create(sb, settings);
    dcs.WriteObject(writer, obj);
    writer.Close();
    string xml = sb.ToString();
    return xml;
}

然后,在更新时(也可以用于记录插入/删除),在进行模型绑定之前获取状态,然后再次获取。推入XML包装器并记录下来! (或者我想您可以在日志记录表中使用两列来实现这些,尽管我的XML方法允许我附加任何其他可能有帮助的信息。)

此外,如果您只想更新某些字段,您可以使用控制器操作方法中的“白名单/黑名单”来执行此操作,或者您可以创建一个“ViewModel”来交付对你的控制器,你可能有你想要的限制。您还可以查看LTS实体类应该具有的许多部分方法和钩子,这样您就可以检测到您不想要的字段的更改。

祝你好运!
-Mike

<小时/> 的更新

对于踢,这是我如何反序列化一个实体(正如我在评论中提到的),在历史的某个稍后点查看它的状态:(在我从日志条目的包装器中提取它之后)

public Account DeserializeAccount(string xmlString)
{
    MemoryStream s = new MemoryStream(Encoding.Unicode.GetBytes(xmlString));
    DataContractSerializer dcs = new DataContractSerializer(typeof(Product));
    Product product = (Product)dcs.ReadObject(s);
    return product;
}

答案 2 :(得分:0)

我还建议阅读“LINQ in Action”一书中的第13章“每层中的LINQ”。它几乎完全解决了我一直在努力解决的问题 - 如何将LINQ用于3层设计。在阅读完本章之后,我倾向于完全没有使用LINQ。