Should we use Data Repository pattern in MVC application using Entity Framework Code First approach?

偶尔善良 提交于 2019-12-06 09:35:57

问题


I have developed many application now with Entity Framework Code First approach. In all of the I use Data Repository pattern. This Data Repository pattern queries over single entity at a Time. for e.g,

I have 2 models Employee and Department

So to fetch all employees and department I would create 2 data repository instances. e.g

var empRepository = new DataRepository<Employee>();
var allEmployees = empRepository.GetAll();

var depRepository = new DataRepository<Department>();
var alldepartment = depRepository.GetAll();

Now, This pattern works great in most of the case. Now, When I want to perform join I can not do that in this pattern. I can perform join only after I have fetched all records of both entities and then i can use join on in-memory data. this creates extra overhead of 2 queries in my logic. Does any one have good pattern or solution that can be used with DataRepository pattern. Please suggest any alternatives to this pattern.


回答1:


I actually just implemented a Join function in my generic repository just yesterday. It's a lot easier to do than how everyone's making it out to be, and you can use some cool features of Entity Framework while doing so.

To get started, I'm using a repository similar to what I wrote in this blog post. You'll notice that a number of the methods are returning IQueryable. This is no accident. IQueryable will allow to still use deferred execution, meaning that the query won't be run on the database until something forces it to (i.e. a loop or a .ToList() call). You'll see that, with putting the Join on this type of repository, that you won't end up needing to load up all the entities into memory to do the join, since all you'd be exposing is an IQueryable.

That being said, here's how I've got the Join in my repository, based off of the Queryable version of the Join method:

public IQueryable<TResult> Join<TInner, TKey, TResult>(IRepository<TInner> innerRepository, Expression<Func<T, TKey>> outerSelector, Expression<Func<TInner, TKey>> innerSelector, Expression<Func<T, TInner, TResult>> resultSelector) where TInner : class
{
  return DbSet.Join(innerRepository.All(), outerSelector, innerSelector, resultSelector);
}

This, then, only makes one query to the database to get all of the information that you're requesting (again, since it only passes an IQueryable in the All() method).




回答2:


with Repository pattern you can join two tables as in normal scenario.say you have defined your repository like this :

 public interface IRepository<T>
    {
        T GetById(object id);
        void Insert(T entity);
        void Update(T entity);
        void Delete(T entity);
        IQueryable<T> Table { get; }
    }

then you can join two table as following :

from p in _someRepository.Table
join p2 in _someOtherRepository.Table on p.FOO equals p2.BAR

with this approach there is no need to load all of table entries into memory.




回答3:


Please take a look at the following links, in case you have not...

Social MSDN and

Forums.Asp.Net

