实体框架实体-来自Web服务的一些数据-最佳体系结构?


10

当前,我们在多个Web应用程序中使用Entity Framework作为ORM,并且到目前为止,由于我们的所有数据都存储在单个数据库中,因此它非常适合我们。我们正在使用存储库模式,并具有使用它们的服务(域层),并将EF实体直接返回到ASP.NET MVC控制器。

但是,提出了使用第三方API(通过Web服务)的要求,这将为我们提供与数据库中与用户相关的额外信息。在我们的本地用户数据库中,我们将存储一个外部ID,我们可以将其提供给API以获取其他信息。有很多可用的信息,但是为了简单起见,其中之一与用户的公司有关(名称,经理,房间,职务,位置等)。此信息将在整个Web应用程序的各个位置使用-而不是在单个位置使用。

所以我的问题是,填充和访问此信息的最佳位置是哪里?由于它已在各个地方使用,因此无论我们在Web应用程序中使用的何处,都临时地获取它并不明智,因此从域层返回这些附加数据是有意义的。

我最初的想法只是创建一个包含EF实体(EFUser)的包装模型类,以及一个包含新信息的新'ApiUser'类-当我们获取用户时,我们获取EFUser,然后获取其他来自API的信息,并填充ApiUser对象。但是,虽然这对于获得单个用户来说是不错的选择,但在获得多个用户时却会失败。获取用户列表时,我们无法点击API。

我的第二个想法只是向EFUser实体添加一个单例方法,该实体返回ApiUser,并在需要时填充它。这解决了上述问题,因为我们仅在需要时才访问它。

或最终的想法是将数据的本地副本保留在我们的数据库中,并在用户登录时将其与API同步。这是最小的工作,因为这只是一个同步过程-而且我们没有点击的开销每当我们想要获取用户信息时,数据库和API。但是,这意味着将数据存储在两个位置,也意味着该数据对于已经一段时间没有登录的任何用户而言都是过时的。

是否有人对如何最好地处理这种情况有任何建议或建议?


it's not really sensible to fetch it on an ad-hoc basis-为什么呢?出于性能原因?
罗伯特·哈维

我的意思不是临时访问API-我只是意味着保持现有实体结构不变,然后在需要时在Web应用程序中临时调用API-我只是说这不是明智的做法是,它需要在很多地方进行。
stevehayter 2013年

Answers:


3

你的情况

就您而言,这三个选项都是可行的。我认为最好的选择可能是将您的数据源同步到某个asp.net应用程序甚至不知道的地方。也就是说,避免每次都在前台进行两次访存,将API与数据库静默同步)。因此,如果这对您而言是可行的选择-我会说。

像其他答案一样“一次”获取的解决方案似乎不太可行,因为它无法持久保存响应,而ASP.NET MVC只会不断地对每个请求进行获取。

我会避免单例,出于很多通常的原因,我认为这根本不是一个好主意。

如果第三个选项是不是可行的-一个选择是延迟加载它。也就是说,有一类扩展的实体,并将其打在API上的必要基础。但是,这是一个非常危险的抽象,因为它具有更神奇,更不明显的状态。

我想这真的可以归结为以下几个问题:

  • API调用数据多久更改一次?不经常?第三选择。经常?突然,第三种选择不太可行。我不确定我是否会像您一样反对临时电话。
  • API调用的价格是多少?您按通话付费吗?他们快吗?自由?如果速度较快,则每次通话都可以,如果速度较慢,则需要进行某种预测并进行通话。如果他们花钱,那将是缓存的巨大动力。
  • 响应时间必须有多快?显然,速度越快越好,但是在某些情况下,为简化而牺牲速度可能是值得的,尤其是当它不直接面向用户时。
  • API数据与您的数据有何不同?他们在概念上是两件事吗?如果是这样,最好将API公开到外部,而不是直接返回带有结果的API结果,并让另一方进行第二次调用并进行管理。

一两个关于关注点分离的词

让我反对鲍伯森在这里所说的关于分离关注点的说法。归根结底,将这样的逻辑放在这样的实体中会违反同样的关注点分离。

通过将表示为中心的逻辑放在业务逻辑层中,拥有这样一个存储库就违反了关注点分离。您的存储库现在突然意识到与演示文稿相关的事情,例如您如何在asp.net mvc控制器中显示用户。

这个相关问题中,我询问了有关直接从控制器访问实体的问题。请允许我引用那里的答案之一:

“欢迎来到定制披萨店BigPizza,我可以接受您的订单吗?” “好吧,我想要一个带橄榄的披萨,但顶部加蕃茄酱,底部加奶酪,然后在烤箱中烘烤90分钟,直到它变黑变硬,像一块花岗岩一样。” “好的,先生,定制披萨是我们的职业,我们会做到的。”

收银员去厨房。他告诉厨师说:“柜台上有一个心理医生,他想和一个披萨一起吃……这是一块花岗岩,还有……等一下……我们需要先取个名字”。

