데이터베이스에 추상화 계층을 디자인-irepository 를 사용하여 올바른 방법?

StackOverflow https://stackoverflow.com/questions/2347337

문제

나를 설계하는 과정에서 나 ASP.NET MVC 응용 프로그램과 나의 몇 가지 흥미로운 생각입니다.

많은 샘플을 보았을 설명하고를 사용하여 저장소의 패턴(IRepository 다)그래서 이 방법입니다 나는 그 동안 학습 MVC.

이제 내가 무엇을 알고 그것은 모든 일,내가 시작해서 현재 디자인하는 경우,그것이 최선의 방법입니다.

현재 갖고 있는 기본 IUserRepository, 을 정의하는 방법 등 FindById(), SaveChanges(), 니다,등등.

현재,고 싶을 때드/쿼리에는 사용자의 테이블에 DB,나는 뭔가의 라인을 따라 다음과 같다:

    private IUserRepository Repository;

    public UserController()
        : this(new UserRepository())
    { }

    [RequiresAuthentication]
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Edit(string ReturnUrl, string FirstRun)
    {
        var user = Repository.FindById(User.Identity.Name);

        var viewModel = Mapper.Map<User, UserEditViewModel>(user);
        viewModel.FirstRun = FirstRun == "1" ? true : false;

        return View("Edit", viewModel);
    }

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken(Salt = "SaltAndPepper")]
    public ActionResult Edit(UserEditViewModel viewModel, string ReturnUrl)
    {
        //Map the ViewModel to the Model
        var user = Repository.FindById(User.Identity.Name);

        //Map changes to the user
        Mapper.Map<UserEditViewModel, User>(viewModel, user);

        //Save the DB changes
        Repository.SaveChanges();

        if (!string.IsNullOrEmpty(ReturnUrl))
            return Redirect(ReturnUrl);
        else
            return RedirectToAction("Index", "User");
    }

지금 저는 완전히 이해할 수 없는 방법 MVC 에서 작품을 만들기 컨트롤러 사용자가 링크를 만듭(확실하지 않는 경우가 있는 1 명의 사용자는 1 명의 응용 프로그램),그래서 내가 긍정적이지 않의 최선의 조치입니다.

내가 발견한 좋은 질문의 사용에 관한 일반적인 저장소 인터페이스 IRepository<T> 고는 또한 보이는 아이디어의 정적 RepositoryFactory 의 수에 대한 블로그도 있습니다.기본적으로만 1 인스턴스의 저장소에 보관하고 그를 통해 얻은 이 factory

그래서 제 질문 중심으로 주위 사람들이 어떻게 그것은 거기에서 앱,그리고 무엇으로 간주 좋습니다.

사람들은 개인 repositorys 에 따라 각각의 테이블(IUserRepository)?
그들은 사용하는 일반 IRepository<T>?
그들은 사용하여 정적 저장소 공장입니까?
또는 다른 뭔가가 완전히?

편집:나는 그냥 깨달아야 아마 요청뿐만 아니라:

는 개인 IRepository 각 컨트롤러에서 작동하는지 평가하는 좋은 방법?거나 새 인스턴스화 IRepository 모든 시간을 사용하고 싶지?

BOUNTY 편집:나는 시작 현상금을 좀 더 많은 관점(하지 않는 것이 팀의이 유용하지 않).

저는 호기심을 알고 사람들이 무엇을 하는지에서 자신의 MVC 앱이나 그들은 무엇을 생각하는 것이 좋습니다.

도움이 되었습니까?

해결책

Some very obvious problems with the idea of a generic IRepository<T>:

  • It assumes that every entity uses the same type of key, which is not true in almost any non-trivial system. Some entities will use GUIDs, others may have some kind of natural and/or composite key. NHibernate can support this fairly well but Linq to SQL is pretty bad at it - you have to write a good deal of hackish code to do automatic key mapping.

  • It means that each repository can only deal with exactly one entity type and only supports the most trivial operations. When a repository is relegated to such a simple CRUD wrapper it has very little use at all. You might as well just hand the client an IQueryable<T> or Table<T>.

  • It assumes that that you perform exactly the same operations on every entity. In reality this is going to be very far from the truth. Sure, maybe you want to get that Order by its ID, but more likely you want to get a list of Order objects for a specific customer and within some date range. The notion of a totally generic IRepository<T> doesn't allow for the fact that you'll almost certainly want to perform different types of queries on different types of entities.

