using short-lived datacontext with LINQ to SQL - c#

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.

Related

Update is not working in OwnsMany Value Object relationship EF Core 5

I have the following classes (reduced for simplicity).
public class User : Entity
{
public List<Skill> Skills { get; set; }
}
public class Skill : ValueObject
{
public string Name { get; private set; }
}
My UserConfiguration class is the following:
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder
.OwnsMany(user => user.Skills);
}
}
Here I am saying EF that my User class owns some Skills and it generates the following table:
I use the Unit of Work pattern, so in my repository I do the following on my Update method:
public virtual void Update(T entityToUpdate)
{
_context.Entry(entityToUpdate).State = EntityState.Modified;
}
And after updating I call my Commit method, which saves the changes:
public void Commit()
{
_dbContext.SaveChanges();
}
As far as I have been able to see, on getting the User entity I am receiving all the properties correctly (including the Skills), but on updating is updating each property but the Skills.
I have tried to manually set the Skill's table Primary Key and Foreign Key changing the UserConfiguration class this way (with the same result, skills are not updated):
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder
.OwnsMany(user => user.Skills, skill =>
{
skill.WithOwner().HasForeignKey("UserId");
skill.Property<int>("Id");
skill.HasKey("id");
});
}
}
I have also tried to change my Update method to:
public virtual void Update(T entityToUpdate)
{
_dbSet.Update(entityToUpdate);
}
But I am getting the following exception: Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException : Database operation expected to affect 1 row(s) but actually affected 0 row(s).
Note that Skill is a ValueObject (it has a shadow Id). I actually believe there should be the problem.
Thanks a lot for your help.
Edit: Since #pejman answer is not working for me I am going to put here my UnitOfWork, Repository and Context (reduced for simplicity), since are the only things I have not done like #pejman, maybe the problem is right there, but I am not able to see it.
Repository:
class BaseRepository<T> : IRepository<T> where T : Entity
{
private readonly Context _context;
private readonly DbSet<T> _dbSet;
public BaseRepository(Context context)
{
_context = context;
_dbSet = context.Set<T>();
}
public virtual T GetById(Guid id) => _dbSet.AsNoTracking().FirstOrDefault(x => x.Id == id);
public virtual void Update(T entityToUpdate)
{
_context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
Unit of work:
public class UnitOfWork : IUnitOfWork
{
private const int SqlObjectAlreadyExistsErrorNumber = 2627;
private readonly Context _dbContext;
private BaseRepository<User> _users;
public UnitOfWork(Context dbContext)
{
_dbContext = dbContext;
}
public IRepository<User> Users
{
get
{
return _users ??= new BaseRepository<User>(_dbContext);
}
}
public void Commit()
{
_dbContext.SaveChanges();
}
}
Context:
public class Context : DbContext
{
public Context(DbContextOptions<DeribeContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(DeribeContext).Assembly);
base.OnModelCreating(modelBuilder);
}
}
Updating collection navigation properties of disconnected entities has very limited support. First off, they are not considered part of the entity data. Second, it's not quite clear what should be done with the existing child entities not found in the disconnected entity.
All this is because EF Core has no information about the original database state, as in their primary supported (and the only reliable) flow of Load (in db context change tracker), Modify, Save.
In theory collections of owned entity types should be handled easier than collection of regular entities. Since they are considered part of the owner (at least when loading), they could have been processed as such during the update, i.e. replace by first deleting all exiting and then insert (add) all incoming.
In practice (current EF Core) updating them in disconnect scenario is virtually impossible. Not only they are not handled automatically, but since you can't define DbSet (hence use Set<T>() method) of owned entity type, there is no other way to tell EF Core to delete existing by actually loading them from the database and clearing the collection. This along with the fact that explicit/lazy loading does not work, leaves the only option of load the owner entity from database.
And once you do that, then better apply the modified properties as well (using CurrentValues.SetValues method), rather than using the "forced update" all which is happening when using Update method (or setting State to EntityState.Modified).
Apply it to your sample model would be something like this
var dbUser = _context.Set<User>().FirstOrDefault(e => e.Id == user.Id);
_context.Entry(dbUser).CurrentValues.SetValues(user);
dbUser.Skills = user.Skills;
Since the only custom code is dbUser.Skills = user.Skills;, it could be generalized for any collection of owned entity types by using EF Core provided metadata and change tracking API:
public virtual void Update(T entityToUpdate)
{
// Load existing entity from database into change tracker
var entity = _dbSet.AsTracking()
.FirstOrDefault(x => x.Id == entityToUpdate.Id);
if (entity is null)
throw new KeyNotFoundException();
var entry = _context.Entry(entity);
// Copy (replace) primitive properties
entry.CurrentValues.SetValues(entityToUpdate);
// Copy (replace) owned collections
var ownedCollections = entry.Collections
.Where(c => c.Metadata.TargetEntityType.IsOwned());
foreach (var collection in ownedCollections)
{
collection.CurrentValue = (IEnumerable)collection.Metadata
.GetGetter().GetClrValue(entityToUpdate);
}
}
Try this, it worked properly:
public class User : Entity
{
private List<Skill> _skills;
public IReadOnlyList<Skill> Skills => _skills.AsReadOnly();// use this way
//for more encapsulation
public void AssignSkills(List<Skill> skills)
{
this._skills = skills;
}
}
and Skill class :
public class Skill
{
public Skill(string name, string desc)
{
this.Name = name;
Description = desc;
}
protected Skill() { }
public string Name { get; private set; }
public string Description { get; private set; }
}
public class Entity
{
public int Id { get; set; }
}
for Mapping:
public class UserMapping : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.Property(a => a.Id).ValueGeneratedNever();
builder.OwnsMany(a => a.Skills, map =>
{
map.ToTable("Skills").HasKey("Id");
map.Property<int>("Id");
map.WithOwner().HasForeignKey("UserId");
map.UsePropertyAccessMode(PropertyAccessMode.Field);
});
}
}
and MyContext:
public class MyDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=users;Integrated Security=true");
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(UserMapping).Assembly);
base.OnModelCreating(modelBuilder);
}
}
and in last which i try to add a list of skills to user then update the list, and it worked properly:
static async Task Main(string[] args)
{
using (var dbContext = new MyDbContext())
{
var user = new User();
var skills = new List<Skill>()
{
new Skill("first skill","desc1"),
new Skill("sec skill","desc2"),
};
user.AssignSkills(skills);
dbContext.Users.Add(user);
dbContext.SaveChanges();
}
using (var readContext = new MyDbContext())
{
var user = readContext.Users.First();
var skills = new List<Skill>()
{
new Skill("first skill","desc1"),
new Skill("sec skill","desc_change"),
new Skill("third skill","desc_New"),
};
user.AssignSkills(skills);
await readContext.SaveChangesAsync();
}

Is there a way of centralizing the access to a DataContext in C# using DI pattern?

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/

Cannot call Injected [through dependency injection] object method

I am trying to call the SendQueuedEmailsToRecipients method from a Maintenance class. My understanding is that because the Maintenance class depends on the SendQueuedEmailsToRecipients method (which is in a different namespace) then I need to DI the method into the Maintenance class. Is this correct? - I am having trouble doing this.
I am unsure if I am going about this the right way. (Still learning Csharp and ASP.NET core). I do not really know where to call up IBulkEmailQueue. Sorry- I am a bit lost here but am trying to understand.
My code is:
root/BLL/MaintenanceBLL/Maintenance.cs
public class Maintenance : IMaintenance
{
private readonly ApplicationDbContext _context;
public Maintenance(ApplicationDbContext Context)
{
_context = Context;
}
//-------------------**MY DI ATTEMPT**
private readonly BulkEmailQueue _bulkEmailQueue;
public Maintenance(BulkEmailQueue BulkEmailQueue)
{
_bulkEmailQueue = BulkEmailQueue;
}
//-------------
public void DoMaintenance()
{
//Parse Maintenance table and action those items
//where Active=True and ActionDate has pass
//==================================
//Retrieve list of rows in Maintenance table
var maintenances = _context.Maintenance;
foreach (Models.Maintenance m in maintenances)
{
switch (m.Subject)
{
case "Send Queued Emails":
if (m.ActionDateTime <= DateTime.Now)
{
//BulkEmailQueue bulkEmailQueue = new BulkEmailQueue();//Create new instance
//BulkEmailQueue.SendQueuedEmailsToRecipients();
_bulkEmailQueue.SendQueuedEmailsToRecipients(); **ERROR DISPLAYS ON THIS LINE**. System.NullReferenceException: 'Object reference not set to an instance of an object.'
m.ActionDateTime = DateTime.Now.AddMinutes(15);
m.LastActionDateTime = DateTime.Now;
}
break;
default:
// code block
break;
}
}
My root/startup contains:
services.AddTransient<IMaintenance, Maintenance>();
services.AddTransient<IBulkEmailQueue, BulkEmailQueue>();
My root/Services/IBulkEmailQueue.cs contains
public interface IBulkEmailQueue
{
public void SendQueuedEmailsToRecipients();
public void DeleteRecord();
public void AddToQueue();
}
It is all fine except that Only one constructor shall be required to inject the dependency IMaintenance and ApplicationDbContext. Also, make sure that ApplicationDbContext is also setup in the startup.cs as done for IMaintenance and IBulkEmailQueue
The MODIFIED code goes as below.
public class Maintenance : IMaintenance
{
private readonly ApplicationDbContext _context;
private readonly IBulkEmailQueue _bulkEmailQueue; // this should be interface
public Maintenance(ApplicationDbContext Context,IBulkEmailQueue BulkEmailQueue)
{
_context = Context;
_bulkEmailQueue = BulkEmailQueue;
}
//-------------------**MY DI ATTEMPT**
public void DoMaintenance()
{
//Parse Maintenance table and action those items
//where Active=True and ActionDate has pass
//==================================
//Retrieve list of rows in Maintenance table
var maintenances = _context.Maintenance;
foreach (Models.Maintenance m in maintenances)
{
switch (m.Subject)
{
case "Send Queued Emails":
if (m.ActionDateTime <= DateTime.Now)
{
_bulkEmailQueue.SendQueuedEmailsToRecipients(); **NO ERROR SHALL DISPLAY ON THIS LINE**.
m.ActionDateTime = DateTime.Now.AddMinutes(15);
m.LastActionDateTime = DateTime.Now;
}
break;
default:
// code block
break;
}
}

How should I setup my repositories to use the same context?

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.

EF 4.1 / Repository / UnitOfWork / MySQL : There is already an open DataReader associated with this Connection which must be closed first

I read all the similar titles questions, but i didn't find the answer to my problem, so I open a new question:
I have two mysql tables:
tb1 (int_id, code, description, ..., my_id);
tb2 (int_id, code, description, ..., tb1_id);
I create a generic repository to manage DbContext and GetAll, GetById, GetByLambda, Insert, Update and Delete methode.
namespace Model.DataAccessLayer
{
public class Repository<T> : IRepository<T> where T : EntityObject, IEntity
{
protected DbContext dbContext = null;
public virtual DbContext DbContext
{
get { return dbContext; }
set { dbContext = value; }
}
public ObjectContext ObjectContext
{
get { return ((IObjectContextAdapter)DbContext).ObjectContext; }
}
public void Dispose()
{
ObjectContext.Dispose();
System.GC.SuppressFinalize(this);
}
public virtual IQueryable<T> GetAll()
{
return ObjectContext.CreateObjectSet<T>();
}
public virtual IEnumerable<T> GetByLambda(Func<T, bool> p)
{
return GetAll().Where(p);
}
public virtual void Save()
{
DbContext.SaveChanges();
}
...
}
}
and the inherited class which I used:
namespace Model.DataAccessLayer
{
public class RepositoryP<T> : Repository<T> where T : EntityObject, IEntity
{
public myEntities MyContext
{
get { return (myEntities)ObjectContext; }
//set { ObjectContext = value; }
}
public override DbContext DbContext
{
get
{
if (dbContext == null)
{
dbContext = new DbContext("myEntities");
}
return dbContext;
}
set
{
base.DbContext = value;
}
}
}
}
It works great when using only one table.
When I try to use my two tables and the foreign key relation between them, it doesn't work.
For example I try to get all records from table tb2 where tb1.my_id=5 with the following join tb1.int_id = tb2.tb1_id.
List<tb2> lj = new Tb2DAO().GetByLambda(l => l.tb1.my_id == 5).ToList();
(Tb2DAO inherited from my generic repository class.)
I have the following MySQL error:
"MySql.Data.MySqlClient.MySqlException: There is already an open DataReader associated with this Connection which must be closed first."
I think this comes from my DbContext which is not common to my two tables entities.
So I tried to implement the UnitOfWork Pattern to solve this problem, like this:
namespace Model.DataAccessLayer
{
public class UnitOfWork : IDisposable
{
private DbContext dbContextUnit = null; //= new DbContext();
private Tb1DAO tb1DAO;
private Tb2DAO tb2DAO;
public Tb1DAO tb1
{
get
{
if (this.tb1DAO == null)
{
if (dbContextUnit != null)
{
this.tb1DAO = new Tb1DAO { DbContext = dbContextUnit };
}
else
{
this.tb1DAO = new Tb1DAO();
dbContextUnit = this.tb1DAO.DbContext;
}
}
return tb1DAO;
}
}
public Tb2DAO tb2
{
get
{
if (this.tb2DAO == null)
{
if (dbContextUnit != null)
{
this.tb2DAO = new Tb2DAO { DbContext = dbContextUnit };
}
else
{
this.tb2DAO = new Tb2DAO();
dbContextUnit = this.tb2DAO.DbContext;
}
}
return tb2DAO;
}
}
public void Save()
{
dbContextUnit.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
dbContextUnit.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
And now in my code I tried to use the Unit of Work like this :
UnitOfWork unitOfWork = new UnitOfWork();
List<tb2> tb2List = unitOfWork.tb2.GetByLambda(l => l.index_job.job_id == job_id).ToList();
But I have always the same error message :
"MySql.Data.MySqlClient.MySqlException: There is already an open DataReader associated with this Connection which must be closed first."
is there something I am doing wrong ? Please can you help me ? This notion of repository and unit Of work are new for me
and I am confused else I read lot of think about it may be not the right one...
I also to try to add MultipleActiveResultSets=true to my connection but it is not recognized.
many thank to all & regards,
wst
Your GetByLambda() method is calling GetAll() which creates a new context using ObjectContext.CreateObjectSet<T>(). So you now have more than one context open. I would advise using the standard EF associations and the repository pattern, then you can avoid this entire mess. Here is a link that may help you get started - http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-code-first-walkthrough.aspx.
I'm not sure about this method
public virtual IQueryable<T> GetAll()
{
return ObjectContext.CreateObjectSet<T>();
}
It's create new object set about which EF context doesn't know.
You can try to pass Func<ObjectContext, ObjectResult<T>> from derived class to base. (It should return ObjectResult which are got from EF context. F.e. context => context.Entities).

Categories