厨师喊道:“不!不再!!你知道我们已经尝试过了。” 他拿了一叠400页的纸,“这里有2005年的花岗岩岩石,但是...它没有橄榄,而是青椒...或者这里是顶级番茄...但是客户想要只烤了半分钟。” “也许我们应该将其命名为TopTomatoGraniteRockSpecial?” 收银员说:“但这并没有考虑到底部的奶酪。” 这位厨师回答说:“但是像比萨饼一样形成金字塔状的比萨岩石也是很特别的。” 这位绝望的收银员说:“嗯……这很困难……”。

“我的比萨饼已经在烤箱里了吗?”,突然间,它穿过厨房的门大喊。厨师决定:“让我们停止讨论,只是告诉我如何制作这种披萨,我们将不会再有这种披萨。” “好吧,这是一个有橄榄的比萨饼,但是上面是番茄酱,下面是奶酪,然后在烤箱中烤90分钟,直到它变黑变硬,就像一块花岗岩一样。”

(阅读其余答案,这真是个不错的imo)。

忽略存在数据库的事实是天真的事 —存在数据库,无论您要多么努力地抽象它,它都无处不在。您的应用程序知道数据源。您将无法对其进行“热插拔”。ORM很有用,但由于它们解决的问题有多复杂,并且出于大量的性能原因(例如,选择n + 1),它们会泄漏。


感谢您对@Benjamin的非常彻底的回复。我最初开始使用上述Bobson的解决方案创建原型(甚至在他发布答案之前),但是您提出了一些重要的观点。要回答您的问题,请:-API调用不是很昂贵(它们是免费的,而且速度很快)。-数据的某些部分会相当定期地更改(有些甚至每两个小时更改一次)。-速度非常重要,但是应用程序的受众并不要求减轻快速加载的速度。
stevehayter 2013年

@stevehayter在这种情况下,我很可能会从客户端执行对API的调用。它更便宜,更快,并且可以为您提供更精细的控制。
本杰明·格伦鲍姆

1
基于这些答案,我不太倾向于保留数据的本地副本。我实际上倾向于将API单独公开,并以这种方式处理其他数据。我认为这可能是@Bobson解决方案的简单性之间的一个很好的折衷,但同时也增加了一定程度的分离性,我对此感到更自在。我将在原型中研究此策略,并报告我的发现(并奖励赏金!)。
stevehayter 2013年

@BenjaminGruenbaum-我不确定我是否遵循您的论点。我的建议如何使存储库了解演示?当然,它知道已经访问了API支持的字段,但这与视图对该信息的处理没有任何关系。
Bobson

1
我选择将所有内容移至客户端-但作为EFUser的扩展方法(存在于表示层的一个单独的程序集中)。该方法仅从API返回数据,并设置一个单例,这样就不会重复命中它。对象的寿命是如此之短,以至于我在这里使用单例都没有问题。这种方式存在一定程度的分离,但是我仍然获得使用EFUser实体的便利。感谢所有受访者的帮助。绝对是一个有趣的讨论:)。
stevehayter 2013年

2

在适当分离关注点的情况下,实体框架/ API级别以上的内容都不应意识到数据来自何处。除非API调用昂贵(就时间或处理而言),否则访问使用它的数据应该与从数据库访问数据一样透明。

因此,实现此目标的最佳方法是向EFUser对象添加额外的属性,以便根据需要延迟加载API数据。像这样:

partial class EFUser
{
    private APIUser _apiUser;
    private APIUser ApiUser
    {
       get { 
          if (_apiUser == null) _apiUser = API.LoadUser(this.ExternalID);
          return _apiUser;
       }
    }
    public string CompanyName { get { return ApiUser.UserCompanyName; } }
    public string JobTitle{ get { return ApiUser.UserJobTitle; } }
}

在外部,第一次使用CompanyNameJobTitle都会进行单个API调用(因此延迟很小),但是在对象被销毁之前进行的所有后续调用都将与访问数据库一样便捷。


谢谢@Bobson ...这实际上是我开始尝试的最初路线(添加了一些扩展方法来批量加载用户列表的详细信息-例如,显示用户列表的公司名称)。到目前为止,它似乎很适合我的需求-但下面的本杰明提出了一些重要观点,因此我将在本周继续进行评估。
stevehayter 2013年

0

一种想法是将ApiUser修改为不总是具有额外信息。相反,您在ApiUser上放置了一个方法来获取它:

ApiUser apiUser = backend.load($id);
//Locally available data directly on the ApiUser like so:
String name = apiUser.getName();
//Expensive extra data available after extra call:
UserExtraData extraData = apiUser.fetchExtraData();
String managerName = extraData.getManagerName();

您也可以稍作修改,以使用延迟加载额外的数据,这样就不必从ApiUser对象提取UserExtraData:

//Extra data fetched on first get:
String managerName = apiUser.lazyGetExtraData().getManagerName();

这样,当您拥有列表时,默认情况下将不会提取多余的数据。但是您仍然可以在遍历列表时访问它!


不过,我不太确定您的意思是什么-在backend.load()中,我们已经在进行加载-可以确定会加载API数据吗?
stevehayter 2013年

我的意思是,您应该等待额外的加载,直到显式请求-延迟加载api数据。
亚历山大·托斯林
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.