MVC、ORM 和数据访问模式

发布于 2024-10-22 19:08:45 字数 917 浏览 2 评论 0原文

我想我已经达到了“分析瘫痪”的状态。 我有一个 MVC 应用程序,使用 EF 作为 ORM。 因此,我正在尝试决定最佳的数据访问模式,到目前为止,我认为将所有数据访问逻辑放入控制器中是可行的方法..但这听起来不太正确。 另一种选择是创建外部存储库,处理数据交互。 这是我的优点/缺点:

如果将数据访问嵌入到控制器中,我最终会得到这样的代码:

using (DbContext db = new DbContext())
{
    User user = db.Users.Where(x=>x.Name == "Bob").Single();
    user.Address.Street = "some st";
    db.SaveChanges();
}

所以有了这个,我可以获得延迟加载的全部好处,我在完成后立即关闭连接,我可以灵活地选择在哪里条款-所有的细节。 缺点 - 我在一个方法中混合了一堆东西 - 数据检查、数据访问、UI 交互。

通过存储库,我可以外部化数据访问,理论上,如果我决定使用 ado.net 或使用不同的数据库,就可以替换存储库。 但是,我没有看到一个好的干净方法来实现延迟加载,以及如何控制 DbContext/连接生命周期。 比如说,我有带有 CRUD 方法的 IRepository 接口,我将如何加载属于给定用户的地址列表?制作像 GetAddressListByUserId 这样的方法看起来很难看,是错误的, 并且会让我创建一堆同样丑陋的方法,并且在使用 ORM 时没有什么意义。

我确信这个问题已经被解决了一百万次,并希望在某个地方有一个解决方案......


还有一个关于存储库模式的问题 - 如何处理属于属性的对象?例如,用户有一个地址列表,您将如何检索该列表?为该地址创建存储库?使用 ORM,地址对象不必有对用户的引用,也不必有 Id 字段,而使用 repo - 它必须拥有所有这些。更多代码,更多暴露的属性..

I think I've hit that "paralysis by analysis" state.
I have an MVC app, using EF as an ORM.
So I'm trying to decide on the best data access pattern, and so far I'm thinking putting all data access logic into controllers is the way to go.. but it kinda doesn't sound right.
Another option is creating an external repository, handling data interactions.
Here's my pros/cons:

If embedding data access to controllers, I will end up with code like this:

using (DbContext db = new DbContext())
{
    User user = db.Users.Where(x=>x.Name == "Bob").Single();
    user.Address.Street = "some st";
    db.SaveChanges();
}

So with this, I get full benefits of lazy loading, I close connection right after I'm done, I'm flexible on where clause - all the niceties.
The con - I'm mixing a bunch of stuff in a single method - data checking, data access, UI interactions.

With Repository, I'm externalizing data access, and in theory can just replace repos if I decide to use ado.net or go with different database.
But, I don't see a good clean way to realize lazy loading, and how to control DbContext/connection life time.
Say, I have IRepository interface with CRUD methods, how would I load a List of addresses that belong to a given user ? Making methods like GetAddressListByUserId looks ugly, wrong,
and will make me to create a bunch of methods that are just as ugly, and make little sense when using ORM.

I'm sure this problem been solved like million times, and hope there's a solution somewhere..


And one more question on repository pattern - how do you deal with objects that are properties ? E.g. User has a list of addresses, how would you retrieve that list ? Create a repository for the address ? With ORM the address object doesn't have to have a reference back to user, nor Id field, with repo - it will have to have all that. More code, more exposed properties..

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

糖果控 2024-10-29 19:08:46

您选择的方法在很大程度上取决于您将要处理的项目类型。对于需要快速应用程序开发 (RAD) 方法的小型项目,直接在 MVC 项目中使用 EF 模型并在 MVC 项目中访问数据可能几乎没问题。控制器,但是项目增长得越多,它就会变得越混乱,你将开始遇到越来越多的问题。如果您想要良好的设计和可维护性,有几种不同的方法,但通常您可以遵循以下方法:

保持控制器和视图干净。控制器应该只控制应用程序流程,而不包含数据访问甚至业务逻辑。视图只能用于表示 - 给它一个 ViewModel,它会将其表示为 Html(没有业务逻辑或计算)。每个视图一个 ViewModel 是一种非常简洁的方法。

典型的控制器操作如下所示:

public ActionResult UpdateCompany(CompanyViewModel model)
{
    if (ModelState.IsValid)
    {
        Company company = SomeCompanyViewModelHelper.
                          MapCompanyViewModelToDomainObject(model);
        companyService.UpdateCompany(company);
        return RedirectToRoute(/* Wherever you go after company is updated */);
    }
    // Return the same view with highlighted errors
    return View(model);
}

由于上述原因,最好抽象您的数据访问(可测试性、切换数据提供者或 ORM 或其他内容的便捷性等)。 Repository 模式是一个不错的选择,但在这里您还可以获得一些实现选项。关于通用/非通用存储库、是否应该返回 IQueryable 等一直存在很多讨论。但最终还是由您来选择。

顺便说一句,为什么你想要延迟加载?一般来说,您确切地知道特定视图需要什么数据,那么为什么要选择以延迟方式获取它,从而进行额外的数据库调用,而不是在一次调用中急切地加载您需要的所有数据呢?就我个人而言,我认为使用多个 Get 方法来获取带或不带子对象的对象是可以的。例如,

public class CompanyRepository
{
    Get(int Id);
    Get(string name);
    GetWithEmployees(int id);
    ...
}