The whole point of the repository pattern is to create an abstraction over common data access patterns. I think some programmers get bored with creating repositories so they say "Hey, I know, I'll create one über-repository that can handle any entity type!" Which is great except that the repository is pretty much useless for 80% of what you're trying to do. It's fine as a base class/interface, but if that's the full extent of the work that you do then you're just being lazy (and guaranteeing future headaches).


Ideally I might start with a generic repository that looks something like this:

public interface IRepository<TKey, TEntity>
{
    TEntity Get(TKey id);
    void Save(TEntity entity);
}

You'll notice that this doesn't have a List or GetAll function - that's because it's absurd to think that it's acceptable to retrieve the data from an entire table at once anywhere in the code. This is when you need to start going into specific repositories:

public interface IOrderRepository : IRepository<int, Order>
{
    IEnumerable<Order> GetOrdersByCustomer(Guid customerID);
    IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate);
    IPager<Order> GetOrdersByProduct(int productID);
}

And so on - you get the idea. This way we have the "generic" repository for if we ever actually need the incredibly simplistic retrieve-by-id semantics, but in general we're never actually going to pass that around, certainly not to a controller class.


Now as for controllers, you have to do this right, otherwise you've pretty much negated all the work you just did in putting together all the repositories.

A controller needs to take its repository from the outside world. The reason you created these repositories is so you can do some kind of Inversion of Control. Your ultimate goal here is to be able to swap out one repository for another - for example, to do unit testing, or if you decide to switch from Linq to SQL to Entity Framework at some point in the future.

An example of this principle is:

public class OrderController : Controller
{
    public OrderController(IOrderRepository orderRepository)
    {
        if (orderRepository == null)
            throw new ArgumentNullException("orderRepository");
        this.OrderRepository = orderRepository;
    }

    public ActionResult List(DateTime fromDate, DateTime toDate) { ... }
    // More actions

    public IOrderRepository OrderRepository { get; set; }
}

In other words the Controller has no idea how to create a repository, nor should it. If you have any repository-construction going on in there, it's creating coupling that you really don't want. The reason that the ASP.NET MVC sample controllers have parameterless constructors that creates concrete repositories is that the sites need to be able to compile and run without forcing you to set up an entire Dependency Injection framework.

But in a production site, if you aren't passing in the repository dependency through a constructor or public property, then you're wasting your time having repositories at all, because the controllers are still tightly coupled to the database layer. You need to be able to write test code like this:

[TestMethod]
public void Can_add_order()
{
    OrderController controller = new OrderController();
    FakeOrderRepository fakeRepository = new FakeOrderRepository();
    controller.OrderRepository = fakeRepository; //<-- Important!
    controller.SubmitOrder(...);
    Assert.That(fakeRepository.ContainsOrder(...));
}

You can't do this if your OrderController is going off and creating its own repository. This test method isn't supposed to do any data access, it just makes sure that the controller is invoking the correct repository method based on the action.


This isn't DI yet, mind you, this is just faking/mocking. Where DI comes into the picture is when you decide that Linq to SQL isn't doing enough for you and you really want the HQL in NHibernate, but it's going to take you 3 months to port everything over, and you want to be able to do this one repository at a time. So, for example, using a DI Framework like Ninject, all you have to do is change this:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>();
Bind<IProductRepository>().To<LinqToSqlProductRepository>();

To:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>();
Bind<IOrderRepository>().To<NHibernateOrderRepository>();
Bind<IProductRepository>().To<NHibernateProductRepository>();

And there you are, now everything that depends on IOrderRepository is using the NHibernate version, you've only had to change one line of code as opposed to potentially hundreds of lines. And we're running the Linq to SQL and NHibernate versions side by side, porting functionality over piece by piece without ever breaking anything in the middle.


