I have two databasecontexts FooContext, BarContext.
For FooContext, when certain entities in the dbSet changes, I need to project these entities to related entities in BarContext. I am looking at the best ways to implement such a job.
My current attempt involves inheriting an interface for the relevant entities, filtering these out in overridden FooContext.SaveChangesAsync() before calling a separate mapper (using dependencyInjection) to update the relevant tables in BarContext
// Baz.cs
public class Baz : IProjectedEntity {
// baz-definition
}
// FooContext.cs
public class FooContext : DbContext {
private IJobSpawner _jobSpawner;
public FooContext(IJobSpawner jobSpawner){
_jobSpawner=jobSpawner;
}
public DbSet<Baz> Baz {get;}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new()) {
ChangeTracker.DetectChanges();
var projections = ChangeTracker
.Entries<IProjectedEntity>()
.Where(e => e.State != EntityState.Unchanged)
.Select(e => e.Entity).ToList();
int changes = await base.SaveChangesAsync(cancellationToken);
await _jobSpawner.ProjectEntities(projections);
return changes;
}
}
Then the implementation of IJobSpawner takes dependency on BarContext and does the necessary mappings.
Is this a bad idea?
What is an alternative way of doing this?
I have also played a bith with the thought of defining an attribute for class properties, such that projections only happen if certain properties is changed
note : The method i have proposed does work, but I feel there should be a better, less cluttered way of achieving the same result. I don't like having to much internal logic in the context
Related
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
}
Given a DbContext and a ClientContext (custom session data about the user) is it possible create a DbContext that is "authorised": where only a subset of the rows on each "table" is available?
With an authorised DbContext I'm trying to have a central row-level authorisation implementation.
I've researched it a bit and the only way to filter out a DbSet would be to use something like Queryable.Where but that returns an IQueryable<T> and there doesn't seem to be a way to return a filtered DbSet<T> (except maybe for global queries that you can setup in Startup but they don't have access to injected dependencies like ClientContext).
Is it possible to define DbSet<T> authorisation filters via an injected scoped dependency like ClientContext?
There are model-level query filters: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#model-level-query-filters
From the link:
This feature allows LINQ query predicates (a boolean expression
typically passed to the LINQ Where query operator) to be defined
directly on Entity Types in the metadata model (usually in
OnModelCreating). Such filters are automatically applied to any LINQ
queries involving those Entity Types, including Entity Types
referenced indirectly, such as through the use of Include or direct
navigation property references.
Example from the link:
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public int TenantId { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>().HasQueryFilter(
p => !p.IsDeleted
&& p.TenantId == this.TenantId);
}
}
You can use this for simple scenarios. You define an instance property in your DbContext and in OnModelCreating you specify HasQueryFilter on any entity you want to filter. The property is an instance property, so if you have a scoped DbContext, the correct property value from that request would be used, which is handy if you want to filter by something from your UserContext. I have personally never tried this so I don't know how complex it allows your implementation to be, but you can play with it.
I'm not sure about EF and EF core, but we abstract the DbContext away into functional specific 'logic' blocks.
e.g:
class DbContext()
{
public DbSet<PeopleEntity> peoples;
}
class PeopleLogic()
{
DbContext _context;
PeopleLogic(DbContext context)
{
_context = context;
}
IEnumerable GetAllPeoples()
{
// create context,
// apply filters
// return result
}
}
We ofcourse have a base for simple CRUD operations;
public void AddOrUpdate(){
lock (SyncDatabaseWriteObject)
{
try
{
using (var context = CreateContext())
{
//insert the entity and add it to the db context
context.Set<TEntity>().AddOrUpdate((TEntity)entity);
context.SaveChanges();
}
return entity;
}
catch (Exception ex)
{
throw new DatabaseAccessException("Error occured while getting saving.", ex);
}
}
}
And instead of passing the dbcontext around, we pass around logics.
e.g. we seperate the logic for the database and the access to the database into 2 seperate projects, the business layer then only uses the dbAccess layer.
I have following classes:
Order:
public class Order {
private Order()
{
//some code to initialise object
}
//more properties
public Prepayment Prepayment { get; private set; }
//more methods and properties
}
Prepayment:
public class Prepayment:ValueObject<Prepayment>
{
private Prepayment()
{
}
public Money AmountPrepaid { get; private set; }
public bool HasPrepaymentBeenTaken => AmountPrepaid.Amount > 0;
}
Money:
public class Money {
private Money()
{
}
private Money(decimal amount)
: this()
{
Amount = amount;
}
public decimal Amount { get; private set; }
}
Then I the Order class is mapped to the database in following way:
modelBuilder.Entity<Order>()
.OwnsOne(x => x.Prepayment,
prepayment =>
{
prepayment.OwnsOne(x => x.AmountPrepaid,
amountPrepaid =>
{
amountPrepaid.Property(x => x.Amount)
.HasColumnName("PrepaymentAmount")
.HasColumnType("decimal(7,2)");
});
});
Repository code to SaveChanges:
public async Task<int> SaveAsync(Order order)
{
if (order.Id == 0)
{
await _context.AddAsync(order);
}
else
{
_context.Update(order);
}
return await _context.SaveChangesAsync();
}
Please understand, I removed all the not important properties from the code, to make the example more clear.
Above code works well for INSERT scenario, where Prepayment -> Money -> Amount is properly saved into the database. UPDATE though doesn't seem to be reflected in the database.
Please note, I have quite a few Owned Types in that model, and all of them are working well. The only difference as far as I can say is the fact that Prepayment property has another nested Owned type - Money.
Order class object passed to repository, is first pulled from the database, then changes are applied on that instance, and finally saved back to the database. Other properties like Customer not mentioned in the example, is also a OwnedType and UPDATE works as expected.
Just in case - the code used to retrieve the object prior to update:
public async Task<Order> GetOrderByIdAsync(int orderId)
{
var result = (from order in _context.Orders
where order.Id == orderId
select order).Include(x => x.OrderLines);
return await result.FirstOrDefaultAsync();
}
The exact version of Entity Framework Core I am using is: 2.2.0
Any help would be highly appreciated.
Thank you.
EDIT:
The code that updates the data looks like this:
public async Task<int> Handle(EditOrderCommand request, CancellationToken cancellationToken)
{
var order = await _orderRepository.GetOrderByIdAsync(request.Id);
var customer = new Customer(
request.FirstName,
request.LastName,
request.TelephoneNumber);
var prepayment = new Prepayment(
Money.SomeMoney(
request.PrepaymentAmount
)
);
order.ApplyChanges(
request.UserId,
request.AdditionalInformation,
collectionDate,
customer,
prepayment);
await _orderRepository.SaveAsync(order);
return order.Id;
}
And part of the ApplyChanges method that sets prepayment:
private void SetPrepayment(Prepayment prepayment)
{
Prepayment = prepayment ?? throw new ArgumentNullException(nameof(prepayment));
}
The issue has something in common with the nested owned entity type.
But the general problem is that the way you are using owned entity types is violating the EF Core rules, thus the behavior is undefined - sometimes it might work, sometimes not, sometimes even throw exceptions etc.
Owned entity types cannot be used to implement value objects, because even they are "owned", by EF Core terminology they are still "entities" (with hidden shadow PK), so they are tracked by reference and follow the same rules as other entity references -most importantly, there must be only one object instance per defining navigation PK.
In short, they are supposed to be allocated once and then mutated via their primitive properties. While what are you doing is mutating the references with immutable objects.
It's hard to give you good advice because of the aforementioned EF Core rule violations. The only working workaround is to make sure all original object references are not tracked by the context.
For instance, if GetOrderByIdAsync implementation uses AsNoTracking query, hence neither order nor order.Prepayment and order.Prepayment.AmountPrepaid instances are tracked by the _context, then _context.Update(order) will work.
It will also work if you manually detach them before calling ApplyChanges (requires access to the db context):
_context.Entry(order).State = EntityState.Detached;
_context.Entry(order.Prepayment).State = EntityState.Detached;
_context.Entry(order.Prepayment.AmountPrepaid).State = EntityState.Detached;
order.ApplyChanges(...);
await _orderRepository.SaveAsync(order); // _context.Update(order);
Looks like AsNoTracking is the better option. You can make it default for all queries by setting ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; inside your db context constructor and use AsTracking() where needed.
I have this situation:
I'm using Nhibernate to map an Entity 'User' that is mapped on a large table in Sql, for perfomance reasons I've created a lightweight entity 'LightweightUser' that has only a small set of properties and is NOT mapped in a hbm file or whatever, I'm using the Nhibernate Projection technique to wire the 'LightweightUser' entity. Both entities are deriving from a simple class 'Entity' that contains an Id property.
The above implementations works fine, sql query is smaller == faster.
But in my Nhibernate SessionFactory I have also injected a Nhibernate Intercerptor.
The OnLoad method of the Interceptor is called when I'm getting a 'User' entity from the NHibernate Dao, but when I get the 'LightweightUser' entity from the NHibernate Dao the Interceptor is not triggered (OnLoad method).
This is probably related to the fact that NHibernate SessionFactory has no knowledge of my 'LightweightUser' entity. Is there a way to inform the Sessionfactory/ Interceptor of the existence of my 'Lightweight' entity? Or is there some other technique to wire Projections to an Interceptor? And yes I can also duplicate the the 'hbm' file for my Lightweight entity, but that seems like bad practice.
[TestFixture]
public class InterceptorTest : NhibernateSessionHelper
{
[Test]
public void GetEntiy()
{
//ok trigger OnLoad in the Interceptor
var user = GetSession().Get<User>(1);
Assert.IsNotNull(user);
}
[Test]
public void GetProjection()
{
var crit = GetSession().CreateCriteria<User>();
crit.Add(Restrictions.Eq("Id", 5));
crit.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Id"), "Id")
.Add(Projections.Property("Username"), "UserName"));
crit.SetResultTransformer(Transformers.AliasToBean(typeof(LightweightUser)));
//does not trigger the Interceptor
var result = crit.List<LightweightUser>();
Assert.IsNotNull(result.First());
}
}
//Wire the Sessionfactory with Interceptor
private static ISessionFactory CreateSessionFactory()
{
return new Configuration()
.CurrentSessionContext<ThreadStaticSessionContext>()
.SetInterceptor(new MyInterceptor())
.Configure().BuildSessionFactory();
}
public class MyInterceptor: IInterceptor
{
public override bool OnLoad(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
{
//do stuff
var s = entity;
return base.OnLoad(entity, id, state, propertyNames, types);
}
Thanks in advance
The Interceptor's method OnLoad is called only in handling of PreLoadEvent. This is raised in two cases: when an entity is loaded from the database, or when the entity is assembled from the cache. Objects created with a projection like yours are not considered being an entity.
If you want to apply the same code for entities and for tuples of LightweightUser you can implement your custom IResultTransformer containing an instance of
Transformers.AliasToBean(typeof(LightweightUser))
and after calling the original transformer methods apply your logic of
//do stuff
If want to know more about interceptor, I encourage you to read a list of my blog entries starting here: http://blog.scooletz.com/2011/02/03/nhibernate-interceptor-magic-tricks-pt-1/
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.