我看不到如何在IQueryable
返回的实体中设置值。
所以以下面的代码为例:
public IQueryable<CustomerInfo> CustomerInfoAsQueryable()
{
IQueryable<CustomerInfo> customerInfo = _dbCustomerService.Info("CustomerID123");
// Note: customerInfo.Logbook has value "~/dir/logbook.txt"
// How to set customerInfo.LogbookContent to the contents of "~/dir/logbook.txt"
return customerInfoAsQueryable;
}
现在,我必须将CustomerInfoAsQueryable
传递给第三方控件,以便他们可以执行查询。我希望能够读取customerInfo.Logbook
并找到文件,然后读取文件的内容,最后将customerInfo.LogbookContent
设置为此内容。
我想这是一种延迟加载。
我该怎么做才能使第三方控制在向不同客户进行查询时获得customerInfo.LogbookContent
?
(注意:如何读取文件不是问题的主题)
答案 0 :(得分:0)
在回答您的问题之前,我会警告您有关从considered a bad practice存储库中返回IQueryable
的做法,因为它鼓励在应用程序层中分布的业务逻辑。
最好使用过滤器并返回只读的过滤结果。
然后关于CustomerInfo.LogbookContent
的事情使我感到困扰。类CustomerInfo
应该反映某些数据库中的表结构。因此,使用该字段会产生误导:人们认为该信息存储在数据库中,尽管绝对不是这种情况。另外,CustomerInfo.Logbook
有一些冗余,它指定了一条路径。如何知道信息在哪里?因此,我认为删除CustomerInfo.LogbookContent
会更好。
还有另一件事让我感到困扰:一些文件路径从持久性层(在CustomerInfo.Logbook
中泄漏出来了,我将尽一切可能避免这种情况。您可以查看this video来了解为什么尽可能多地限制API是个好主意(总之,这会带来更好的设计和安全性)。
基于这些评论,我得出的结论是,您需要基于CustomerInfo
的新的抽象级别,以便更好地为您的用户服务。
我会提出这样的设计:
public IReadOnlyList<Customer> GetCustomers(/*some filters (id = ..., name like ..., etc.)*/)
{
return _dbCustomerService.Info("CustomerID123")
.Where(/*some filters (id = ..., name like ..., etc.)*/)
.Select(ci => new Customer(ci.Name, GetLogbookContent(ci.Logbook))
.ToList();
}
private string GetLogbookContent(string localFilePath)
{
//Connect to AWS/Azure
//Return file's content
}
public sealed class Customer
{
public Customer(string name, string logbook)
{
Name = name;
Logbook = logbook;
}
public string Name { get; }
public string Logbook { get; }
}
有关此代码的一些注意事项:
Customer
加载。我认为这是一种正确的做法,因为如果您的用户想要这些客户,则应遵循最小惊讶原则并获取数据以避免以后再进行长途调用,因为这可能会引起问题,因为这是不可预期的(UI冻结等) )。Customer
类,该类仅包含与您的用户有关的信息,并完全抽象了以下事实:数据既来自数据库又来自存储在某个云存储中的文件。IReadOnlyList
来明确指定我们已经完成了所有持久性,并且结果现在已存储在内存中(否则可以在真正执行IQueryable
之前关闭持久性层=>你好噩梦调试)。现在,如果并且仅在调用GetCustomers
时由于文件加载而花费的时间确实太长,仍然可以进行以下重构以启用日志的延迟加载:
public IReadOnlyList<Customer> Get(/*some filters (id = ..., name like ..., etc.)*/)
{
return _dbCustomerService.Info("CustomerID123")
.Where(/*some filters (id = ..., name like ..., etc.)*/)
.Select(ci => new Customer(ci.Name, () => GetLogbookContent(ci.Logbook))
.ToList();
}
public sealed class Customer
{
private string _logbook;
private readonly Func<string> _loadLogbook;
public Customer(string name, Func<string> logbook)
{
Name = name;
_loadLogbook = logbook;
}
public string Name { get; }
public string Logbook
{
get
{
if (_logbook == null)
{
_logbook = _loadLogbook();
}
return _logbook;
}
}
}
注意:这仅允许一次(第一次)从云存储中加载日志。但是,如果此文件的内容不断变化,并且您需要知道(=在每次调用Customer.Logbook
时加载文件内容),则可以像这样重构Customer
:
public sealed class Customer
{
private readonly Func<string> _loadLogbook;
public Customer(string name, Func<string> logbook)
{
Name = name;
_loadLogbook = logbook;
}
public string Name { get; }
public string Logbook => _loadLogbook();
}
根据您的评论,您不能绕过我最初做出的两个假设:
IQueryable
CustomerInfo
以外的其他内容基于此,我将实现惰性文件加载的方式(我假设CustomerInfo
看起来像POCO类,对于某些ORM使用的类通常是这种情况):
public class CustomerInfo
{
private Func<string, string> _loadLogbook;
private string _logbookContent;
...
public void SetLoadLogbook(Func<string, string> loadLogbook) => _loadLogbook = loadLogbook;
...
public string Logbook { get; set; }
public string LogbookContent
{
get
{
if(_logbookContent == null)
{
_logbookContent = _loadLogbook?.Invoke(Logbook);
}
return _logbookContent;
}
set => _logbookContent = value;
}
...
}
然后CustomerInfoAsQueryable
变成:
public IQueryable<CustomerInfo> CustomerInfoAsQueryable()
{
return _dbCustomerService.Info("CustomerID123")
.Select(ci =>
{
ci.SetLoadLogbook(path => GetLogbookContent(path));
return ci;
});
}
注意:
_loadLogbook?
在未调用NullReferenceException
的情况下保护代码不受SetLoadLogbook
的侵害。您知道此解决方案不是最优的,但是(应该)可以在约束条件下工作。