Problem with getting inserted Id from database - c#

I am trying to get the last inserted Id from database via Entity Framework, however my problem is somehow unique and I am not able to find any solution, but to rewrite my whole infrastructure and business layer, so all my Ids are Guids, which I am able to create manually, or get last record with another database call after commit.
Here is the problem. I have a three-layered architecture, where I am using UoW, repository, services and facades. I will show my code from top to bottom, so you can understand.
Here is my facade, where uow.Commit is calling SaveChanges()
public async Task<int> RegisterUserAsync(UserCreateDto user)
{
using (var uow = UnitOfWorkProvider.Create())
{
var id = _userService.Create(user);
await uow.Commit();
return id;
}
}
As you can see I am sending only my DTO into the service, where I process is like this, also I am mapping inside service
public virtual int Create(TCreateDto entityDto)
{
var entity = Mapper.Map<TEntity>(entityDto);
Repository.Create(entity);
return entity.Id;
}
and finally my repository looks like this
public TKey Create(TEntity entity)
{
Context.Set<TEntity>().Add(entity);
return entity.Id;
}
Is there some elegant solution to this? Like I said my only idea is to switch all Ids to Guid or second call for Id after commit, which I find as not very good solution, because when I want to connect two or more tables in one transaction it would be impossible.

EF Core solution of the problem is simple - auto generated PK of an entity instance is available after calling SaveChanges[Async]. e.g.
var entity = Mapper.Map<TEntity>(entityDto);
Context.Add(entity);
// Here entity.Id contains auto-generated temporary value
// Other stuff...
Context.SaveChanges();
// Here entity.Id contains actual auto-generated value from the db
So the problem is more in the design of your infrastructure - all these (unnecessary) UoW, repository, services and facades simply hide that functionality.
The only relatively simple and elegant solution I see with your architecture is to change the service return type from int to Func<int>, e.g.
public virtual Func<int> Create(TCreateDto entityDto)
{
var entity = Mapper.Map<TEntity>(entityDto);
Repository.Create(entity);
return () => entity.Id; // <--
}
Then in your façade you could use
public async Task<int> RegisterUserAsync(UserCreateDto user)
{
using (var uow = UnitOfWorkProvider.Create())
{
var id = _userService.Create(user);
await uow.Commit();
return id(); // <-- the actual id!
}
}
Edit:
Actually EF Core provides another option which would allow you to keep your current design intact - the HiLo key generation strategy, but only if the database provider supports it. I can say for sure Microsoft.EntityFrameworkCore.SqlServer and Npgsql.EntityFrameworkCore.PostgreSQL do support it with respectively ForSqlServerUseSequenceHiLo and ForNpgsqlUseSequenceHiLo fluent APIs.

Related

Problems with Generic Repository Pattern and Entity Framework

