我正在阅读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服务中复制逻辑。
我的设计模式首先是不是很糟糕,还是我可以以某种方式使这项工作干净利落?
非常感谢任何见解。
答案 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实体类应该具有的许多部分方法和钩子,这样您就可以检测到您不想要的字段的更改。
祝你好运!<小时/> 的更新强>
对于踢,这是我如何反序列化一个实体(正如我在评论中提到的),在历史的某个稍后点查看它的状态:(在我从日志条目的包装器中提取它之后)
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。