So to summarize all the points I've made:

  1. Don't rely strictly on a generic IRepository<T> interface. Most of the functionality you want from a repository is specific, not generic. If you want to include an IRepository<T> at the upper levels of the class/interface hierarchy, that's fine, but controllers should depend on specific repositories so you don't end up having to change your code in 5 different places when you find that the generic repository is missing important methods.

  2. Controllers should accept repositories from the outside, not create their own. This is an important step in eliminating coupling and improving testability.

  3. Normally you'll want to wire up the controllers using a Dependency Injection framework, and many of them can be seamlessly integrated with ASP.NET MVC. If that's too much for you to take in, then at the very least you should be using some kind of static service provider so you can centralize all of the repository-creation logic. (In the long run, you'll probably find it easier to just learn and use a DI framework).

다른 팁

사람들은 개인 repositorys 에 따라 각각의 테이블(IUserRepository)? 는 저장소에 대한 각 집계하지,각각의 테이블에 있습니다.

그들은 사용하는 일반 irepository 를? 가능한 경우에는 예

그들은 사용하여 정적 저장소 공장입니까? 내가 선호하는 주사 저장소의 인스턴스를 통해 IOC container

Here is how I'm using it. I'm using IRepository for all the operations that are common to all of my repositories.

public interface IRepository<T> where T : PersistentObject
{
    T GetById(object id);
    T[] GetAll();
    void Save(T entity);
}

and I use also a dedicated an ITRepository for each aggregate for the operation that are distinct to this repository. For example for user I will use IUserRepository to add method that are distinct to UserRepository:

public interface IUserRepository : IRepository<User>
{
    User GetByUserName(string username);
}

The implementation will look like this:

public class UserRepository : RepositoryBase<User>, IUserRepository
{
    public User GetByUserName(string username)
    {
        ISession session = GetSession();
        IQuery query = session.CreateQuery("from User u where u.Username = :username");
        query.SetString("username", username);

        var matchingUser = query.UniqueResult<User>();

        return matchingUser;
    }
}


public class RepositoryBase<T> : IRepository<T> where T : PersistentObject
{
    public virtual T GetById(object id)
    {
        ISession session = GetSession();
        return session.Get<T>(id);
    }

    public virtual T[] GetAll()
    {
        ISession session = GetSession();
        ICriteria criteria = session.CreateCriteria(typeof (T));
        return criteria.List<T>().ToArray();
    }

    protected ISession GetSession()
    {
        return new SessionBuilder().GetSession();
    }

    public virtual void Save(T entity)
    {
        GetSession().SaveOrUpdate(entity);
    }
}

Than in the UserController will look as:

public class UserController : ConventionController
{
    private readonly IUserRepository _repository;
    private readonly ISecurityContext _securityContext;
    private readonly IUserSession _userSession;

    public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession)
    {
        _repository = repository;
        _securityContext = securityContext;
        _userSession = userSession;
    }
}

Than the repository is instantiated using dependency injection pattern using custom controller factory. I'm using StructureMap as my dependency injection layer.

The database layer is NHibernate. ISession is the gateway to the the database in this session.

I'm suggest you to look on CodeCampServer structure, you can learn a lot from it.

Another project that you can learn fro it is Who Can Help Me. Which I don't dig enough in it yet.

다음 이미지는 전체 앱 스키마에 대한 개요를 제공 할 수 있습니다.

여기에 이미지 설명

다음 호스팅 된 앱의 각 유형에 대한 정의를 읽을 수 있습니다.

SharePoint 호스팅 앱

SharePoint 호스팅 된 응용 프로그램 또는 모든 구성 요소가 온 - 프레미스 또는 Office 365 SharePoint Farm에서 호스팅되는 앱. SharePoint-Hosted Apps는 호스트 웹이라는 SharePoint 2013 웹 사이트에 설치됩니다. 앱 웹이라는 호스트 웹의 고립 된 하위 사이트에서 호스팅되는 자원이 있습니다. 호스트 웹 및 앱 웹 간의 차이점을 아는 것이 중요합니다. 그림 1은 SharePoint-Hosted App의 기본 아키텍처를 보여줍니다. 여기에 이미지 설명

공급자 호스팅 앱

Provider-Hosted SharePoint 용 앱에는 SharePoint 팜 외부에서 배포 및 호스팅되는 구성 요소가 포함됩니다. 호스트 웹에 설치되지만 원격 구성 요소는 다른 서버에서 호스팅됩니다. 그림 2는 공급자 호스팅 응용 프로그램의 기본 아키텍처를 보여줍니다. 여기에 이미지 설명

autohosted apps

SharePoint 용 AutoHosted Apps는 클라우드 호스팅 된 앱입니다. 구성 요소는 Windows Azure에서 프로비저닝되고 배포됩니다. 같이 공급자 호스팅 앱을 사용하면 SharePoint 용 자동 호출 된 응용 프로그램 SharePoint 웹 사이트와 상호 작용하지만 리소스를 사용하고 Windows에서 호스팅하는 원격 사이트에있는 서비스 하늘빛. SharePoint 2013 설치 조항은 이들을 배포합니다 당신을위한 리소스. 여기에 이미지 설명

자세한 정보는 다음을 보냅니다. http://msdn.microsoft.com/en-us/library/office/fp179887(v=office.15).aspx

You can find an excellent Generic Repopsitory library that was written to enable it to be used as a WebForms asp:ObjectDataSource on codeplex: MultiTierLinqToSql

Each of my controllers have private repositories for the actions they need to support.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top