I'm trying to implement the repository pattern using entity framework code first rc 1. The problem I am running into is with creating the DbContext. I have an ioc container resolving the IRepository and it has a contextprovider which just news up a new DbContext with a connection string in a windsor.config file. With linq2sql this part was no problem but EF seems to be choking. I'll describe the problem below with an example. I've pulled out the code to simplify things a bit so that is why you don't see any repository pattern stuff here. just sorta what is happening without all the extra code and classes.
using (var context = new PlssContext())
{
var x = context.Set<User>();
var y = x.Where(u => u.UserName == LogOnModel.UserName).FirstOrDefault();
}
using (var context2 = new DbContext(#"Data Source=.\SQLEXPRESS;Initial Catalog=PLSS.Models.PlssContext;Integrated Security=True;MultipleActiveResultSets=True"))
{
var x = context2.Set<User>();
var y = x.Where(u => u.UserName == LogOnModel.UserName).FirstOrDefault();
}
PlssContext is where I am creating my DbContext class. The repository pattern doesn't know anything about PlssContext. The best I thought I could do was create a DbContext with the connection string to the sqlexpress database and query the data that way. The connection string in the var context2 was grabbed from the context after newing up the PlssContext object. So they are pointing at the same sqlexpress database.
The first query works. The second query fails miserably with this error:
The model backing the 'DbContext'
context has changed since the database
was created. Either manually
delete/update the database, or call
Database.SetInitializer with an
IDatabaseInitializer instance. For
example, the
DropCreateDatabaseIfModelChanges
strategy will automatically delete and
recreate the database, and optionally
seed it with new data.
on this line
var y = x.Where(u => u.UserName == LogOnModel.UserName).FirstOrDefault();
Here is my DbContext
namespace PLSS.Models
{
public class PlssContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Corner> Corners { get; set; }
public DbSet<Lookup_County> Lookup_County { get; set; }
public DbSet<Lookup_Accuracy> Lookup_Accuracy { get; set; }
public DbSet<Lookup_MonumentStatus> Lookup_MonumentStatus { get; set; }
public DbSet<Lookup_CoordinateSystem> Lookup_CoordinateSystem { get; set; }
public class Initializer : DropCreateDatabaseAlways<PlssContext>
{
protected override void Seed(PlssContext context)
{
I've tried all of the Initializer strategies with the same errors. I don't think the database is changing. If I remove the
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
Then the error returns is
The entity type User is not part of the model for the current context.
Which sort of makes sense. But how do you bring this all together?
That is correct behavior. Plain DbContext has no idea about mappings (= doesn't know any of your entities). That is the reason why you should always create derived context. Your repository doesn't know about PlssContext but you can still inject it like:
public class Repository
{
private readonly DbContext _context;
public Repository(DbContext context)
{
_context = context;
}
...
}
var repository = new Repository(new PlssContext());
You can't use base DbContext instance directly when using code first.
Related
Futher into my studies on MVVM I found an issue I can't understand, and I couldn't find (rather, I think I couldn't word my Google searches correctly) information specific to this situation:
I have the following entities:
public class Sale : EntityBase
{
public ICollection<SaleItem> SaleItems {get;set;}
}
public class SaleItem : EntityBase
{
public Sale Sale {get;set;}
public Stock Stock {get;set;}
public TaxType TaxType {get;set;}
}
public class Stock : EntityBase
{
public TaxType TaxType {get;set;}
public ICollection<Sale> SaleStocks {get;set;}
}
public class EntityBase
{
[Key]
public int ID {get;set;}
}
public TaxType
{
[Key]
public int TaxCode {get;set;}
public string Description {get;set;
}
TaxType is seeded by the database migrations. I'm using MySQL.
From what I've read on what is the Alternate for AddorUpdate method in EF Core?, to record a new entry on my database, I should just call _context.Update(Sale) and _context.SaveChangesAsync().
However I still can't understand what am I doing wrong on a simple CRUD:
User is directed to a viewModel:
SaleViewModel.cs
private SaleItemStore _saleItemStore;
public Sale Sale {get;set;} = new();
public ObservableCollection<SaleItem> SaleItems {get;set;} = new();
public SaleViewModel(IServiceProvider serviceProvider)
{
_saleItemStore = serviceProvider.GetRequiredService<SaleItemStore>();
SaveSale = new SaveSaleCommand(this, serviceProvider);
}
public class SaveSaleCommand()
{
public SaveSaleCommand(SaleViewModel saleViewModel, IServiceProvidere serviceProvider)
{
_parentViewModel = saleViewModel;
_saleDataService = serviceProvider.GetRequiredService<SalaDataService>();
}
public Execute()
{
foreach (SaleItem saleItem in SaleItems)
{
Sale.SaleItems.Add(saleItem);
}
_saleDataService.AddOrUpdate(Sale sale);
}
}
On a different viewModel, the user can select, among other properties, the TaxType, from a dropdown combobox. The combobox's ItemsSource is bound to TaxTypesList like:
SelectStockToSaleItemViewModel
private TaxTypeDataService _taxTypeDataService;
public SaleItem SaleItem {get;set;} = new();
public TaxType SelectedTaxType {get;set;}
public ObservableCollection<TaxType> TaxTypesList {get;} = new();
public SelectStockToSaleItemViewModel(IServiceProvider serviceProvider)
{
_taxTypeDataService = serviceProvider.GetRequiredService<TaxTypeDataService>();
SendSaleItemBack = new SendSaleItemBackCommand(this, serviceProvider);
Task.Run(FillLists);
}
public async Task FillLists()
{
foreach (TaxType taxType in await _taxTypeDataService.GetAllAsNoTracking())
{
TaxTypesList.Add(taxType);
}
}
public SendSaleItemBackCommand()
{
private SaleItemStore _saleItemStore;
public SendSaleItemBackCommand(SelectStockToSaleItemViewModel selectStockToSaleItemViewModel, IServiceProvider serviceProvider)
{
_parentViewModel = selectStockToSaleItemViewModel;
_saleItemStore = serviceProvider.GetRequiredService<SaleItemStore>();
}
public Execute()
{
_saleItemStore.Message = new SaleItem()
{
TaxType = _parentViewModel.SelectedTaxType
}
//Takes user back to previous ViewModel;
}
}
So, my idea is that on SelectStockToSaleItemViewModelthe user selects a TaxType from a combobox filled with _taxTypeDataService.GetAllAsNoTracking(), and when they execute SendSaleItemBackCommand, SaleItemStore has its property set to a SaleItem with the TaxType not null;
Afterwards, should the user add more SaleItems to Sale, they'll have to open SelectStockToSaleItemViewModel again, and do the process for each extra SaleItem they wand to add to Sale.
Finally, they will have to execute SaveSaleCommand, and I'd end up with a Sale and its associated SaleItems on my database.
My dataServices are:
public class SaleDataService
{
private MyDbContext _context;
public SaleDataService(IServiceProvider serviceProvider)
{
_context = serviceProvider.GetRequiredService<MyDbContext>();
}
public async Task<Sale> AddOrUpdate(Sale sale)
{
_context.Update(sale);
await _context.SaveChangesAsync();
return sale;
}
}
public class TaxTypeDataService
{
private MyDbContext _contex;
public TaxTypeDataService(IServiceProvider serviceProvider0
{
_context = serviceProvider.GetRequiredService<MyDbContext>();
}
public async Task<List<TaxType>> GetAllAsNoTracking()
{
return _context.Set<TaxType>().AsNoTracking().ToListAsync();
}
}
public class Messaging<TObject> : IMessaging<TObject>
{
public TObject Message { get; set; }
}
SaleItemStore is singleton. SelectStockToSaleItemViewModel, and SelectStockToSaleItemViewModel are transient; TaxTypeDataService and SaleDataService are scoped; As for the context,
public static IHostBuilder AddDbContext(this IHostBuilder host)
{
host.ConfigureServices((_, services) =>
{
string connString = $"MyConnString";
ServerVersion version = new MySqlServerVersion(new Version(8, 0, 23));
Action <DbContextOptionsBuilder> configureDbContext = c =>
{
c.UseMySql(connString, version, x =>
{
x.CommandTimeout(600);
x.EnableRetryOnFailure(3);
});
c.EnableSensitiveDataLogging();
};
services.AddSingleton(new AmbiStoreDbContextFactory(configureDbContext));
services.AddDbContext<AmbiStoreDbContext>(configureDbContext);
});
return host;
}
The issue happens when the user tries to save a Sale with two or more SaleItems using the same TaxType, as EF Core throws an "another instance of TaxType is already being tracked". I understand the TaxType starts being tracked when the first Sale.SaleItem is updated, but how do I deal with this issue?
According to The instance of entity type 'Product' cannot be tracked because another instance with the same key value is already being tracked and The instance of entity type cannot be tracked because another instance of this type with the same key is already being tracked I did fill the combobox with TaxType using an AsNoTracking call, but I don't think it applies to my situation. Also, they say to "flush" the tracked entries before updating by setting the context's tracked entries' states to Detached, but, again, TaxType starts being tracked while being saved. I've checked the context's tracked entries collection, and there is no mention of TaxTypebeing tracked at all before Update(Sale) is called.
The only way I can think of as a workaround is using Fluent API and setting the foreign key, rather than the property itself (i.e. TaxTypeId = SelectedTaxType.ID), which doesn't look like the best way to deal with this.
Alright, I read some more about EF Core and I think I understood "the recommended way".
Whenever working with data services and dependency injection, the entities must be loaded by the same context that will be using them to add or update the context's database.
My mistake was loading the collections used by the interface (comboboxes, drop downs and checkboxes) using AsNoTracking. What happened was that whenever I tried to add or update two entities that used the same AsNoTracking-obtained entity, EF Core (as it should) tried to add the entity to the context's change tracker. So when the second entity tried to add/update, it threw a The instance of entity type cannot be tracked because another instance of this type with the same key is already being tracked exception.
So the "correct flow" is to load a collection/list using the same context that will be using them. Either using a singleton dbContext for the whole application and not using AsNoTracking, which is not recommended due to the possibility of ending up with concurrent operations; or using scoped dbContext for each "unit-of-work", i.e. a single dialog to add a new record, or an updating window with a record pulled from the same instance of the context that will be saving it, and disposing of the context (and it's change tracker) after whatever you wanted to do has been dealt with.
One of the big mistakes I was making when using scoped contexts was creating one context to load a datagrid with entries, which, when double-clicked, would open a dialog (with a new scoped context) to edit it without releasing that record from the previous context, throwing the already being tracked exception.
In my solution I have a Domain project and a DataAccess project. My Domain project contains domain objects and interfaces for various repositories and an interface for a UnitOfWork which will contain all my repositories. My DataAccess project contains the implementations of all these interfaces and the EF versions of my Domain objects.
So if I have a class called Person and I want to add a Person to the DB it would look like this:
unitOfWork.People.Add(person);
unitOfWork.Complete();
Inside unitOfWork.People.Add(person) I map my domain Person object to my EF Person object with AutoMapper. Then I add my EF Person object to the EF DbContext object.
public void Add(Person person) {
DataAccess.Entities.Person dbPerson = Mapper.Map<DataAccess.Entities.Person>(person);
dbContext.People.Add(dbPerson);
}
The Complete() function just wraps SaveChanges().
public int Complete() {
dbContext.SaveChanges();
}
Normally EF updates an inserted object's DB generated ID after insertion. But in this case EF knows nothing about my Domain Person object.
Since my Domain Person object doesn't have it's ID, I can't update the DB entry or re-fetch it from the database.
The only alternative I can think of is generating a guid on the server and using that as an ID. But this creates redundancy in the form of two IDs. It also isn't very user friendly since users will reference this ID in search fields. Remembering ID 135 is much simpler than remembering a guid.
Is there some way to get dbContext to update the domain object's ID directly or alternatively have the ID bubble up through the layers?
You can try something like this.
Repository
public class GenericRepository<TEntity> where TEntity : class
{
internal DbContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(DbContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual TEntity Insert(TEntity entity)
{
dbSet.Add(entity);
return entity;
}
}
Use it like this
var uow = new UnitOfWork();
var added = uow.StudentReposiotry.Insert(new Student
{
Name = "Repository",
RegistrationNo = "BSE-2018-004",
Date = DateTime.Now,
Department = "Philosphy",
Email = "test#"
});
uow.Save(); //if its saved.
long id = added.Id;
I added a separate layer called DataProvider which help to keep my entities live only in Repository everything came from DataProvider speaks about DTO.
public class StudentDataProvider : DataProviderBase
{
public StudentDto Insert(Student dto)
{
var entity = new Student{ Name = dto.Name,Deparment= dto.Deparment};
var addedItem = unitofwork.StudentReposiotry.Insert(entity);
unitofWork.Save();
dto.Id = addedItem.Id;
return dto;
}
public StudentDto AddAndUpdate(StudentDto dto, StudentDto updateDto)
{
var entity = new Student{ Name = dto.Name,Deparment= dto.Deparment};
var update= new Student{ Name = updateDto.Name,Deparment= updateDto.Deparment};
var addedItem = unitofwork.StudentReposiotry.Insert(entity);
var updateItem= unitofwork.StudentReposiotry.Update(update)
unitofWork.Save();
dto.Id = addedItem.Id;
return dto;
}
}
public class DataProvideBase
{
protected IUnitOfWork UnitOfWork;
public DataProvideBase()
{
UnitOfWork = new UnitOfWork();
}
}
You can check my anser in another similar question, since UnitofWork is intimatelly related to Repository pattern.
Hope it helps:
How to get id from Add using UnitOfWork pattern?
I have a situation, which I will try to greatly simplify, in which a method on an object should create a new persistent object of a different class. How can I do this while maintaining repository ignorance and encapsulation at the same time?
In this example, we have widgets and containers of widgets. When a widget is sold or purchased, a WidgetEvent is created (somewhere else) and added to the list of WidgetEvents for the container. We can always query the container for the number of pieces on hand by summing the collection of WidgetEvents.At some point, a worker calls and says the container is empty even though there should still be some widgets in the container. In that case, we call the "SetComplete" method which creates a final WidgetEvent to zero out the container.
public class TestContext : DbContext
{
public DbSet<WidgetEvent> WidgetEvents { get; set; }
public DbSet<WidgetContainer> WidgetContainers { get; set; }
}
public class WidgetEvent
{
public int Id { get; set; }
public int Amount {get;set;}
public WidgetContainer Container {get;set;}
}
public class WidgetContainer
{
public int Id { get; set; }
public virtual ICollection<WidgetEvent> WidgetEvents {get;set;}
public int GetPiecesOnHand()
{
return WidgetEvents.Sum(a=> a.Amount);
}
public void SetComplete()
{
if (GetPiecesOnHand() != 0)
{
WidgetEvents.Add(new WidgetEvent() { Amount = -GetPiecesOnHand() });
}
}
}
It seems to me that proper encapsulation would keep the creation of this event within the class definition. It makes it more understandable to someone reading the code. But I also see there is no way to make this new WidgetEvent persistent without introducing some repository knowledge into the class.
How should I do this? You can assume that I have a Factory for the WidgetEvents somewhere else. In case you were wondering, SaveChanges does not realize that the WidgetEvent has been created.
Edit: This returns
25
25
0
0
which is correct. I guess this leaves me a bit confused about track changes, but its good to know this works. I did something like this a couple of days ago and thought it did not work.
using (var context = new TestContext())
{
WidgetContainer acontainer = new WidgetContainer();
acontainer.WidgetEvents = new List<WidgetEvent>();
context.WidgetContainers.Add(acontainer);
acontainer.WidgetEvents.Add(new WidgetEvent() { Container = acontainer, Amount = 25 });
Console.WriteLine(acontainer.GetPiecesOnHand());
context.SaveChanges();
}
using (var context = new TestContext())
{
WidgetContainer acontainer = context.WidgetContainers.Find(1);
Console.WriteLine(acontainer.GetPiecesOnHand());
acontainer.SetComplete();
Console.WriteLine(acontainer.GetPiecesOnHand());
context.SaveChanges();
}
using (var context = new TestContext())
{
WidgetContainer acontainer = context.WidgetContainers.Find(1);
Console.WriteLine(acontainer.GetPiecesOnHand());
}
It should actually work if the WidgetContainer is attached to (=tracked by) the Entity Framework context without giving the class a reference to a context (or a repository).
For example a code like this...
var widgetContainer = context.WidgetContainers.Find(1);
// or:
// var widgetContainer = context.WidgetContainers.Create();
// widgetContainer.Id = 1;
// context.WidgetContainers.Attach(widgetContainer);
widgetContainer.SetComplete();
context.SaveChanges();
...should insert the new WidgetEvent into the database with a foreign key relationship to the widgetContainer because EF change tracking will recognize the new event that has been added to the WidgetEvents collection when SaveChanges is called.
The important piece is that the widgetContainer must be attached to the context before SetComplete is called.
The exception I am receiving is An entity object cannot be referenced by multiple instances of IEntityChangeTracker. My code is structured like so...
My context class looks like this:
public class MyContext : DbContext, IDataContext
{
public MyContext (string connectionString) :
base(connectionString)
{
}
public DbSet<AssigneeModel> Assignees { get; set; }
public DbSet<AssetAssignmentModel> AssetAssignments { get; set; }
}
public class AssigneeController : Controller
{
protected MyContext db = new MyContext(ConnectionString);
[HttpPost]
public ActionResult Import(SomeObjectType file)
{
AssigneeModel assignee = new AssigneeModel();
assignee.FirstName = "Joe";
assignee.LastName = "Smith";
// Assignees have assets, and the relationship is established via an AssetAssignmentModel entity
AssetAssignmentModel assetAssignmentModel = new AssetAssignmentModel
{
Asset = someExistingAsset,
// Assignee = assignee, // Don't establish relationship here, this object will be added to the assignee collection
}
assignee.AssetAssignments.Add(assetAssignmentModel); // Manually add object to establish relationship
db.Assignees.Add(assignee); // Add the assignee object
// Exception occurs when adding the object above
};
}
EF Version 4.1
The problem is from your Asset object, when you're getting it from the other method, you'll need to explicitly detach it from that context, before adding it to this new context. As Julie mentioned, the entity instance will carry the context with it, but the porblem wasn't with the AssigneeModel you created, but with the someExistingAsset you retrieved.
You've tagged this as EF4.1 (where I expected code first & dbcontext) but it looks like a side effect of EntityObject (edmx, objectcontext, default code gen in VS2008 & VS2010).
In that case, if you have an entity (that derives from EntityObject) and you dispose its' context without first detaching the entity, the entity instance still has an artifact of that context. So when you try to attach it to another context, it gives this message. THat was a problem with EF 3.5 and EF4 if you aren't using POCOs. I haven't had to wrestle with it in a long time but I remember the sting. :)
I am using Entity Framework 4.3 and I am trying to reference an existing entity by setting the navigation property when creating a new entity, however when i call save EF complains that there is a PK violation in the table for which i set the navigation property to (i.e. it is creating a new record as opposed to a reference!).
How can i attach to an existing POCO as opposed to referencing it and having EF trying to create a new database record (but not simply use an ID, ideally i would like to reference an actual entity that came from another query)?
Thanks in advance,
Chris
public class BusinessUnit
{
public int BusinessUnitID { get; set; }
public ExternalPlugin AccountsDataSourceModule { get; set; }
public ExternalPlugin OptionalContactsDataSourceModule { get; set; }
}
public BusinessUnit NewBusinessUnit(string name, ExternalPlugin accountsModuleId = null, ExternalPlugin contactsModuleId = null)
{
IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork();
BusinessUnit unit = new BusinessUnit();
unit.CompanyName = name;
unit .AccountsDataSourceModule = accountsModuleId; // this causes a problem
unit .OptionalContactsDataSourceModule = contactsModuleId; // as does this
unitOfWork.BusinessUnitRepository.Insert(unit);
unitOfWork.Save();
return unit;
}
You must attach the existing entities to the context:
BusinessUnit unit = new BusinessUnit();
unit.CompanyName = name;
unitOfWork.ExternalPluginRepository.Attach(accountsModuleId);
unitOfWork.ExternalPluginRepository.Attach(contactsModuleId);
unit.AccountsDataSourceModule = accountsModuleId;
unit.OptionalContactsDataSourceModule = contactsModuleId;
unitOfWork.BusinessUnitRepository.Insert(unit);
...where unitOfWork.ExternalPluginRepository.Attach(ExternalPlugin plugin) must do:
context.ExternalPlugins.Attach(plugin);
I expect that all repositories use the same context instance. Attach tells EF that the plugins already exist in the database and avoids an INSERT of those entities.
Edit
If you get the error message...
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker.
...it means that you have an entity that is attached to more than one context instance at the same time. You can avoid that in most cases by disposing the context always when you don't need it anymore. Your code sample does not follow this good practice. It rather should look like this:
public BusinessUnit NewBusinessUnit(string name,
ExternalPlugin accountsModuleId = null,
ExternalPlugin contactsModuleId = null)
{
using (IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
{
BusinessUnit unit = new BusinessUnit();
unit.CompanyName = name;
unitOfWork.ExternalPluginRepository.Attach(accountsModuleId);
unitOfWork.ExternalPluginRepository.Attach(contactsModuleId);
unit.AccountsDataSourceModule = accountsModuleId;
unit.OptionalContactsDataSourceModule = contactsModuleId;
unitOfWork.BusinessUnitRepository.Insert(unit);
unitOfWork.Save();
return unit;
}
}
At the end of the using block the Dispose method of the unitOfWork is called automatically. To get this working (and compiling as well) you need to derive your IUnitOfWork interface from IDisposable and implement it in the concrete UnitOfWork class:
public interface IUnitOfWork : IDisposable
{
// ...
}
public class ConcreteUnitOfWork : IUnitOfWork, IDisposable
{
private MyDbContext _context;
// I assume that you have a member for the DbContext in this class
// ...
// implementation of IDisposable
public void Dispose()
{
if (_context != null)
_context.Dispose();
}
}