I have implemented repository pattern and unit of work on top of Data Access Layer. I have all the crud operation in Generic Repository but save method in unit of work. In my business class I am passing object to generic class, followed by Save method in unit of work, my question is how can i get scope ID from this point forward
Basically I need to get ID of object after saving where I don't know what is object ID name as I am using generic class to save data
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
protected DbSet<TEntity> _DbSet;
private readonly DbContext _dbContext;
public GenericRepository()
{ }
public GenericRepository(DbContext dbContext)
{
this._dbContext = dbContext;
_DbSet = _dbContext.Set<TEntity>();
}
public void InsertEntity(TEntity obj)
{
_DbSet.Add(obj);
}
}
...
public class FunctionsNavigation_UnitOfWork : IDisposable
{
private FunctionContext _FunctionContext = new FunctionContext();
uow.Qualification_FeeSchemeRepository.InsertEntity(obj);
}
}
public void Save()
{
_FunctionContext.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
_FunctionContext.SaveChanges();
}
in following business class after saving I try to get ID on object but I am not getting any thing
_uof.Sys_Nav_Functions_Repository.InsertEntity(_Sys_Nav_FunctionEntity);
_uof.Save();
_FunctionID = _obj.Sys_Nav_Function.Function_ID;
You will get the inserted Id at the object itself.
private FunctionContext _FunctionContext = new FunctionContext();
var obj = new yourEntity();
uow.Qualification_FeeSchemeRepository.InsertEntity(obj);
uow.Save();
Once you save the data into the database. The entity-framework will fill the entity type PK that is generated from the database.
In short you get here
int id = obj.Id;
Update Inserting 1:1 Relationship Sample
Person person = new Person//create person entity
{
FirstName = "Eldho",
LastName = "Abe",
};
AuthorizedUser user = new AuthorizedUser//create authorized user role entity
{
Person = person, //The reference of newly inserted user
UserId = myUserid,
HashedPassword = password,
};
uow.PersonDA.Insert(person);
uow.AuthorizedUserDA.Insert(user);
uow.Save();//insert to database
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.
I want to unit-test the retrieval methods from a repository which has a mocked DbContext, but I am not able to set the mocked DbSet values to the repository.
The Repository looks like this:
public class ChangeLogRepository : Repository<ChangeLog>, IChangeLogRepository
{
public ChangeLogRepository(IDbContext context, long tenantId) : base(context, tenantId)
{
}
}
The base class:
public class Repository<TEntity> where TEntity : class {
protected readonly IDbContext Context;
protected DbSet<TEntity> Entities { get; set; }
public long TenantId { get; set; }
protected Repository(IDbContext context, long tenant)
{
Context = context;
TenantId = tenant;
Entities = Context.Set<TEntity>();
}
public List<TEntity> GetAll()
{
return Entities.ToList();
}
//..
}
Last but not least, the test class:
[TestClass]
public class ChangeLogRepository_Test
{
private ChangeLogRepository repository;
private List<ChangeLog> allTestData;
[TestInitialize]
public void TestInitialize()
{
var dbContext = new Mock<IDbContext>();
allTestData = new List<ChangeLog>() {
new ChangeLog { Id = 10, EntityName = "User",PropertyName = "UserName",PrimaryKeyValue = 1,OldValue = "Max",NewValue = "Moritz",DateChanged = DateTime.Now,FieldType = ChangeLogFieldType.Default },
new ChangeLog { Id = 10, EntityName = "User",PropertyName = "CreatedAt",PrimaryKeyValue =2,OldValue = "15/06/2017",NewValue = "15/06/2017",DateChanged = DateTime.Now,FieldType = ChangeLogFieldType.Date },
new ChangeLog { Id = 10, EntityName = "Role",PropertyName = "RoleName",PrimaryKeyValue = 56,OldValue = "Admin",NewValue = "Administrator",DateChanged = DateTime.Now,FieldType = ChangeLogFieldType.Default },
};
var changelogs = MockDbSet(allTestData);
dbContext.Setup(m => m.Set<ChangeLog>()).Returns(() => changelogs);
repository = new ChangeLogRepository(dbContext.Object, 10);
}
[TestMethod]
public void Setup_Test()
{
Assert.AreEqual(repository.GetAll(), allTestData);
}
private static DbSet<T> MockDbSet<T>(IEnumerable<T> list) where T : class, new()
{
IQueryable<T> queryableList = list.AsQueryable();
Mock<DbSet<T>> dbSetMock = new Mock<DbSet<T>>();
dbSetMock.As<IQueryable<T>>().Setup(x => x.Provider).Returns(queryableList.Provider);
dbSetMock.As<IQueryable<T>>().Setup(x => x.Expression).Returns(queryableList.Expression);
dbSetMock.As<IQueryable<T>>().Setup(x => x.ElementType).Returns(queryableList.ElementType);
dbSetMock.As<IQueryable<T>>().Setup(x => x.GetEnumerator()).Returns(queryableList.GetEnumerator());
return dbSetMock.Object;
}
}
If I run it, the test fails, because the getAll() method retuns null. It seams, that the property 'Entities' wasn't correctly initialized by the mocked Set() method.
When I set a breakpoint in the repository-constructor
and examine the Entities property, under 'Expression > Value > Result View' the three Entries appear. Under the first Result View there are one "Enumeration yielded no results" message and two rows with ? in it (Visual Studio 2017).
How can I mock the entities in the repository correctly? What am I doing wrong?
I recreated the test based exactly on the example provided in the original question and was unable to reproduce the null issue. The mock returned a populated collection just as it was configured to do.
A problem occurred however when comparing the two collections,
Assert.AreEqual(repository.GetAll(), allTestData);
they were not considered equal. Expected, as the ToList would be creating a new list which would obviously be a different reference to the original list used as the data source for the mock.
Compare the two collections using CollectionAssert.AreEquivalent instead
[TestMethod]
public void Setup_Test() {
var actual = repository.GetAll();
CollectionAssert.AreEquivalent(allTestData, actual);
}
and the test passes.
The EF documentation covers this.
Use IDbSet<T> in your context.
Create a fake implementation of IDbSet<T> for your tests.
See https://msdn.microsoft.com/en-gb/data/dn314431.
Note there is also a page on using mocking frameworks but I've never done that: https://msdn.microsoft.com/en-gb/data/dn314429.
Note also, if using EF Core (aka EF7) there is an in memory provider avoiding needing doubles at all.
I'm trying to build a generic repository with Dapper. However, I have some difficulties to implement the CRUD-operations.
Here is some code from the repository:
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
internal IDbConnection Connection
{
get
{
return new SqlConnection(ConfigurationManager.ConnectionStrings["SoundyDB"].ConnectionString);
}
}
public GenericRepository(string tableName)
{
_tableName = tableName;
}
public void Delete(TEntity entity)
{
using (IDbConnection cn = Connection)
{
cn.Open();
cn.Execute("DELETE FROM " + _tableName + " WHERE Id=#ID", new { ID = entity.Id });
}
}
}
As you can see, my delete-method takes a TEntity as parameter which is a parameter of type class.
I call my Delete-method from my UserRepository like this:
public class UserRepository : GenericRepository<User>, IUserRepository
{
private readonly IConnectionFactory _connectionFactory;
public UserRepository(IConnectionFactory connectionFactory) : base("User")
{
_connectionFactory = connectionFactory;
}
public async Task<User> Delete(User model)
{
var result = await Delete(model);
return result;
}
}
The thing is that I can't write entity.Id in my Delete-opration in my generic repository. I get a error. So how can I easily implement CRUD-operations like this?
Here is the error message:
TEntity does not contain a definition of "Id" and no extension method "Id" accepting a argument of type "TEntity" could be found
Define an interface like so.
public interface ITypeWithId {
int Id {get;}
}
And make sure your User type implements that interface.
Now apply it to your class as a generic constraint.
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, ITypeWithId
If you have types that are stored in the repository but DO Not have an Id property then make your delete type constraint specific to the method and not the class. This will allow you to still use the same repository type even with types that might key on something else like a string or a compound (multi) key.
public void Delete<T>(T entity) where T : class, ITypeWithId
{
using (IDbConnection cn = Connection)
{
cn.Open();
cn.Execute("DELETE FROM " + _tableName + " WHERE Id=#ID", new { ID = entity.Id });
}
}
Please don't do this! Your generic repository adds more confusion than value. It's fragile code (string literals for _tableName, invalid cast errors on the id parameter), and introduces a gaping security hole (sql injection via _tableName). If you've chosen Dapper, it's because you want to be in control of your sql, so it makes no sense to generate the sql you send to Dapper.
you have to define an interface like below
public interface IIdentityEntity
{
public int Id { get; set;}
}
all your entities which want to use the class, must implement the IIdentityEntity.
and the first line should be changed to the following
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class,IIdentityEntity
and what was the problem is that you only described the TEntity as class and class does not have an Id in its description so you have to notify compiler that the Generic type implemented an Interface that holds an Id field inside it
In case it helps, I've just published a library Harbin.DataAccess which implements Generic Repositories (Generic Repository Pattern) using "raw" Dapper, Dapper.FastCRUD, and DapperQueryBuilder:
The Inserts/Updates/Deletes are automatically generated by Dapper FastCRUD (class should be decorated with attributes for keys/autoincrement columns)
Supports FastCRUD bulk update, bulk delete, and async methods.
Repositories can be extended with custom Queries and custom Commands (allows/promotes CQRS separation)
Queries can be defined manually (raw sql) or using Dapper FastCRUD syntax
Dynamic Queries (dynamic number of conditions) can be built using DapperQueryBuilder
There are Read-only Connection Wrappers and Read-only Repositories, so it's easy to use read-replicas (or multiple databases)
Support for ADO.NET transactions
Support for mocking Queries and Commands
Sample Insert/Update/Delete (Generic Repository - this uses Dapper FastCRUD):
var conn = new ReadWriteDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));
// Get a IReadWriteRepository<TEntity> which offers some helpers to Query and Write our table:
var repo = conn.GetReadWriteRepository<ContactType>();
var contactType = repo.QueryAll().First();
// Updating a record
contactType.ModifiedDate = DateTime.Now;
repo.Update(contactType);
// Adding a new record
var newContactType = new ContactType() { Name = "NewType", ModifiedDate = DateTime.Now };
repo.Insert(newContactType);
// FastCRUD will automatically update the auto-generated columns back (identity or guid)
// Deleting a record
repo.Delete(newContactType);
[Table("ContactType", Schema = "Person")]
public class ContactType
{
[Key] // if column is part of primary key
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // if column is auto-increment
public int ContactTypeId { get; set; }
public DateTime ModifiedDate { get; set; }
public string Name { get; set; }
}
Sample Dynamic Queries:
var conn = new ReadDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));
// Get a IReadRepository<TEntity> which offers some helpers to Query our table:
var repo = conn.GetReadRepository<Person>();
// Custom Query (pure Dapper)
var people = repo.Query("SELECT * FROM Person.Person WHERE PersonType = #personType ", new { personType = "EM" } );
// DapperQueryBuilder allows to dynamically append conditions using string interpolation (but injection-safe)
string type = "EM"; string search = "%Sales%";
var dynamicQuery = repo.QueryBuilder(); // if not specified query is initialized with "SELECT * FROM tablename"
dynamicQuery.Where($"PersonType = {type}");
dynamicQuery.Where($"ModifiedDate >= {DateTime.Now.AddDays(-1)} ");
dynamicQuery.Where($"Name LIKE {search}");
// Result is SELECT * FROM [Person].[Person] WHERE PersonType = #p0 AND ModifiedDate >= #p1 AND Name LIKE #p2
var people = dynamicQuery.Query();
Extending Repositories (adding custom Queries and Commands) using Inheritance:
public class PersonRepository : ReadWriteDbRepository<Person>
{
public PersonRepository(IReadWriteDbConnection db) : base(db)
{
}
public virtual IEnumerable<Person> QueryRecentEmployees()
{
return this.Query("SELECT TOP 10 * FROM [Person].[Person] WHERE [PersonType]='EM' ORDER BY [ModifiedDate] DESC");
}
public virtual void UpdateCustomers()
{
this.Execute("UPDATE [Person].[Person] SET [FirstName]='Rick' WHERE [PersonType]='EM' ");
}
}
public void Sample()
{
// Registers that GetReadWriteRepository<Person>() should return a derived type PersonRepository
ReadWriteDbConnection.RegisterRepositoryType<Person, PersonRepository>();
var conn = new ReadWriteDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));
// we know exactly what subtype to expect, so we can just cast.
var repo = (PersonRepository) conn.GetReadWriteRepository<Person>();
repo.UpdateCustomers();
var recentEmployees = repo.QueryRecentEmployees();
}
Full documentation here.
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 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();
}
}