There are a large number of examples on how to use the Generic Repository Pattern over Entitry Framework but none ever go into great detail. More often than not the example they use for accessing data from a webapi is a one to one mapping to the model.
For Eg. Get a single model object _carRepository<Car>.Get(id); or get all cars _carRepository<Car>.GetAll();
The way I have it implemented is in seperate layers like below:
Controller -> Service Layer -> Repository Layer -> DataAccess
I have my service layer using AutoMapper where I map from the model(entity) to a DTO and that gets returned to the controller who returns that.
The issue that I cant get my head around is how do I return an object from the repository layer which isnt of type < T>.
Eg. I want to return some data for a grid lets say a combination of multipe entities like a customers last number of purchases. Am I supposed to pass the Customer, Product, Orders repository into the service and make multiple calls to the database and aggregate? Or I was thinking in the cutomers repository just make a method which returns like a view dto of sorts already aggreating the data where it does a linq query and projects to this dto. The issue I have is that I'm sure my repository shouldnt know about dto's?
Would appreciate anyone who has thoughts on the correct way of doing this?
In short, I don't ever recommend using the Generic Repository (anti)pattern. The trouble is that it ends up being far too restrictive and inefficient. Methods like GetAll() serve only to end up materializing entire tables into memory. They don't accommodate things like projection (essentially what you are looking for) as well as eager loading related data, filtering, sorting, or things like supporting pagination.
The way I recommend looking at repositories is as a one-to-one supplier of data for a Controller in the MVC pattern. If you have a CarController you have a CarRepository to serve all data for that controller. If you break it down to CarController and AddCarController then similarly you can build repositories to serve each of these. These repositories are not Generic in that they don't just serve Car entities, but any and all entities from the DbContext that this controller (or service) needs rather than trying to marry repositories to a specific entity. This gives them only one concern for their existance, so a CarController only needs to worry about primarily one repository, and that repository only has to worry about serving that controller. (Rather than every controller that might want information about a Car)
Regarding using a Service between the controller and repository, I would only suggest this if there is a distinct requirement to provide that separation. For example, if you want to support both an MVC controller and a public facing WebAPI and you want the inputs and outputs provided to these to be 100% consistent. The data transported from Service to Consumer (Controllers, etc.) would be DTOs. This adds the complexity of needing to send details like sorting, pagination, and filtering etc. from the consumer to the service.
Removing the service can make interacting with the repository a lot easier, and you can leverage projection to populate the DTOs/view models actually sent to the view via the controller actions/endpoints. The way I facilitate this is leveraging IQueryable<TEntity> in the repository. This lets the repository handle low level filtering/rules if necessary and lets the consumer (controller) handle determining how it wants to consume the data.
For example if we have a CarController with a CarRepository and CarRepository has a method:
public IQueryable<Car> GetCars(bool includeInactive = false)
{
var query = _context.Cars.AsQueryable();
if (!includeInactive)
query = query.Where(x => x.IsActive);
return query;
}
When it comes time for the controller to request cars from the repository, it has full control over how to consume it:
var cars = await CarRepository.GetCars()
.Where(x => x.Make == make && x.Model == model)
.OrderBy(x => x.ModelYear)
.Select(x => new CarSummary
{
Make = x.Make,
Model = x.Model,
ModelYear = x.ModelYear,
Color = x.Color,
Features = x.Features.Select(...)
}.Skip(pageSize * (pageNumber-1))
.Take(pageSize)
.ToListAsync();
The controller has full control over how the data should come back including whether to run an async query or synchronous one all without adding any complexity to the repository. So here we can project (Select, or leverage Automapper and ProjectTo) to get whatever data we need from the entities and their related data. This leads to building far faster and memory/network efficient queries because the projections only worry about the data the end consumer actually needs. When we want to load an entity and its relations to perform an update, we can fetch the entity and eager load the related data we need to inspect/validate/update. Eager loading data is faster than lazy loading, but both use a fair bit of memory so we don't want to be serializing and transmitting entire object graphs. However, when doing an update we are typically only dealing with a single top-level object at a time.
The question then becomes "Why use a repository then?" The repository provides a nice abstraction for unit testing, and it can help standardize core rules you want in the system such as Active/Inactive (soft delete) and things like authorization in systems using multi-tenancy or otherwise having distinct access controls for the current user as to what data they should ever be able to see. If you don't have either of these requirements, then there isn't really much point to using a repository at all. The DbContext and it's DbSets essentially serve that role.
If you do have plans for multiple consumers, then the repository remains the same but the above consuming code happens in the Service, returning the DTO/ViewModel. This means that your controller will need to package and transmit standardized requests to the service or parameters covering whether it expects any sorting, pagination, etc. Again, I wouldn't recommend taking on that overhead and complexity unless there is a very real requirement justifying it. It just serves to make the code harder to work with.
This may not be complete answer but few things while working with Generic Repository.
Assumption I made that your generic repository return type of T and T is the entity.
Following is not exact interface but based on your question.
public class IRepository<T>
{
T Get(int Id);
IEnumrable<T> GetAll();
}
So most of the scenario above will suffice the thing.
Now let's assume that you have requirement that will only return specific attribute of T or partial of T.
Generic repository implementation never say that you don't need any extra or extended repository.
So you can do like this.
public interface ICarRepository : IRepository<Car>
{
IList<int> GetIds();
IList<string> GetAllModels();
bool IsActiveInProduction(int carId);
}
Implement above just like you have implemented IRepository for EF core. Inject this in your services and Use ICarRepository instead of IRepository
for your development.
Note: This is personal preference but for large project GenericRepository become issue sometime or may not completely fit but Repository is useful pattern and it overall depends on how you use it.
Update 1
In Car Repository if you want to return some other DTO but make sure it related to Car Entity otherwise it is not reponsibility of Repo layer.
public interface ICarRepository : IRepository<Car>
{
IList<int> GetIds();
IList<string> GetAllModels();
bool IsActiveInProduction(int carId);
MyDto GetMyDto(int carId); // MyDto is just for explanation.
}
Below is the BaseRespository.cs code that is use that I use as Generic class for entity framework. I inherit this class as parent with my other RepositoryClass.
public class BaseRepository
{
#region Ctor(s)
protected RuhBotEntities RuhBotEntities;
public BaseRepository(RuhBotEntities entities)
{
this.RuhBotEntities = entities;
}
#endregion
#region Protected & Private Members
protected async Task<T> Get<T>(Guid id) where T : class
{
return await this.RuhBotEntities.Set<T>().FindAsync(id);
}
protected IQueryable<T> GetAll<T>(out int totalCount ,bool isDeleted, string searchField = null, string searchTerm = null,
int? pageIndex = null, int? pageSize = null, string sortBy = null, SortOrder? sortOrder = null) where T : class
{
IQueryable<T> entities = this.RuhBotEntities.Set<T>();
var sortByProperty = typeof(T).GetProperty(sortBy);
if (!string.IsNullOrWhiteSpace(sortBy) && sortOrder.HasValue && sortByProperty != null)
{
switch (sortByProperty.PropertyType.FullName)
{
case "System.Int32":
entities = this.RuhBotEntities.Set<T>().Sort<T, int>(sortBy, sortOrder);
break;
case "System.String":
entities = this.RuhBotEntities.Set<T>().Sort<T, string>(sortBy, sortOrder);
break;
}
}
PropertyInfo[] entityProperties = typeof(T).GetProperties();
var propertyList = entityProperties.Where(p => p.PropertyType.FullName == "System.String").Select(p => p.Name).ToList();
if (!string.IsNullOrWhiteSpace(searchTerm) && !string.IsNullOrWhiteSpace(searchField))
{
entities = entities.Where(string.Format("{0}.Contains(#0)", searchField), searchTerm);
}
else if (!string.IsNullOrWhiteSpace(searchTerm) && propertyList.Count > 0)
{
var searchFieldList = new List<string>();
propertyList.ForEach(p => searchFieldList.Add(string.Format("{0}.Contains(#0)", p)));
var propertySearchField = String.Join("||", searchFieldList);
entities = entities.Where(propertySearchField, searchTerm);
}
if (isDeleted)
{
entities = entities.Where(string.Format("{0}.Equals(#0)", "IsDeleted"), false);
}
totalCount = entities.Count();
if (pageIndex.HasValue && pageSize.HasValue)
{
entities = entities.OrderBy(sortBy).Skip(pageIndex.Value * pageSize.Value).Take(pageSize.Value);
}
return entities;
}
protected void Add<T>(T entity) where T : class
{
this.RuhBotEntities.Set<T>().Add(entity);
}
public async Task<T> AddIfNotExists<T>(T entity, Expression<Func<T, bool>> predicate = null) where T : class, new()
{
var dbSet = this.RuhBotEntities.Set<T>();
var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
return exists ? await dbSet.FirstOrDefaultAsync(predicate) : dbSet.Add(entity);
}
protected void Update<T>(T entity, params string[] fieldsExcludedForUpdation) where T : class
{
var dbEntityEntry = this.AttachEntity(entity);
var createdOn = dbEntityEntry.Entity.GetType().GetProperty("CreatedOn");
var createdBy = dbEntityEntry.Entity.GetType().GetProperty("CreatedBy");
if (createdOn != null)
{
dbEntityEntry.Property("CreatedOn").IsModified = false;
}
if (createdBy != null)
{
dbEntityEntry.Property("CreatedBy").IsModified = false;
}
if (fieldsExcludedForUpdation != null && fieldsExcludedForUpdation.Length > 0)
{
foreach (var field in fieldsExcludedForUpdation)
{
dbEntityEntry.Property(field).IsModified = false;
}
}
dbEntityEntry.State = EntityState.Modified;
}
protected void Delete<T>(Guid id) where T : class
{
var entity = this.RuhBotEntities.Set<T>().Find(id);
this.RuhBotEntities.Set<T>().Remove(entity);
}
protected async Task SaveChanges()
{
await this.RuhBotEntities.SaveChangesAsync();
}
private DbEntityEntry AttachEntity(object entity)
{
var entry = this.RuhBotEntities.Entry(entity);
if (entry.State == EntityState.Detached)
{
var set = this.RuhBotEntities.Set(entity.GetType());
object attachedEntity = set.Find(entity.GetType().GetProperty("Id").GetValue(entity));
if (attachedEntity != null)
{
var attachedEntry = this.RuhBotEntities.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
return attachedEntry;
}
else
{
entry.State = EntityState.Modified;
}
}
else
{
this.RuhBotEntities.Set(entity.GetType()).Attach(entity);
this.RuhBotEntities.Entry(entity).State = EntityState.Modified;
}
return entry;
}
#endregion
}

Error: "alternate key property 'id' is null" with "Update" call to Cosmos DB and Entity Framework Core 3.1.2

Let's say I have a simple model of PersonModel as follows which I update the FirstName property from an ASP.NET Core 3.1 Blazor or MVC UI (web form):
public class PersonModel
{
public Guid PersonModelId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
I send the model to an http triggered Azure Function and bind to the model. This is where I implement EF Core and perform the following to update it:
public async Task<int> UpdateAsync(TEntity entity)
{
_dbSet.Attach(entity); //<--this throws the error
_dbContext.Entry(entity).State = EntityState.Modified;
return await _dbContext.SaveChangesAsync();
}
I have no issue creating or deleting entities with this model and given the following article, I don't see any issue creating a model which is 'clean' from any specific repository requirements (i.e. like a partition key or primary key, etc.) however I can't seem to run the update code without getting the following error:
Unable to track an entity of type 'PersonModel' because alternate key property 'id' is null.
Is anyone aware of specific requirements around the model and using this method to update an entity? Is there an alternative which is as easy/elegant as the example from the docs article?
I've tried _dbContext.Update(entity) but I get the exact same error which leads me to believe my model needs an "ID" property.
I've also tried modifying my DbContext setup as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonModel>().HasAlternateKey(p => p.PersonModelId);
}
...but this just gives me the same error and also doesn't change the way the entity is stored.
Update
Following the guidance from the accepted answer below, I wrote the following changes to make this work:
public async Task<int> UpdateAsync(TEntity entity)
{
var entry = _dbSet.Add(entity);
entry.State = EntityState.Unchanged;
_dbContext.Update(entity);
return await _dbContext.SaveChangesAsync();
}
Edit: I believe the issue arises from a property that EF Core automatically generates for each item named "id", specifically all lowercased and separate from your key(s) defined in your model. Appears to be specific to the Cosmos DB provider and the null value issue is noted within the documentation for EF Core -> Database Providers -> Cosmos under the section "Working with Disconnected Entities".
Apparently EF Core only generates this property when entity enters the "Added" state. So when you call Attach, this "id" property is null hence the exception. A workaround is listed in the documentation with a much better explanation than I can give.
Linked below to EF Core docs page for Cosmos provider. Scroll to bottom of overview page to find the disconnected entities section that covers this.
https://learn.microsoft.com/en-us/ef/core/providers/cosmos/?tabs=dotnet-core-cli#working-with-disconnected-entities
According to this issue [1] you'll need to get the entry and set the item id explicitly:
public async Task<T> UpdateItemAsync(string id, T item)
{
var itemEntry = context.Entry(item);
itemEntry.Property<string>("id").CurrentValue = "Item|" + id;
itemEntry.State = EntityState.Modified;
await context.SaveChangesAsync();
return itemEntry.Entity;
}
[1] https://github.com/dotnet/efcore/issues/15289

Set Entity State to Modified for entities with different reference

I am working on WPF Application using MVVM pattern, also I use EF code first for database entities. and because of MVVM I use AutoMapper to map objects from Database "Models" to "ViewModels".
When I update an entity I have to map it back from view model to model and pass it to the update method
public void Update(TEntity entity)
{
TEntity databaseEntity = Context.Set<TEntity>().Find(entity.Id);
DbEntityEntry dbEntityEntry = Context.Entry(databaseEntity);
dbEntityEntry.CurrentValues.SetValues(entity);
dbEntityEntry.State = EntityState.Modified;
}
The problem here I have to Get the entity each time I want to update it, and if I have many objects to update it will cause performance issue. I tried to use this following code but it didn't work because the auto mapper create new instance of the entity different than the one attached with EF dbConext.
public void Update(TEntity entity)
{
DbEntityEntry<TEntity> dbEntityEntry = Context.Entry(entity);
dbEntityEntry.State = EntityState.Modified;
}
I tried to handle this by overriding Equals and GetHashCode methods but it also didn't work.
Is there any way to handle update entity without getting it form the database?
Thank you.
Since you're manually tracking changes you should not retain a reference to the same dbcontext. Read your data inside a Using statement which news up and disposes the dbcontext.
When you write the data back new up an entity, set the properties to those from your viewmodel, set it's state to modified and savechanges.
Again, open and close your dbcontext round this.
There is then no need to go find the entity you read.
Here's a trivial example in case that explanation was unclear.
This does not represent a best practice enterprise solution - I would expect at least a generic updateentityasync method.
We're updating a Person in a People table.
public async Task<bool> UpdatePersonAsync(Person person)
{
int numUpdated = 0;
using (var db = new AndyPeopleContext())
{
db.People.Add(person);
db.Entry(person).State = EntityState.Modified;
numUpdated = await db.SaveChangesAsync();
}
return numUpdated == 1;
}

Unable to update modified entities

I've got an aggregate for a specific type of entity which is stored in a collection inside the aggregate. Now I'd like to add a new entry of that type and update the aggregate afterwards, however Entity Framework never updates anything!
Model
public class MyAggregate {
protected ICollection<MyEntity> AggregateStorage { get; set; }
public void AddEntity(MyEntity entity) {
// some validation
AggregateStorage.Add(entity);
}
}
API Controller
[UnitOfWork, HttpPost]
public void UpdateMyEntity(int aggregateId, MyEntityDto dto) {
var aggregate = _aggregateRepository.Find(aggregateId);
aggregate.AddEntity(...// some mapping of the dto).
_aggregateRepository.Update(aggregate);
}
EF Update
EntitySet.Attach(aggregate);
Context.Entry(aggregate).State = EntityState.Modified;
(Please note that there's an unit of work interceptor on the API action who fires DbContext.SaveChanges() after successful execution of the method.)
Funny thing is, the update never get's executed by EF. I've added a log interceptor to the DbContext to see what's going on sql-wise and while everything else works fine, an update statement never occurs.
According to this answer in detached scenario (either aggregate is not loaded by EF or it is loaded by different context instance) you must attach the aggregate to context instance and tell it exactly what did you changed, set state for every entity and independent association in object graph.
You must either use eager loading and load all data together at the beginning and
instead of changing the state of aggregate, change the state of entities:
foreach(var entity in aggregate.AggregateStorage)
{
if(entity.Id == 0)
Context.Entry(entity).State = EntityState.Added;
}

Repository Pattern with Entity Framework 4.1 and Parent/Child Relationships

I still have some confusion with the Repository Pattern. The primary reason why I want to use this pattern is to avoid calling EF 4.1 specific data access operations from the domain. I'd rather call generic CRUD operations from a IRepository interface. This will make testing easier and if I ever have to change the data access framework in the future, I will be able to do so without refactoring a lot of code.
Here is an example of my situation:
I have 3 tables in the database: Group, Person, and GroupPersonMap. GroupPersonMap is a link table and just consists of the Group and Person primary keys. I created an EF model of the 3 tables with VS 2010 designer. EF was smart enough to assume GroupPersonMap is a link table so it doesn't show it in the designer. I want to use my existing domain objects instead of EF's generated classes so I turn off code generation for the model.
My existing classes that matches the EF model are as follows:
public class Group
{
public int GroupId { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
public class Person
{
public int PersonId {get; set; }
public string FirstName { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
I have a generic repository interface like so:
public interface IRepository<T> where T: class
{
IQueryable<T> GetAll();
T Add(T entity);
T Update(T entity);
void Delete(T entity);
void Save()
}
and a generic EF repository:
public class EF4Repository<T> : IRepository<T> where T: class
{
public DbContext Context { get; private set; }
private DbSet<T> _dbSet;
public EF4Repository(string connectionString)
{
Context = new DbContext(connectionString);
_dbSet = Context.Set<T>();
}
public EF4Repository(DbContext context)
{
Context = context;
_dbSet = Context.Set<T>();
}
public IQueryable<T> GetAll()
{
// code
}
public T Insert(T entity)
{
// code
}
public T Update(T entity)
{
Context.Entry(entity).State = System.Data.EntityState.Modified;
Context.SaveChanges();
}
public void Delete(T entity)
{
// code
}
public void Save()
{
// code
}
}
Now suppose I just want to map an existing Group to an existing Person. I would have to do something like the following:
EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
EFRepository<Person> personRepository = new EFRepository<Person>("name=connString");
var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();
group.People.Add(person);
groupRepository.Update(group);
But this doesn't work because EF thinks Person is new, and will try to re-INSERT the Person into the database which will cause a primary key constraint error. I must use DbSet's Attach method to tell EF that the Person already exists in the database so just create a map between Group and Person in the GroupPersonMap table.
So in order to attach Person to the context I must now add an Attach method to my IRepository:
public interface IRepository<T> where T: class
{
// existing methods
T Attach(T entity);
}
To fix the primary key constraint error:
EFRepository<Group> groupRepository = new EFRepository<Group>("name=connString");
EFRepository<Person> personRepository = new EFRepository<Person>(groupRepository.Context);
var group = groupRepository.GetAll().Where(g => g.GroupId == 5).First();
var person = personRepository.GetAll().Where(p => p.PersonId == 2).First();
personRepository.Attach(person);
group.People.Add(person);
groupRepository.Update(group);
Fixed. Now I have to deal with another issue where Group is being UPDATE'd in the database every time I create a Group/Person map. This is because in my EFRepository.Update() method, the entity state is explicitly set to Modified'. I must set the Group's state toUnchangedso theGroup` table doesn't get modified.
To fix this I must add some sort of Update overload to my IRepository that does not update the root entity, or Group, in this case:
public interface IRepository<T> where T: class
{
// existing methods
T Update(T entity, bool updateRootEntity);
}
The EF4 implentation of the Update method would look something like this:
T Update(T entity, bool updateRootEntity)
{
if (updateRootEntity)
Context.Entry(entity).State = System.Data.EntityState.Modified;
else
Context.Entry(entity).State = System.Data.EntityState.Unchanged;
Context.SaveChanges();
}
My question is: Am I approaching this the right way? My Repository is starting to look EF centric as I start to work with EF and the repository pattern. Thanks for reading this long post
The primary reason why I want to use this pattern is to avoid calling
EF 4.1 specific data access operations from the domain. I'd rather
call generic CRUD operations from a IRepository interface. This will
make testing easier
No it will not make your testing easier. You exposed IQueryable so your repository is not unit testable.
if I ever have to change the data access framework in the future, I
will be able to do so without refactoring a lot of code.
No you will have to change a lot of code anyway because you exposed IQueryable and because EF / ORM is leaky abstraction - your upper layer expects some behavior happens magically inside your ORM (for example lazy loading). Also this is one of the most odd reasons to go for repository. Simply choose the right technology now and use it to get the bets of it. If you have to change it later it means either that you did a mistake and chose the wrong one or requirements have changed - in either case it will be a lot of work.
But this doesn't work because EF thinks Person is new, and will try to
re-INSERT the Person into the database which will cause a primary key
constraint error.
Yes because you are using a new context for each repository = that is wrong approach. Repositories must share the context. Your second solution is not correct as well because you put your EF dependency back to the application - repository is exposing the context. This is usually solved by second pattern - unit of work. Unit of work wraps the context and unit of work forms the atomic change set - SaveChanges must be exposed on unit of work to commit changes done by all related repositories.
Now I have an issue with the Group being UPDATE'd in the database
every time I want to create a Group/Person map.
Why do you change the state? You received entity from the repository so until you detached it there is no reason to call Attach and change the state manually. This all should happen automatically on attached entity. Simply call SaveChanges. If you are using detached entities then you must correctly set state for every entity and relation so in such case you will indeed needs some logic or update overloads to handle all scenarios.
Am I approaching this the right way? My Repository is starting to look
EF centric as I start to work with EF and the repository pattern.
I don't think so. First of all you are not using aggregate roots. If you do you would immediately found that generic repository is not suitable for that. Repository for aggregate roots have specific methods per aggregate root to handle working with relations aggregated by the root. Group is not part of Person aggregate but GroupPersonMap should be so your Person repository should have specific methods to handle adding and removing groups from person (but not to create or delete groups themselves). Imo generic repository is redundant layer.

Categories