In my LabelService I'm trying to join records from the OrderLineRepository with the LabelRepository. When I do this I get this error:
The specified LINQ expression contains references to queries that are
associated with different contexts.
What would be the best solution for this problem? Any suggestions would be appreciated.
Here is the code for my two repositories:
public class LabelRepository : ILabelRepository
{
private OrderContext _context;
public LabelRepository(string connectionName)
{
_context = new OrderContext(connectionName);
}
public LabelRepository()
{
_context = new OrderContext();
}
public LabelRepository(OrderContext context)
{
_context = context;
}
}
public class OrderLineRepository : IOrderLineRepository
{
private OrderContext _context;
public OrderLineRepository(string connectionName)
{
_context = new OrderContext(connectionName);
}
public OrderLineRepository()
{
_context = new OrderContext();
}
public OrderLineRepository(OrderContext context)
{
_context = context;
}
}
And here is parts of the code in my LabelService:
public class LabelService : ILabelService
{
private readonly ILabelRepository _labelRepository;
private readonly IOrderLineRepository _orderLineRepository;
public LabelService(ILabelRepository labelRepository, IOrderLineRepository orderLineRepository)
{
_labelRepository = labelRepository;
_orderLineRepository = orderLineRepository;
}
public List<Label> GetLabelsOrderedByCustomerId(string customerId)
{
var labels = (from ol in _orderLineRepository.GetAll()
join l in _labelRepository.GetAll() on new {ol.QualityId, ol.CustomerId, ol.SerialNumber} equals
new {l.QualityId, l.CustomerId, l.SerialNumber}
where ol.OrderCustomer == customerId
select l).Distinct().ToList();
return labels;
}
}
Unity is used for dependency injection:
public class Resolver : IDependencyResolver
{
public object GetService(Type serviceType)
{
if (serviceType == typeof (LabelsController)) {
return new LabelsController(
new LabelService(new LabelRepository(), new OrderLineRepository()),
new OrderLineService(new OrderLineRepository())
);
}
return null;
}
}
When I need to share context between repositories I tend to introduce an overloaded constructor which lets me pass in a context. It's best to wrap your context up in a nice interface though e.g.
public partial OrderContext : DbContext, IContext
{
...
}
public class OrderLineRepository : IOrderLineRepository
{
public OrderLineRepository(string connectionString)
: this(new OrderContext(connectionName))
{
}
public OrderLineRepository(IContext context)
{
this.Context = (OrderContext)context;
}
public IContext Context { get; private set; }
}
Then in your resolver you could do
public object GetService(Type serviceType)
{
if (serviceType == typeof (LabelsController)) {
var labelRepo = new LabelRepository();
var orderRepo = new OrderLineRepository(labelRepo.Context);
return new LabelsController(
new LabelService(labelRepo, orderRepo),
new OrderLineService(orderRepo)
);
}
return null;
}
Alternatively, another approach I have used in the past is to have a UnitOfWork type class which exposes a context e.g.
public interface IUnitOfWork : IDisposable
{
public IContext Context { get; }
}
public class UnitOfWork : IUnitOfWork
{
public UnitOfWork(string connectionString)
{
this.Context = new OrderContext(connectionString);
}
public IContext Context { get; private set; }
public void Dispose()
{
if (Context != null)
Context.Dispose();
}
}
In your scenario, I would update my repositories to have a read/write Context property which would allow you to swap them out in your services
public List<Label> GetLabelsOrderedByCustomerId(string customerId)
{
using (var uow = new UnitOfWork(connectionString))
{
_labelRepository.Context = uow.Context;
_orderLineRepository.Context = uow.Context;
var labels = (from ol in _orderLineRepository.GetAll()
join l in _labelRepository.GetAll() on new {ol.QualityId, ol.CustomerId, ol.SerialNumber} equals
new {l.QualityId, l.CustomerId, l.SerialNumber}
where ol.OrderCustomer == customerId
select l).Distinct().ToList();
return labels;
}
}
The best solution would be to stop using repositories altogether. DbContext hides the database well enough, and repositories don't really help you with unit tests (you spend a lot of time creating mock versions, while what you really should be doing is create a database instance for unit tests and use EF).
Aside from that, you should pass the same context to all your repositories, that way you can share objects between them very easily.
Related
this is what I got so far, but have to keep passing the DataClassesDataContext around. I wonder if there is a better more centralized way of using the DataClassesDataContext and fill the connectionstring each time the data context is used... Thanks for all the help in advance
public interface ICustomerDataAccess
{
string GetCustomerName(int customerId);
}
public class CustomerDataAccess : ICustomerDataAccess
{
private readonly DataClassesDataContext _context;
public CustomerDataAccess(DataClassesDataContext ctx)
{
_context = ctx;
}
public string GetCustomerName(int id)
{
return _context.Customers.Where(i => i.id == id).FirstOrDefault().name;
}
}
public class DataAccessFactory
{
public static ICustomerDataAccess GetCustomerDataAccessObj(DataClassesDataContext ctx)
{
return new CustomerDataAccess(ctx);
}
}
public class CustomerService
{
CustomerBusinessLogic _customerBL;
public CustomerService(DataClassesDataContext ctx)
{
_customerBL = new CustomerBusinessLogic(new CustomerDataAccess(ctx));
}
public string GetCustomerName(int id, DataClasses1DataContext ctx)
{
return _customerBL.GetCustomerName(id,ctx);
}
}
public class CustomerBusinessLogic
{
ICustomerDataAccess _custDataAccess;
public CustomerBusinessLogic(ICustomerDataAccess custDataAccess)
{
_custDataAccess = custDataAccess;
}
public CustomerBusinessLogic(DataClassesDataContext ctx)
{
_custDataAccess = new CustomerDataAccess(ctx);
}
public string GetCustomerName(int id, DataClassesDataContext ctx)
{
_custDataAccess = DataAccessFactory.GetCustomerDataAccessObj(ctx);
return _custDataAccess.GetCustomerName(id);
}
}
// and using a code like this on the interface
private void button5_Click(object sender, EventArgs e)
{
using (var ctx = new DataClassesDataContext)
{
CustomerService customerSrv = new CustomerService(ctx);
textBox1.Text = customerSrv.GetCustomerName(1, ctx);
}
}
You can use Generic Repository with Dependency Injection. This is a bit complex structure for the first time but this can solve your problem for your problem.
Also I share with you a nice and detail example. That was created by me
https://github.com/EyupCanARSLAN/N-Tier-Architecture-with-Generic-Repository--Dependency-Injection-And-Ninject/security/dependabot
Also, an article about this topic
https://dotnettutorials.net/lesson/generic-repository-pattern-csharp-mvc/
Using the CQS pattern I have a query like this:
public class MyQuery : IQuery<View<SingleView>>
{
public string Token { get; set; }
public Criteria Criteria { get; set; }
}
public class MyQueryHandler : IQueryHandler<MyQuery, View<SingleView>>
{
private readonly IProductRepository _productRepository;
public MyQueryHandler(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public View<SingleView> Handle(MyQuery query)
{
var data = // get my data
return data;
}
}
Off course I have many queries and their belonging QueryHandlers. Now I have a situation that I want to decorate only the MyQueryHandler. I don't want to decorate the rest of the queries.
I am using Autofac, but I just can't get it to work.
Here is how the queries are registered:
builder.RegisterAssemblyTypes(assemblies)
.As(type => type.GetInterfaces()
.Where(interfaceType => interfaceType.IsClosedTypeOf(typeof(IQueryHandler<,>)))
.Select(interfaceType => new KeyedService("queryHandler", interfaceType)));
// Register query decorators
builder.RegisterGenericDecorator(
typeof(LogQueryDecorator<,>),
typeof(IQueryHandler<,>),
"queryHandler");
And here is the decorator I want to use for MyQueryHandler:
public class SaveMyQueryData : IQueryHandler<MyQuery, View<SingleView>>
{
private readonly IQueryHandler<MyQuery, View<SingleView>> _queryHandler;
private readonly IProductRepository _productRepository;
public SaveMyQueryData(
IQueryHandler<MyQuery, View<SingleView>> queryHandler,
IProductRepository productRepository)
{
_queryHandler = queryHandler;
_productRepository = productRepository;
}
public View<SingleView> Handle(MyQuery query)
{
var result = _queryHandler.Handle(query);
// do something with result
return result;
}
}
So how can I register SaveMyQueryData as a decorator just for MyQueryHandler using Autofac?
I finally have it working the way I want. Here is my solution:
Make the decorator open instead of a closed type:
public class SaveMyQueryData : IQueryHandler<Q, R>
{
private readonly IQueryHandler<Q, R> _queryHandler;
private readonly IProductRepository _productRepository;
public SaveMyQueryData(
IQueryHandler<Q, R> queryHandler,
IProductRepository productRepository)
{
_queryHandler = queryHandler;
_productRepository = productRepository;
}
public Q Handle(Q query)
{
var result = _queryHandler.Handle(query);
var view = result as View<SingleView>;
// do something with view
return result;
}
}
Then when registering the type using Autofac, do this:
builder.RegisterGenericDecorator(
typeof(SaveMyQueryData<,>),
typeof(IQueryHandler<,>),
context => context.ImplementationType == typeof(MyQueryHandler));
The last line contains the magic. With the third parameter of RegisterGenericDecorator you can specify a condition when to apply the decorator. This is new since Autofac 4.9 and this solved my problem.
I am using the DbContextScope described here
In his example of how to get a hold of a dbcontext outside of the class its instantiated, Mehdi writes:
public class UserRepository : IUserRepository {
private readonly IAmbientDbContextLocator _contextLocator;
public UserRepository(IAmbientDbContextLocator contextLocator)
{
if (contextLocator == null) throw new ArgumentNullException("contextLocator");
_contextLocator = contextLocator;
}
public User Get(Guid id)
{
return _contextLocator.Get<MyDbContext>.Set<User>().Find(id);
}
}
But, if im going for a generic repository, say
public abstract class RepositoryBase<T> : IRepository<T> where T : class, IDomainEntity
{
private readonly DbSet<T> set;
private IAmbientDbContextLocator contextLocator;
protected RepositoryBase(IAmbientDbContextLocator ctxLocator)
{
if (ctxLocator == null) throw new ArgumentNullException(nameof(ctxLocator));
contextLocator = ctxLocator;
}
public T Get(Guid id)
{
//return _contextLocator.Get<MyDbContext>.Set<T>().Find(userId);
}
}
then how is the dbset supposed to be resolved? how do i work with "MyDbContext" in the Get method?
i do have multiple contexts.
public abstract class RepositoryBase<T, TDbContext> : IRepository<T> where T : IDomainEntity where TDbContext : DbContext
{
private readonly DbSet<T> _dbset;
private readonly IAmbientDbContextLocator _contextLocator;
protected RepositoryBase(IAmbientDbContextLocator ctxLocator)
{
if (ctxLocator == null) throw new ArgumentNullException(nameof(ctxLocator));
_contextLocator = ctxLocator;
_dbset = _contextLocator.Get<TDbContext>.Set<T>();
}
protected DbSet<T> DbSet { get { return _dbset; } }
public T Get(Guid id)
{
return DbSet.Find(id);
}
}
If you don't want TDbContext, You can send DbContext on constructor beside of contextlocator. But he forces you to use DbContextScope, I didn't read all article but let's not break his logic.
I have a BaseRepository which my repositories inherit. The code is declared as the following:
public interface IBaseRepository<T> : where T : class
{
IQueryable<T> GetAll();
}
public abstract class BaseRepository<C, T> : IBaseRepository<T>
where T : class
where C : DbContext, new()
{
protected BaseRepository()
{
_context = new C();
_context.Database.Log = message => Trace.WriteLine(message);
}
private readonly C _context;
protected C Context
{
get { return _context; }
}
public virtual IQueryable<T> GetAll()
{
return _context.Set<T>();
}
}
public interface IARepository : IBaseRepository<A>
{
}
public ARepository : BaseRepository<Entities, A>, IARepository
{
}
public interface IBRepository : IBaseRepository<B>
{
}
public ARepository : BaseRepository<Entities, B>, IBRepository
{
}
I then have a serivce layer which will be using multiple repositories to fetch data for my controllers.
public class SomeService
{
private readonly IARepository _aRepository;
private readonly IBRepository _bRepository;
public EventService(IARepository aRepository, IBRepository bRepository)
{
_aRepository = aRepository;
_bRepository = bRepository;
}
public EventService() : this(new ARepository(), new BRepository())
{
}
public IEnumerable<SomeDTO> GetSomeDTOs()
{
return _aRepository.GetAll()
.Join(_bRepository.GetAll(), a => a.SomeId, b => b.SomeId, (c, d) => new SomeDTO
{
...
...
...
}).ToList();
}
}
But here's the problem. I get the following error:
A first chance exception of type 'System.NotSupportedException'
occurred in EntityFramework.SqlServer.dll
Additional information: The specified LINQ expression contains
references to queries that are associated with different contexts.
when I'm calling the GetSomeDTOs function. From what I can see it should use the same context as it is declared in the baserepository. What seems to be the problem here?
The issue is that each repository has it's own context, you cannot then join them both together. A simple fix would be to create a shared context and pass that in to your repository:
public abstract class BaseRepository<C, T> : IBaseRepository<T>
where T : class
where C : DbContext
{
protected BaseRepository(C context)
{
_context = context;
_context.Database.Log = message => Trace.WriteLine(message);
}
//snip
}
And create your repositories like this:
var context = new MyDbContext();
IARepository aRepository = new ARepository(context);
IBRepository bRepository = new BRepository(context);
I currently have a long-lived datacontext in my data layer like this:
public class DataRepository
{
private readonly NorthwindDatacontext db;
public DataRepository()
{
db = new NorthwindDatacontext();
}
public void insert(Order o)
{
db.Oreder.InsertOnSubmit(o);
db.SubmitChanges();
}
}
from what I understand it is preferred to have short-lived data context
but what I don't understand is when working with short-lived data context is how I handle the next example.
Some method on a client doing this:
public void AddOrderDetails(IEnumrable<OrderDetails> od, Order o)
{
DataRepository repo = new DataRepository();
o.OrderDeatils.AddRange(od);
repo.Update(o);
}
And now my DataRepository is like that:
public class DataRepository
{
public Order GetOrder(int id)
{
using ( var db = New NorthwindDataContext() )
{
db.ObjectTrackingEnabled = false;
var order = db.Oreder.Where(o => o.id == id ).SingleOrDefault();
return order;
}
}
public void Update (Order o)
{
using ( var db = New NorthwindDataContext() )
{
db.Order.Attach(o,true);
db.SubmitChanges();
}
}
}
Will it update the relations ? what if some of the OrderDeatils are new (no id yet) and some are just updated. How should I handle all situations ?
"Short lived" doesn't mean "it lives as long as the method is executed". It also can mean "the context lives at least as long as your repository".
And this seems to be the option preferred in most implementations:
public class DataRepository()
{
private NorthwindContext _context;
public DataRepository( NorthwindContext context )
{
this._context = context;
}
public Order GetOrder( int id )
{
return this._context.....
}
Note that by injecting the context into the repository, rather than creating a context instance per repository, you not only have the same context in all repository methods but also you can share the same instance of the context between different repositories.