这可能看起来有点矫枉过正,您可能会选择不同的方法,但只要您有遵循的模式,维护代码就会容易得多。

The approach you choose depends a lot on the type of project you are going to be working with. For small projects where a Rapid Application Development (RAD) approach is required, it might almost be OK to use your EF model directly in the MVC project and have data access in the controllers, but the more the project grows, the more messy it will become and you will start running into more and more problems. In case you want good design and maintainability, there are several different approaches, but in general you can stick to the following:

Keep your controllers and Views clean. Controllers should only control the application flow and not contain data access or even business logic. Views should only be used for presentation - give it a ViewModel and it will present it as Html (no business logic or calculations). A ViewModel per view is a pretty clean way of doing it.

A typical controller action would look like:

public ActionResult UpdateCompany(CompanyViewModel model)
{
    if (ModelState.IsValid)
    {
        Company company = SomeCompanyViewModelHelper.
                          MapCompanyViewModelToDomainObject(model);
        companyService.UpdateCompany(company);
        return RedirectToRoute(/* Wherever you go after company is updated */);
    }
    // Return the same view with highlighted errors
    return View(model);
}

Due to the aforementioned reasons, it is good to abstract your data access (testability, ease of switching the data provider or ORM or whatever, etc.). The Repository pattern is a good choice, but here you also get a few implementation options. There's always been a lot of discussion about generic/non-generic repositories, whether or not one should return IQueryables, etc. But eventually it's for you to choose.

Btw, why do you want lazy loading? As a rule, you know exactly what data you require for a specific view, so why would you choose to fetch it in a deferred way, thus making extra database calls, instead of eager loading everything you need in one call? Personally, I think it's okay to have multiple Get methods for fetching objects with or without children. E.g.

public class CompanyRepository
{
    Get(int Id);
    Get(string name);
    GetWithEmployees(int id);
    ...
}

It might seem a bit overkill and you may choose a different approach, but as long as you have a pattern you follow, maintaining the code is much easier.

甜宝宝 2024-10-29 19:08:46

我个人是这样做的:

我有一个抽象的域层,它不仅有 CRUD 方法,还有专门的方法,例如 UsersManager.Authenticate() 等。它内部使用数据访问逻辑或数据访问层抽象(取决于在我需要的抽象级别上)。

至少有一个抽象的依赖总是更好。以下是它的一些优点:

  • 您可以稍后将一种实现替换为另一种实现。
  • 您可以在需要时对控制器进行单元测试。

至于控制器本身,让它有 2 个构造函数:一个具有抽象域访问类(例如域的外观),另一个(空)构造函数选择默认实现。这样,您的控制器在 Web 应用程序运行时(调用空构造函数)和单元测试期间(注入模拟域层)就能正常运行。

此外,为了以后能够轻松切换到另一个域,请务必注入域创建者,而不是域本身。这样,将域层构造本地化到域创建者,您可以随时切换到另一个实现,只需重建域创建者(我所说的创建者是指某种工厂)。

我希望这有帮助。

补充

  • 我不建议在领域层使用CRUD方法,因为每当你丰富单元测试阶段时,甚至当你需要将实现更改为新的阶段时,这都会成为一场噩梦稍后。

Personally I do it this way:

I have an abstract Domain layer, which has methods not just CRUD, but specialized methods, for example UsersManager.Authenticate(), etc. It inside uses data access logic, or data-access layer abstraction (depending on the level of abstraction I need to have).

It is always better to have an abstract dependency at least. Here are some pros of it:

  • you can replace one implementation with another at a later time.
  • you can unit test your controller when needed.

As of controller itself, let it have 2 constructors: one with an abstract domain access class (e.g. facade of domain), and another (empty) constructor which chooses the default implementation. This way your controller lives well during web application run-time (calling empty constructor) and during the unit-testing (with mock domain layer injected).

Also, to be able to easily switch to another domain at a later time, be sure to inject the domain creator, instead of domain itself. This way, localizing the domain layer construction to the domain creator, you can switch to another implementation at any time, by just reconstructing the domain creator (by creator I mean some kind of factory).

I hope this helps.

Addition:

  • I would not recommend having CRUD methods in domain layer, because this will become a nightmare whenever you rich the unit-testing phase, or even more, when you need to change the implementation to the new one at a later time.
各空 2024-10-29 19:08:46

这实际上取决于您想要代码的位置。如果您需要对某个对象进行数据访问,您可以将其放在 IRepository 对象后面或放在控制器中,这并不重要:您仍然会遇到一系列 GetByXXX 调用或等效代码。无论哪种方式,您都可以延迟加载并控制连接的生命周期。所以现在你需要问自己:我希望我的代码放在哪里?

就我个人而言,我主张将其从控制器中取出。我的意思是把它移动到另一层。可能使用 IRespository 类型的模式,其中有一系列 GetByXXX 调用。当然他们很丑。错误的?我会反驳说。至少它们都包含在同一逻辑层中,而不是分散在与验证代码等混合的整个控制器中。

It really comes down to where you want your code. If you need to have data access for an object you can put it behind an IRepository object or in the controller doesn't matter: you will still wind up with either a series of GetByXXX calls or the equivilent code. Either way you can lazy load and control the lifetime of the connection. So now you need to ask yourself: where do I want my code to live?

Personally, I would argue to get it out of the controller. By that I mean moving it to another layer. Probably using an IRespository type of pattern where you have a series of GetByXXX calls. Sure they are ugly. Wrong? I would argue otherwise. At least they are all contained within the same logical layer together rather than being scattered throughout the controllers where they are mixed in with validation code, etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文