There is a answer by @Link.fr which states as follows:-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Repository
{
  class Program
  {
    static void Main(string[] args)
    {
      // get all the informations about orders
      CustomersRepository oCustomersRepository = new CustomersRepository();
      ProductsRepository oProductsRepository = new ProductsRepository();
      OrdersRepository oOrdersRepository = new OrdersRepository();


      var query1 = oOrdersRepository.SelectAll().
        Join(oCustomersRepository.SelectAll(),
           order => order.CustomerId,
           customer => customer.Id,
           (order, customer) => new
                        {
                          MyOrder = order,
                          MyCustomer = customer
                        }).
        Join(oProductsRepository.SelectAll(),
           item => item.MyOrder.ProductId,
           product => product.Id,
           (item, product) => new 
                      { 
                        MyOrder = item.MyOrder, 
                        MyCustomer = item.MyCustomer, 
                        MyProduct = product }).
        ToList();

      foreach (var row in query1)
      {
        Console.WriteLine("query1 : {0} - {1}", row.MyCustomer.Name, row.MyProduct.Name);
      }

      Console.WriteLine("--");

Or

var query2 = (from order in oOrdersRepository.SelectAll()
             join customer in oCustomersRepository.SelectAll() on order.CustomerId equals customer.Id
             join product in oProductsRepository.SelectAll() on order.ProductId equals product.Id
             select
             new
             {
               CustomerName = customer.Name,
               ProductName = product.Name
             }).ToList();

      foreach (var row in query2)
      {
        Console.WriteLine("query2 : {0} - {1}", row.CustomerName, row.ProductName);
      }



      Console.ReadKey();
    }
  }

The CLASS goes like:-

public class Customer
  {
    public int Id { get; set; }
    public string Name { get; set; }
  }

  public class Product
  {
    public int Id { get; set; }
    public string Name { get; set; }
  }

  public class Order
  {
    public int CustomerId { get; set; }
    public int ProductId { get; set; }
  }

  public interface IRepository<T>
  {
    IList<T> SelectAll();
    IList<T> SelectAll(Func<T, bool> expression);
  }

  public class CustomersRepository : IRepository<Customer>
  {
    public IList<Customer> SelectAll()
    {
      return new List<Customer>
      {
        new Customer{ Id = 1, Name = "Customer1"},
        new Customer{ Id = 2, Name = "Customer2"},
        new Customer{ Id = 3, Name = "Customer3"},
        new Customer{ Id = 4, Name = "Customer4"}
      };
    }

    public IList<Customer> SelectAll(Func<Customer, bool> expression)
    {
      return new List<Customer>
      {
        new Customer{ Id = 1, Name = "Customer1"},
        new Customer{ Id = 2, Name = "Customer2"},
        new Customer{ Id = 3, Name = "Customer3"},
        new Customer{ Id = 4, Name = "Customer4"}
      }.Where(expression).ToList();
    }
  }

  public class ProductsRepository : IRepository<Product>
  {
    public IList<Product> SelectAll()
    {
      return new List<Product>
      {
        new Product{ Id = 1, Name = "Product1"},
        new Product{ Id = 2, Name = "Product2"},
        new Product{ Id = 3, Name = "Product3"},
        new Product{ Id = 4, Name = "Product4"}
      };
    }

    public IList<Product> SelectAll(Func<Product, bool> expression)
    {
      return new List<Product>
      {
        new Product{ Id = 1, Name = "Product1"},
        new Product{ Id = 2, Name = "Product2"},
        new Product{ Id = 3, Name = "Product3"},
        new Product{ Id = 4, Name = "Product4"}
      }.Where(expression).ToList();
    }
  }

  public class OrdersRepository : IRepository<Order>
  {
    public IList<Order> SelectAll()
    {
      return new List<Order>
      {
        new Order{ CustomerId = 1, ProductId = 1},
        new Order{ CustomerId = 1, ProductId = 2},
        new Order{ CustomerId = 2, ProductId = 3},
        new Order{ CustomerId = 3, ProductId = 4},
      };
    }

    public IList<Order> SelectAll(Func<Order, bool> expression)
    {
      return new List<Order>
      {
        new Order{ CustomerId = 1, ProductId = 1},
        new Order{ CustomerId = 1, ProductId = 2},
        new Order{ CustomerId = 2, ProductId = 3},
        new Order{ CustomerId = 3, ProductId = 4},
      }.Where(expression).ToList();
    }
  }
}

May be it helps.




回答4:


This is one of the reasons why repositories are a burden, especially generic repositories (one per entity type). If you try remain true to the pattern you always bump into some major problems.

  • You keep writing these clumsy join queries (linq). You can't really exploit the power of navigation properties. When queries get beyond simple this is a major pain.
  • So you're probably going to use navigation properties regardless. Now the question is: which repository is responsible for which entity? The goal of a query is to get children but a comfortable entry point is the parent.
  • You also have to have some unit of work (UoW) to collect and "transactionize" the CUD actions of separate repositories.

Why not use the context directly? See this post (it seems I keep quoting it). A context is a class that contains repositories (DbSets or ObjectSets) and keeps track of changes and transactions (UoW). Why wrap this unit of repositories and UoW in yet another layer of repositories and UoW's? That's two layers. And then, you're not going to expose your repositories to client code, because you don't want to expose IQueryables. So you're going to wrap repositories in a service layer that creates, orchestrates and disposes them. That's three layers.

(And repositories should expose IQueryable otherwise they're not composable).

But it is not testable, people say, we want to mock repositories so we can inject these mock repositories into our services for unit testing. That's an illusion. Mocking entity framework behaviour is deceptive and practically impossible. I'm not saying that unit tests are useless. I write unit tests for all kinds of business logic that should work once the objects exist. I just don't write unit tests to test data access code.



来源:https://stackoverflow.com/questions/15532723/should-we-use-data-repository-pattern-in-mvc-application-using-entity-framework

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!