C # 엔티티 프레임 워크 : 저장소 클래스 내에서 DBContext 클래스의 올바른 사용
아래에서 볼 수 있듯이 저장소 클래스를 구현했습니다.
public Class MyRepository
{
private MyDbContext _context;
public MyRepository(MyDbContext context)
{
_context = context;
}
public Entity GetEntity(Guid id)
{
return _context.Entities.Find(id);
}
}
그러나 최근에 저장소에서 데이터 컨텍스트를 개인 구성원으로 사용하는 것이 좋지 않다는 기사를 읽었습니다. http://devproconnections.com/development/solving-net-scalability-problem
이제 이론적으로 기사가 맞습니다. DbContext가 IDisposable을 구현하기 때문에 가장 정확한 구현은 다음과 같습니다.
public Class MyRepository
{
public Entity GetEntity(Guid id)
{
using (MyDbContext context = new MyDBContext())
{
return context.Entities.Find(id);
}
}
}
그러나이 다른 기사에 따르면 DbContext를 폐기하는 것은 필수적이지 않습니다. http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html
두 기사 중 어느 것이 맞습니까? 꽤 혼란 스럽습니다. 리포지토리 클래스에서 DbContext를 개인 멤버로 사용하면 첫 번째 기사에서 제안한 것처럼 실제로 "확장 성 문제"가 발생할 수 있습니까?
나는 당신 이 첫 번째 기사를 따르지 말아야 한다고 생각 하고 그 이유를 알려줄 것입니다.
첫 번째 접근 방식을 따르면 Entity Framework DbContext
가 1 단계 캐시, ID 맵, 작업 단위, 변경 추적 및 지연로드 기능을 포함하여 Entity Framework가 제공하는 거의 모든 기능을 잃게 됩니다. 이는 위의 시나리오 DbContext
에서 모든 데이터베이스 쿼리에 대해 새 인스턴스가 생성되고 즉시 폐기되므로 DbContext
인스턴스가 전체 비즈니스 트랜잭션에서 데이터 개체의 상태를 추적 할 수 없기 때문입니다.
DbContext
저장소 클래스에 개인 속성 을 갖는 것도 문제가 있습니다. 더 나은 접근 방식은 CustomDbContextScope를 사용하는 것입니다. 이 접근 방식은 Mehdi El Gueddari라는 사람에 의해 매우 잘 설명됩니다.
이 기사 http://mehdi.me/ambient-dbcontext-in-ef6/ 내가 본 EntityFramework에 대한 최고의 기사 중 하나입니다. 당신은 그것을 완전히 읽어야하며, 나는 그것이 당신의 모든 질문에 답할 것이라고 믿습니다.
두 개 이상의 저장소가 있고 다른 저장소에서 2 개의 레코드를 업데이트해야한다고 가정 해 보겠습니다. 그리고 트랜잭션 방식으로 수행해야합니다 (하나가 실패하면 두 업데이트 롤백).
var repositoryA = GetRepository<ClassA>();
var repositoryB = GetRepository<ClassB>();
repository.Update(entityA);
repository.Update(entityB);
따라서 각 리포지토리 (케이스 2)에 대해 고유 한 DbContext가있는 경우이를 위해 TransactionScope를 사용해야합니다.
더 나은 방법-하나의 작업에 대해 하나의 공유 DbContext를 사용합니다 (하나의 호출에 대해 하나 의 작업 단위에 대해 ). 따라서 DbContext는 트랜잭션을 관리 할 수 있습니다. EF는 그럴 것입니다. 하나의 DbContext 만 생성하고, 많은 리포지토리에서 모든 변경을 수행하고, SaveChanges를 한 번 호출하고, 모든 작업 및 작업이 완료된 후 폐기 할 수 있습니다.
다음 은 UnitOfWork 패턴 구현의 예입니다.
두 번째 방법은 읽기 전용 작업에 유용 할 수 있습니다.
루트 규칙은 다음과 같습니다 . DbContext 수명은 실행중인 트랜잭션으로 제한되어야합니다 .
여기서 "트랜잭션"은 읽기 전용 쿼리 또는 쓰기 쿼리를 의미 할 수있다. 이미 알고 계시 겠지만 거래는 가능한 한 짧아야합니다.
즉, 대부분의 경우 "사용"방식을 선호하고 비공개 멤버를 사용하지 않는 것이 좋습니다.
비공개 멤버를 사용하는 유일한 경우는 CQRS 패턴 (CQRS : 작동 방식에 대한 교차 검토) 입니다.
그건 그렇고, Jon Gallant의 게시물 에있는 Diego Vega 응답은 현명한 조언을 제공합니다.
샘플 코드가 항상 "사용"을 사용하거나 다른 방식으로 컨텍스트를 처리하는 두 가지 주요 이유가 있습니다.
기본 자동 열기 / 닫기 동작은 비교적 쉽게 재정의 할 수 있습니다. 연결을 수동으로 열어 연결을 열고 닫을 때 제어 할 수 있습니다. 코드의 일부에서이 작업을 시작하면 열린 연결이 누출 될 수 있으므로 컨텍스트를 삭제하는 것을 잊는 것이 해로워집니다.
DbContext는 권장 패턴에 따라 IDiposable을 구현합니다. 여기에는 예를 들어 다른 관리되지 않는 리소스를 컨텍스트의 수명에 집계해야하는 경우 파생 형식이 재정의 할 수있는 보호 된 가상 Dispose 메서드를 노출하는 것이 포함됩니다.
HTH
사용할 방법은 저장소의 책임에 따라 다릅니다.
전체 트랜잭션을 실행하는 것이 저장소의 책임입니까? 즉, 변경 한 다음`SaveChanges? '를 호출하여 데이터베이스에 변경 사항을 저장합니다. 아니면 더 큰 트랜잭션의 일부일 뿐이므로 저장하지 않고 변경 만 수행합니까?
사례 # 1) 저장소는 전체 트랜잭션을 실행합니다 (변경하고 저장합니다).
이 경우 두 번째 방법이 더 좋습니다 (두 번째 코드 샘플의 방법).
다음과 같은 팩토리를 도입하여이 접근 방식을 약간 수정합니다.
public interface IFactory<T>
{
T Create();
}
public class Repository : IRepository
{
private IFactory<MyContext> m_Factory;
public Repository(IFactory<MyContext> factory)
{
m_Factory = factory;
}
public void AddCustomer(Customer customer)
{
using (var context = m_Factory.Create())
{
context.Customers.Add(customer);
context.SaveChanges();
}
}
}
종속성 주입 을 활성화하기 위해 약간의 변경을 수행하고 있습니다 . 이를 통해 나중에 컨텍스트를 만드는 방식을 변경할 수 있습니다.
리포지토리가 컨텍스트를 자체적으로 생성하는 책임을지는 것을 원하지 않습니다. 구현하는 공장 IFactory<MyContext>
은 컨텍스트를 생성 할 책임이 있습니다.
리포지토리가 컨텍스트의 수명을 관리하고, 컨텍스트를 생성하고, 변경 사항을 저장하고, 컨텍스트를 폐기하는 방법에 주목하십시오. 이 경우 저장소는 컨텍스트보다 수명이 더 깁니다.
사례 # 2) 저장소가 더 큰 트랜잭션의 일부입니다 (일부 변경을 수행하고 다른 저장소가 다른 변경을 수행 한 다음 다른 사람이을 호출하여 트랜잭션을 커밋 할 것임 SaveChanges
).
이 경우 첫 번째 접근 방식 (질문에서 먼저 설명)이 더 좋습니다.
저장소가 더 큰 트랜잭션의 일부가 될 수있는 방법을 이해하기 위해 계속되는 것을 상상해보십시오.
using(MyContext context = new MyContext ())
{
repository1 = new Repository1(context);
repository1.DoSomething(); //Modify something without saving changes
repository2 = new Repository2(context);
repository2.DoSomething(); //Modify something without saving changes
context.SaveChanges();
}
저장소의 새 인스턴스가 각 트랜잭션에 사용됩니다. 이것은 저장소의 수명이 매우 짧다는 것을 의미합니다.
내 코드에서 리포지토리를 새로 만들고 있다는 점에 유의하십시오 (종속성 주입 위반). 나는 이것을 예시로 보여주고있다. 실제 코드에서는 팩토리를 사용하여이 문제를 해결할 수 있습니다.
이제이 접근 방식에 대해 수행 할 수있는 한 가지 개선 사항은 저장소가 더 이상 액세스 할 수 없도록 인터페이스 뒤에 컨텍스트를 숨기는 것입니다 SaveChanges
( 인터페이스 분리 원칙 참조 ).
다음과 같이 할 수 있습니다.
public interface IDatabaseContext
{
IDbSet<Customer> Customers { get; }
}
public class MyContext : DbContext, IDatabaseContext
{
public IDbSet<Customer> Customers { get; set; }
}
public class Repository : IRepository
{
private IDatabaseContext m_Context;
public Repository(IDatabaseContext context)
{
m_Context = context;
}
public void AddCustomer(Customer customer)
{
m_Context.Customers.Add(customer);
}
}
원하는 경우 인터페이스에 필요한 다른 메소드를 추가 할 수 있습니다.
이 인터페이스는에서 상속되지 않습니다 IDisposable
. 즉, Repository
클래스는 컨텍스트의 수명을 관리 할 책임이 없습니다. 이 경우 컨텍스트는 저장소보다 수명이 더 깁니다. 다른 누군가가 컨텍스트의 수명을 관리 할 것입니다.
첫 번째 기사에 대한 참고 사항 :
첫 번째 기사는 질문에서 설명하는 첫 번째 접근 방식을 사용하지 말 것을 제안합니다 (컨텍스트를 저장소에 삽입).
이 기사는 저장소가 어떻게 사용되는지 명확하지 않습니다. 단일 거래의 일부로 사용됩니까? 아니면 여러 트랜잭션에 걸쳐 있습니까?
나는 기사가 (부정적으로) 설명하는 접근 방식에서 저장소가 많은 트랜잭션에 걸친 장기 실행 서비스로 사용된다는 것을 추측하고 있습니다 (확실하지 않습니다). 이 경우 나는 기사에 동의합니다.
그러나 여기서 제가 제안하는 것은 다릅니다.이 접근 방식은 트랜잭션이 필요할 때마다 저장소의 새 인스턴스가 생성되는 경우에만 사용된다는 것을 제안합니다.
두 번째 기사에 대한 참고 사항 :
두 번째 기사에서 말하는 것은 어떤 접근 방식을 사용해야하는지와는 무관하다고 생각합니다.
두 번째 기사에서는 어떤 경우 에든 컨텍스트를 폐기해야하는지 여부에 대해 논의합니다 (저장소 설계와 무관).
두 가지 디자인 접근 방식에서는 컨텍스트를 처리하고 있습니다. 유일한 차이점은 그러한 처분에 대한 책임입니다.
이 기사는 DbContext
컨텍스트를 명시 적으로 처리 할 필요없이 리소스를 정리 하는 것처럼 보인다고 말합니다 .
링크 한 첫 번째 기사에서는 정확한 한 가지 중요한 사실을 잊었습니다. 소위 NonScalableUserRepostory
인스턴스 의 수명은 무엇입니까 (인스턴스 를 적절하게 처리하기 위해 NonScalableUserRepostory
구현 하는 것도 잊었습니다 ).IDisposable
DbContext
다음과 같은 경우를 상상해보십시오.
public string SomeMethod()
{
using (var myRepository = new NonScalableUserRepostory(someConfigInstance))
{
return myRepository.GetMyString();
}
}
Well... there would still be some private DbContext
field inside the NonScalableUserRepostory
class, but the context will only be used once. So it's exactly the same as what the article describes as the best practice.
So the question is not "should I use a private member vs a using statement ?", it's more "what should be the lifetime of my context ?".
The answer would then be: try to shorten it as much as possible. There's the notion of Unit Of Work, which represents a business operation. Basically you should have a new DbContext
for each unit of work.
How a unit of work is defined and how it's implemented will depend on the nature of your application ; for instance, for an ASP.Net MVC application, the lifetime of the DbContext
is generally the lifetime of the HttpRequest
, i.e. one new context is created each time the user generates a new web request.
EDIT :
To answer your comment:
One solution would be to inject via constructor a factory method. Here's some basic example:
public class MyService : IService
{
private readonly IRepositoryFactory repositoryFactory;
public MyService(IRepositoryFactory repositoryFactory)
{
this.repositoryFactory = repositoryFactory;
}
public void BusinessMethod()
{
using (var repo = this.repositoryFactory.Create())
{
// ...
}
}
}
public interface IRepositoryFactory
{
IRepository Create();
}
public interface IRepository : IDisposable
{
//methods
}
The 1st code doesn't have relation to the scability problem, the reason it is bad is because he create new context for every repository which is bad, which one of commenters comment but he failed to even reply. In web it was 1 request 1 dbContext, if you plan to use repository pattern then it translate into 1 request > many repository > 1 dbContext. this easy to achieve with IoC, but not necessary. this is how you do it without IoC:
var dbContext = new DBContext();
var repository = new UserRepository(dbContext);
var repository2 = new ProductRepository(dbContext);
// do something with repo
As for disposing or not, I usually dispose it but if the lead itself said this then probably no reason to do it. I just like to dispose if it has IDisposable.
Basically DbContext class is nothing but a wrapper which handles all the database related stuffs like: 1. Create connection 2. Execute Query. Now if we do the above mentioned stuff using normal ado.net then we need to explicitly close the connection properly either by writing the code in using statement or by calling the close() method on connection class object.
Now, as the context class implements the IDisposable inteface internally, it is good practise to write the dbcontext in using statement so that we need not care about closing the connection.
Thanks.
I use the first way (injecting the dbContext) Of course it should be an IMyDbContext and your dependency injection engine is managing the lifecycle of the context so it is only live whilst it is required.
This lets you mock out the context for testing, the second way makes it impossible to examine the entities without a database for the context to use.
Second approach (using) is better as it surely holds the connection only for the minimum needed time and is easier to make thread-safe.
I think that fist approach is better, you never have to create a dbcontext for each repository, even if you dispose it. However in the first case you can use a databaseFactory to instantiate only one dbcontext:
public class DatabaseFactory : Disposable, IDatabaseFactory {
private XXDbContext dataContext;
public ISefeViewerDbContext Get() {
return dataContext ?? (dataContext = new XXDbContext());
}
protected override void DisposeCore() {
if (dataContext != null) {
dataContext.Dispose();
}
}
}
And use this instance in Repository:
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private IXXDbContext dataContext;
private readonly DbSet<TEntity> dbset;
public Repository(IDatabaseFactory databaseFactory) {
if (databaseFactory == null) {
throw new ArgumentNullException("databaseFactory", "argument is null");
}
DatabaseFactory = databaseFactory;
dbset = DataContext.Set<TEntity>();
}
public ISefeViewerDbContext DataContext {
get { return (dataContext ?? (dataContext = DatabaseFactory.Get()));
}
public virtual TEntity GetById(Guid id){
return dbset.Find(id);
}
....
'IT Share you' 카테고리의 다른 글
java.net.SocketTimeoutException 가져 오기 : Android에서 연결 시간이 초과되었습니다. (0) | 2020.11.08 |
---|---|
Ruby에서 동적 메서드 호출 (0) | 2020.11.08 |
탭을 사용하지 않고 jupyter 노트북에서 자동 완성을 얻는 방법은 무엇입니까? (0) | 2020.11.08 |
인덱스가 범위를 벗어난 부분 문자열 슬라이싱이 작동하는 이유는 무엇입니까? (0) | 2020.11.08 |
Haskell 단위 테스트 (0) | 2020.11.08 |