DbContext in ResolveWith, HotChocolate GraphQL - c#

So I just get started,
I have an entity which keeps the data from other entities not by a direct relation but with keeping the EntityId and EntityType(Enum).
When I read these records from GraphQL I expect to resolve a field with a resolver as follow,
public class AssignmentResolver
{
public object GetEntity( Assignment assignment, AppDbContext context)
{
if(assignment.EntityType == AssignmentEntityType.PERSON)
{
return context.People.FirstOrDefault(x => x.Id == assignment.EntityId);
}
// And more checks
return null;
}
}
Then I can say
public class AssignmentQueryType: ObjectType<Assignment>
{
protected override void Configure(IObjectTypeDescriptor<Assignment> descriptor)
{
descriptor.Field("entity").ResolveWith<AssignmentResolver>(x => x.GetEntity(default!, default!));
}
}
I wanna know if this is right or is there a better way...
I mean the better way would be using a document database for this but that's not an option for now.
I also maybe instead of putting the EntityType and EntityId can simply set an actual relation to those other entities but I wanna see if this current way is possible.

Well that was fast.
I found my problem. It seems that in the resolver I cannot just return an object because the schema should be clear when being read.
So from the resolver if I return a viewModel which is shared between all those entities then we are good to go.
So the GetEntity code will change to
public EntityViewModel GetEntity( Assignment assignment, [Service] AppDbContext context)
{
if(assignment.EntityType == AssignmentEntityType.PERSON)
{
var entity = context.People.FirstOrDefault(x => x.Id == assignment.EntityId);
return new EntityViewModel(entity);
}
// And more checks
return null;
}

Related

nested Owned Type not saved when Updating in the database

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.

EF Core with GraphQL

I'm currently exploring the GraphQL development and I'm currently exploring what kind of SQL queries are Generated via EF Core and I observed that no matter that my GraphQL query includes only a few fields the EF Core sends SQL Select for all fields of the Entity.
This is the code I'm using now:
public class DoctorType : ObjectGraphType<Doctors>
{
public DoctorType()
{
Field(d => d.PrefixTitle);
Field(d => d.FName);
Field(d => d.MName);
Field(d => d.LName);
Field(d => d.SufixTitle);
Field(d => d.Image);
Field(d => d.EGN);
Field(d => d.Description);
Field(d => d.UID_Code);
}
}
public class Doctors : ApplicationUser
{
public string Image { get; set; }
[StringLength(50)]
public string UID_Code { get; set; }
}
the query I'm using is
{
doctors{
fName
lName
}
}
The SQL generated selects all fields of the Doctor entity.
Is there any way to further optimize that the generated SQL query from EF Core?
I'm guessing this happens because the DoctorType inherits from ObjectGraphType<Doctors> and not from some Projection of the Doctor, but I can't think of a clever workaround of this?
Any suggestions?
EDIT:
I'm using GraphQL.NET (graphql-dotnet) by Joe McBride version 2.4.0
EDIT 2:
Either I'm doing it wrong or I don't know.
As one of the comments suggested i downloaded GraphQL.EntityFramework Nuget package by SimonCropp
I did all the configuration needed for it:
services.AddDbContext<ScheduleDbContext>(options =>
{
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"));
});
using (var myDataContext = new ScheduleDbContext())
{
EfGraphQLConventions.RegisterInContainer(services, myDataContext);
}
My Object graph Type is looking as follows
public class SpecializationType : EfObjectGraphType<Specializations>
{
public SpecializationType(IEfGraphQLService graphQlService)
:base(graphQlService)
{
Field(p => p.SpecializationId);
Field(p => p.Code);
Field(p => p.SpecializationName);
}
}
My query looks is:
public class RootQuery : EfObjectGraphType
{
public RootQuery(IEfGraphQLService efGraphQlService,
ScheduleDbContext dbContext) : base(efGraphQlService)
{
Name = "Query";
AddQueryField<SpecializationType, Specializations>("specializationsQueryable", resolve: ctx => dbContext.Specializations);
}
}
and I'm using this graphQL query
{
specializationsQueryable
{
specializationName
}
}
The debug log show that the generated SQL query is
SELECT `s`.`SpecializationId`, `s`.`Code`, `s`.`SpecializationName`
FROM `Specializations` AS `s`
even though I want only specializationName field and I'm expecting it to be:
SELECT `s`.`SpecializationName`
FROM `Specializations` AS `s`
UPDATE
I guess so far I didn't understand how graphQL really worked. I thought that there is some behind the scene fetch of data but there isn't.
The primary fetch is done in the query's field resolver :
FieldAsync<ListGraphType<DoctorType>>("doctors", resolve: async ctx => await doctorServices.ListAsync());
and as long the result to the resolver is the full object in my case the resolver return List of Doctors entity, it will query the Database for the whole entity (all fields). No optimisations are done out of the box from GraphQL doesn't matter if you return IQueryable or else of the entity you are querying.
Every conclusion here is thought of mine it is not 100% guaranteed right
So what I've did is create a group of Helper methods which are creating an selection Expression to use in the LINQ query. The helpers are using resolver's context.SubFields property to get the fields needed.
The problem is that you need for every level of the query only the leaves, say some query "specializations" with "SpecializationName" and "Code" and the "Doctors" with their "Name" and else. In this case in the RootQuery specializations field's resolver you need only the Specializations entity projection so: SpecializationName and Code , then when it goes to fetch all Doctors from the "doctors" Field in SpecializationType the resolver's context has different SubFields which should be used for the projection of the Doctor.
The problem with the above is, when you use query batches i guess even if you dont the thing is that the Doctors Field in SpecializationType needs the SpecializationId fetched in the RootQuery specializations Field.
I guess i didn't explain good what i went through.
Base line is as far as I understand we have to dynamically create selectors which the linq should use to project the entity.
I'm posting my approach here:
public class RootQuery : EfObjectGraphType
{
public RootQuery(IEfGraphQLService efGraphQlService, ISpecializationGraphQlServices specializationServices,
IDoctorGraphQlServices doctorServices, ScheduleDbContext dbContext) : base(efGraphQlService)
{
Name = "Query";
FieldAsync<ListGraphType<SpecializationType>>("specializations"
, resolve: async ctx => {
var selectedFields = GraphQLResolverContextHelpers.GetFirstLevelLeavesNamesPascalCase(ctx.SubFields);
var expression = BuildLinqSelectorObject.DynamicSelectGenerator<Specializations>(selectedFields.ToArray());
return await specializationServices.ListAsync(selector: expression);
});
}
}
SpecializationType
public class SpecializationType : EfObjectGraphType<Specializations>
{
public SpecializationType(IEfGraphQLService graphQlService
, IDataLoaderContextAccessor accessor, IDoctorGraphQlServices doctorServices)
: base(graphQlService)
{
Field(p => p.SpecializationId);
Field(p => p.Code);
Field(p => p.SpecializationName);
Field<ListGraphType<DoctorType>, IEnumerable<Doctors>>()
.Name("doctors")
.ResolveAsync(ctx =>
{
var selectedFields = GraphQLResolverContextHelpers.GetFirstLevelLeavesNamesPascalCase(ctx.SubFields);
selectedFields = GraphQLResolverContextHelpers.AppendParrentNodeToEachItem(selectedFields, parentNode: "Doctor");
selectedFields = selectedFields.Union(new[] { "Specializations_SpecializationId" });
var expression = BuildLinqSelectorObject.BuildSelector<SpecializationsDoctors, SpecializationsDoctors>(selectedFields);
var doctorsLoader = accessor.Context
.GetOrAddCollectionBatchLoader<int, Doctors>(
"GetDoctorsBySpecializationId"
, (collection, token) =>
{
return doctorServices.GetDoctorsBySpecializationIdAsync(collection, token, expression);
});
return doctorsLoader.LoadAsync(ctx.Source.SpecializationId);
});
}
}
DoctorsServices:
public class DoctorGraphQlServices : IDoctorGraphQlServices
{
public ScheduleDbContext _dbContext { get; set; }
public DoctorGraphQlServices(ScheduleDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<List<Doctors>> ListAsync(int? specializationId = null)
{
var doctors = _dbContext.Doctors.AsQueryable();
if(specializationId != null)
{
doctors = doctors.Where(d => d.Specializations.Any(s => s.Specializations_SpecializationId == specializationId));
}
return await doctors.ToListAsync();
}
public async Task<ILookup<int, Doctors>> GetDoctorsBySpecializationIdAsync(IEnumerable<int> specializationIds, CancellationToken token, Expression<Func<SpecializationsDoctors, SpecializationsDoctors>> selector = null)
{
var doctors = await _dbContext.SpecializationsDoctors
.Include(s => s.Doctor)
.Where(spDocs => specializationIds.Any(sp => sp == spDocs.Specializations_SpecializationId))
.Select(selector: selector)
.ToListAsync();
return doctors.ToLookup(i => i.Specializations_SpecializationId, i => i.Doctor);
}
}
SpecializationServices
public class SpeciaizationGraphQlServices : ISpecializationGraphQlServices
{
public ScheduleDbContext _dbContext { get; set; }
public SpeciaizationGraphQlServices(ScheduleDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<dynamic> ListAsync(string doctorId = null, Expression<Func<Specializations, Specializations>> selector = null)
{
var specializations = _dbContext.Specializations.AsQueryable();
if (!string.IsNullOrEmpty(doctorId))
{
specializations = specializations.Where(s => s.Doctors.Any(d => d.Doctors_Id == doctorId));
}
return await specializations.Select(selector).ToListAsync();
}
public async Task<ILookup<string, Specializations>> GetSpecializationsByDoctorIdAsync(IEnumerable<string> doctorIds, CancellationToken token)
{
var specializations = await _dbContext.SpecializationsDoctors
.Include(s => s.Specialization)
.Where(spDocs => doctorIds.Any(sp => sp == spDocs.Doctors_Id))
.ToListAsync();
return specializations.ToLookup(i => i.Doctors_Id, i => i.Specialization);
}
public IQueryable<Specializations> List(string doctorId = null)
{
var specializations = _dbContext.Specializations.AsQueryable();
if (!string.IsNullOrEmpty(doctorId))
{
specializations = specializations.Where(s => s.Doctors.Any(d => d.Doctors_Id == doctorId));
}
return specializations;
}
}
This post has become pretty large, sorry for the span..
For DoctorType, check the defined ObjectGraphType which is used to return Doctors.
For example, I have PlayerType like below:
public class PlayerType : ObjectGraphType<Player>
{
public PlayerType(ISkaterStatisticRepository skaterStatisticRepository)
{
Field(x => x.Id);
Field(x => x.Name, true);
Field(x => x.BirthPlace);
Field(x => x.Height);
Field(x => x.WeightLbs);
Field<StringGraphType>("birthDate", resolve: context => context.Source.BirthDate.ToShortDateString());
Field<ListGraphType<SkaterStatisticType>>("skaterSeasonStats",
arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "id" }),
resolve: context => skaterStatisticRepository.Get(context.Source.Id), description: "Player's skater stats");
}
}
And I return Field<ListGraphType<PlayerType>> by
public class NHLStatsQuery : ObjectGraphType
{
public NHLStatsQuery(IPlayerRepository playerRepository, NHLStatsContext dbContext)
{
Field<ListGraphType<PlayerType>>(
"players",
resolve: context => {
return dbContext.Players.Select(p =>new Player { Id = p.Id, Name = p.Name });
//return playerRepository.All();
});
}
}
For the query and its columns, it is controlled by resolve in Field.
No matter what fields you want to return, make sure the columns defined in PlayerType are returned in resolve.
I suggest you:
1-use dto models and map them with database models
This means that you need to convert input dto model in database model to save in db; and also convert database models got from entity framework database select into dto model.
This is the classic approach used when you made a generic api, that for example get dto model data in input request, convert dto to save data in database, and viceversa.
2-map dto model to graphqltypes (objectgraphtype and inputobjectgraphtype)
This means that for each dto model could be necessary write 1 objectgraphtype and 1 inputobjectgraphtype.
TO DO THIS I'VE CREATE AN AUTOMATIC DTO TO GRAPHTYPE CONVERTER, so you don't need to write K and K of codes!! (see link at the end)
3-DON'T USE ADDDBCONTEXT! Graphql middleware use a singleton pattern; everything used via Dependecy injection in graphql is singleton externally, even if it is register as scoped (AddDbContext means "scoped").
This means that you have 1 connection opened to startup. You can't do 2 db operation in the same time!
In the real life you can't use AddDbContext with Graphql!
You can use factory pattern to do this. So, don't pass dbcontext in Dependency injection, but a Func and instantiate dbcontext explicitally.
Here a complete implementation example:
https://github.com/graphql-dotnet/graphql-dotnet/issues/576#issuecomment-626661695
There was a talk about GraphQL with EF Core 6 by #jeremylikness on .NET Conf 2021. I would recommend using .NET 6 and check his talk out:
https://devblogs.microsoft.com/dotnet/get-to-know-ef-core-6/#graphql
https://aka.ms/graphql-efcore
https://www.youtube.com/watch?v=GBvTRcV4PVA
https://www.youtube.com/watch?v=4nqjB_z5CU0
Here is an example implementation using Hot Chocolate GraphQL server:
https://chillicream.com/docs/hotchocolate/integrations/entity-framework
This is what Microsoft wrote about GraphQL for EF Core 6.0 in their High-level plan:
GraphQL has been gaining traction over the last few years across
a variety of platforms. We plan to investigate the space and find ways
to improve the experience with .NET. This will involve working with
the community on understanding and supporting the existing ecosystem.
It may also involve specific investment from Microsoft, either in the
form of contributions to existing work or in developing complimentary
pieces in the Microsoft stack.
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/plan#graphql
To enable automatic fields projection, having your DB context set up as usual for .NET 6, add hotchocolate server:
dotnet add package HotChocolate.Data.EntityFramework
Expose some data to it:
public class MyQueries
{
[UseProjection] // Enables field projection
public IQueryable<Book> Books([Service] MyContext db) => db.Books;
}
Enable it in Program.cs:
builder.Services.AddGraphQLServer().AddQueryType<MyQueries>().AddProjections();
...
app.MapGraphQL("/graphql");
That should be enough to ensure automatic db fields projection. Now you can run GraphQL queries via builder generated at /graphql/, while monitoring SQL via MyContext.Database.Log = Console.Write;
I'm using GraphQL.NET (graphql-dotnet) by Joe McBride version 2.4.0
First of all, I'd recommend updating at least to v4.6 - there are lots of fixes and useful updates.
Secondly, if you don't have data mutation (means - update/delete/insert data), I'd say that better to not use EF for fetching data. Based on the same GraphQL.Net lib you can take a look, for example, NReco.GraphQL is using lightweight-ORM to fetch and map data (you just need to define a schema in json-file).

Set Value before save

I was wondering if there is any way to set a value to an entity onsave?
Because I'm working on a multi tenant web application and I would like to set the the current tenant ID (through simple DI service).
I tried using HasDefaultValue() in Fluent API, however this will try to convert to a SQL function. So this doesn't work for me.
builder.Entity<Order>( )
.HasQueryFilter(p => p.TenantId == _tenantProvider.GetTenantId())
.Property(p => p.TenantId)
.HasDefaultValue(_tenantProvider.GetTenantId());
Any suggestions are greatly appreciated.
You could override the DbContext.SaveChanges() method and iterate the ChangeTracker entries:
public override int SaveChanges()
{
foreach (var entityEntry in ChangeTracker.Entries()) // Iterate all made changes
{
if (entityEntry.Entity is Order order)
{
if (entityEntry.State == EntityState.Added) // If you want to update TenantId when Order is added
{
order.TenantId = _tenantProvider.GetTenantId();
}
else if (entityEntry.State == EntityState.Modified) // If you want to update TenantId when Order is modified
{
order.TenantId = _tenantProvider.GetTenantId();
}
}
}
return base.SaveChanges();
}
Of course, this needs the tenant provider to be injected into your context.
EF Core value generation on add with custom ValueGenerator
Generates values for properties when an entity is added to a context.
could be utilized to assign TenantId to the new entities. Inside the Next method you could obtain the TenantId from the context (or some service).
Taking your sample, the value generator could be a nested class inside your DbContext like this:
class TenantIdValueGenerator : ValueGenerator<int>
{
public override bool GeneratesTemporaryValues => false;
public override int Next(EntityEntry entry) => GetTenantId(entry.Context);
int GetTenantId(DbContext context) => ((YourDbContext)context)._tenantProvider.GetTenantId();
}
The all you need is to assign the generator to TenantId property using some of the HasValueGenerator fluent API.
The only problem is that by design the value generators are called only if the property does not have explicitly set value (for int property - if the value is 0).
So the better approach it to abstract (and fully control) the TenantId property by removing it from entity models and replacing it with shadow property.
Hence my suggestion is, remove the TenantId from entity classes and call the following method inside your OnModelCreating for each entity that needs TenantId column:
void ConfigureTenant<TEntity>(ModelBuilder modelBuilder) where TEntity : class
{
modelBuilder.Entity<TEntity>(builder =>
{
builder.Property<int>("TenantId")
.HasValueGenerator<TenantIdValueGenerator>();
builder.HasQueryFilter(e => EF.Property<int>(e, "TenantId") == _tenantProvider.GetTenantId());
});
}
If you are using EF Core 5+, then you have the option of using the SavingChanges event. This will allow you to set your custom logic for both SaveChanges and SaveChangesAsync without having to override both methods.
Example:
public MyDbContext(DbContextOptions<MyDbContext> dbContextOptions, ITenantProvider tenantProvider) : base(dbContextOptions)
{
SavingChanges += (sender, args) =>
{
foreach (var orderEntity in ChangeTracker.Entries<Order>())
{
if (orderEntity.State == EntityState.Added)
{
orderEntity.Entity.TenantId = tenantProvider.GetTenantId();
}
}
};
}

Get DbContext from Entity in Entity Framework Core

Is there a way to get an instance of the DbContext an entity is being tracked by (if any)?
I found the following suggestion/solution for EF6
Get DbContext from Entity in Entity Framework
public static DbContext GetDbContextFromEntity(object entity)
{
var object_context = GetObjectContextFromEntity( entity );
if ( object_context == null )
return null;
return new DbContext( object_context, dbContextOwnsObjectContext: false );
}
private static ObjectContext GetObjectContextFromEntity(object entity)
{
var field = entity.GetType().GetField("_entityWrapper");
if ( field == null )
return null;
var wrapper = field.GetValue(entity);
var property = wrapper.GetType().GetProperty("Context");
var context = (ObjectContext)property.GetValue(wrapper, null);
return context;
}
Is there a way to get this result in EF Core?
No. EF Core does not have lazy loading yet. If it had, then, a proxy generated from it would eventually have a reference to the DbContext that loaded it. As of now, there is no such reference.
One could use dependency injection on the instance/entity at creation. To allow the owning dbcontext to be retrieved from the entity later.
eg
class Book
{
public readonly DBContext _dbcontext;
public Book(DBContext dbcontext)
{
_dbcontext = dbcontext;
}
}
There is no good way to do this. There seems to be no easy way to inject any code into the process after an entity object is constructed but before it is enumerated through in the calling code.
Subclassing InternalDbSet was something I considered but you can only fix calls to the .Find methods and the IQueryable implementation (the main way you'd use a DbSet) is out of reach.
So the only option I can see left is to not allow access to the DbSet at all but have accessor functions which will set the .Owner (or whatever you want to call it) property for me. This is messy since you would normally have to write a function for every query type you'd want to make, and the caller couldn't use LINQ any more. But we can use generics and callbacks to preserve most of the flexibility though it looks ugly. Here is what I came up with.
I am working on porting and cleaning up a complex system so I am not in a position to really test this yet but the concept is sound. The code may need further tweaking to work as desired. This should not have any penalties with eg pulling down the entire table before processing any records as long as you use EnumerateEntities to enumerate, instead of QueryEntities, but again I have yet to do any real testing on this.
private void InitEntity(Entity entity) {
if (entity == null) {
return;
}
entity.Owner = this;
// Anything you want to happen goes here!
}
private DbSet<Entity> Entities { get; set; }
public IEnumerable<Entity> EnumerateEntities() {
foreach (Entity entity in this.Entities) {
this.InitEntity(entity);
yield return entity;
}
}
public IEnumerable<Entity> EnumerateEntities(Func<DbSet<Entity>, IEnumerable<Entity>> filter) {
IEnumerable<Entity> ret = filter(this.Entities);
foreach (Entity entity in ret) {
this.InitEntity(entity);
yield return entity;
}
}
public T QueryEntities<T>(Func<DbSet<Entity>, T> filter) {
if (filter is Func<DbSet<Entity>, Entity>) {
T ret = filter(this.Entities);
this.InitEntity(ret as Entity);
return ret;
}
if (filter is Func<DbSet<Entity>, IEnumerable<Entity>>) {
IEnumerable<Entity> ret = filter(this.Entities) as IEnumerable<Entity>;
// You should be using EnumerateEntities, this will prefetch all results!!! Can't be avoided, we can't mix yield and no yield in the same function.
return (T)ret.Select(x => {
this.InitEntity(x);
return x;
});
}
return filter(this.Entities);
}
public void QueryEntities(Action<DbSet<Entity>> filter) => filter(this.Entities);

ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value

In a nutshell the exception is thrown during POSTing wrapper model and changing the state of one entry to 'Modified'. Before changing the state, the state is set to 'Detached' but calling Attach() does throw the same error. I'm using EF6.
Please find my code below(model names have been changed to make it easier to read)
Model
// Wrapper classes
public class AViewModel
{
public A a { get; set; }
public List<B> b { get; set; }
public C c { get; set; }
}
Controller
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (!canUserAccessA(id.Value))
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
var aViewModel = new AViewModel();
aViewModel.A = db.As.Find(id);
if (aViewModel.Receipt == null)
{
return HttpNotFound();
}
aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList();
aViewModel.Vendor = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault();
return View(aViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(AViewModel aViewModel)
{
if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
if (ModelState.IsValid)
{
db.Entry(aViewModel.a).State = EntityState.Modified; //THIS IS WHERE THE ERROR IS BEING THROWN
db.SaveChanges();
return RedirectToAction("Index");
}
return View(aViewModel);
}
As shown above line
db.Entry(aViewModel.a).State = EntityState.Modified;
throws exception:
Attaching an entity of type 'A' failed because another entity of the
same type already has the same primary key value. This can happen when
using the 'Attach' method or setting the state of an entity to
'Unchanged' or 'Modified' if any entities in the graph have
conflicting key values. This may be because some entities are new and
have not yet received database-generated key values. In this case use
the 'Add' method or the 'Added' entity state to track the graph and
then set the state of non-new entities to 'Unchanged' or 'Modified' as
appropriate.
Does anybody see anything wrong in my code or understand in what circumstances it would throw such error during editing a model?
Problem SOLVED!
Attach method could potentially help somebody but it wouldn't help in this situation as the document was already being tracked while being loaded in Edit GET controller function. Attach would throw exactly the same error.
The issue I encounter here was caused by function canUserAccessA() which loads the A entity before updating the state of object a. This was screwing up the tracked entity and it was changing state of a object to Detached.
The solution was to amend canUserAccessA() so that the object I was loading wouldn't be tracked. Function AsNoTracking() should be called while querying the context.
// User -> Receipt validation
private bool canUserAccessA(int aID)
{
int userID = WebSecurity.GetUserId(User.Identity.Name);
int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
return (aFound > 0); //if aFound > 0, then return true, else return false.
}
For some reason I couldnt use .Find(aID) with AsNoTracking() but it doesn't really matter as I could achieve the same by changing the query.
Hope this will help anybody with similar problem!
Interestingly:
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
Or if you still is not generic:
_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);
seems to solved my problem smoothly.
It seems that entity you are trying to modify is not being tracked correctly and therefore is not recognized as edited, but added instead.
Instead of directly setting state, try to do the following:
//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a);
db.SaveChanges();
Also, I would like to warn you that your code contains potential security vulnerability. If you are using entity directly in your view model, then you risk that somebody could modify contents of entity by adding correctly named fields in submitted form. For example, if user added input box with name "A.FirstName" and the entity contained such field, then the value would be bound to viewmodel and saved to database even if the user would not be allowed to change that in normal operation of application.
Update:
To get over security vulnerability mentioned previously, you should never expose your domain model as your viewmodel but use separate viewmodel instead. Then your action would receive viewmodel which you could map back to domain model using some mapping tool like AutoMapper. This would keep you safe from user modifying sensitive data.
Here is extended explanation:
http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/
Try this:
var local = yourDbContext.Set<YourModel>()
.Local
.FirstOrDefault(f => f.Id == yourModel.Id);
if (local != null)
{
yourDbContext.Entry(local).State = EntityState.Detached;
}
yourDbContext.Entry(applicationModel).State = EntityState.Modified;
for me the local copy was the source of the problem.
this solved it
var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
if (local != null)
{
context.Entry(local).State = EntityState.Detached;
}
My case was that I did not have direct access to EF context from my MVC app.
So if you are using some kind of repository for entity persistence it could be appropiate to simply detach explicitly loaded entity and then set binded EntityState to Modified.
Sample (abstract) code:
MVC
public ActionResult(A a)
{
A aa = repo.Find(...);
// some logic
repo.Detach(aa);
repo.Update(a);
}
Repository
void Update(A a)
{
context.Entry(a).EntityState = EntityState.Modified;
context.SaveChanges();
}
void Detach(A a)
{
context.Entry(a).EntityState = EntityState.Detached;
}
Use AsNoTracking() where you are getting your query.
var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
I have added this answer only because the problem is explained based on more complex data pattern and I found it hard to understand here.
I created a fairly simple application. This error occurred inside Edit POST action. The action accepted ViewModel as an input parameter. The reason for using the ViewModel was to make some calculation before the record was saved.
Once the action passed through validation such as if(ModelState.IsValid), my wrongdoing was to project values from ViewModel into a completely new instance of Entity. I thought I'd have to create a new instance to store updated data and then saved such instance.
What I had realised later was that I had to read the record from database:
Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);
and updated this object. Everything works now.
I thought I'd share my experience on this one, even though I feel a bit silly for not realising sooner.
I am using the repository pattern with the repo instances injected into my controllers. The concrete repositories instantiate my ModelContext (DbContext) which lasts the lifetime of the repository, which is IDisposable and disposed by the controller.
The issue for me was that I have a modified stamp and row version on my entities, so I was getting them first in order to compare with the inbound headers. Of course, this loaded and tracked the entity that was subsequently being updated.
The fix was simply to change the repository from newing-up a context once in the constructor to having the following methods:
private DbContext GetDbContext()
{
return this.GetDbContext(false);
}
protected virtual DbContext GetDbContext(bool canUseCachedContext)
{
if (_dbContext != null)
{
if (canUseCachedContext)
{
return _dbContext;
}
else
{
_dbContext.Dispose();
}
}
_dbContext = new ModelContext();
return _dbContext;
}
#region IDisposable Members
public void Dispose()
{
this.Dispose(true);
}
protected virtual void Dispose(bool isDisposing)
{
if (!_isDisposed)
{
if (isDisposing)
{
// Clear down managed resources.
if (_dbContext != null)
_dbContext.Dispose();
}
_isDisposed = true;
}
}
#endregion
This allows the repository methods to re-new their context instance upon each use by calling GetDbContext, or use a previous instance if they so desire by specifying true.
I had this problem with local var and i just detach it like this:
if (ModelState.IsValid)
{
var old = db.Channel.Find(channel.Id);
if (Request.Files.Count > 0)
{
HttpPostedFileBase objFiles = Request.Files[0];
using (var binaryReader = new BinaryReader(objFiles.InputStream))
{
channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength);
}
}
else
channel.GateImage = old.GateImage;
var cat = db.Category.Find(CatID);
if (cat != null)
channel.Category = cat;
db.Entry(old).State = EntityState.Detached; // just added this line
db.Entry(channel).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(channel);
Problem causes of loaded objects with same Key, so first we will detach that object and do the the updating to avoid conflict between two object with the same Key
i mange to fix the issue by updating state. when you trigger find or any other query operation on the same record sate has been updated with modified so we need to set status to Detached then you can fire your update change
ActivityEntity activity = new ActivityEntity();
activity.name="vv";
activity.ID = 22 ; //sample id
var savedActivity = context.Activities.Find(22);
if (savedActivity!=null)
{
context.Entry(savedActivity).State = EntityState.Detached;
context.SaveChanges();
activity.age= savedActivity.age;
activity.marks= savedActivity.marks;
context.Entry(activity).State = EntityState.Modified;
context.SaveChanges();
return activity.ID;
}
I had a similar issue, after probing for 2-3 days found ".AsNoTracking" should be removed as EF doesn't track the changes and assumes there are no changes unless an object is attached. Also if we don't use .AsNoTracking, EF automatically knows which object to save/update so there is no need to use Attach/Added.
I encountered this error where
two methods, A & B, in a single controller both used the same instance of an ApplicationDbContext, and
method A called method B
private ApplicationDbContext db;
// api methods
public JsonResult methodA(string id){
Resource resource = db.Resources.Find(id);
db.Entry(resource).State = EntityState.Modified;
db.SaveChanges();
return methodB()
}
public JsonResult methodB(string id){
Resource resource = db.Resources.Find(id);
db.Entry(resource).State = EntityState.Modified;
db.SaveChanges();
return new JsonResult();
}
I changed method B to have a using statement and rely only on the local db2.
After:
private ApplicationDbContext db;
// api methods
public JsonResult methodA(string id){
Resource resource = db.Resources.Find(id);
db.Entry(resource).State = EntityState.Modified;
db.SaveChanges();
return methodB()
}
public JsonResult methodB(string id){
using (var db2 = new ApplicationDbContext())
{
Resource resource = db2.Resources.Find(id);
db2.Entry(resource).State = EntityState.Modified;
db2.SaveChanges();
}
return new JsonResult();
}
Similar to what Luke Puplett is saying, the problem can be caused by not properly disposing or creating your context.
In my case, I had a class which accepted a context called ContextService:
public class ContextService : IDisposable
{
private Context _context;
public void Dispose()
{
_context.Dispose();
}
public ContextService(Context context)
{
_context = context;
}
//... do stuff with the context
My context service had a function which updates an entity using an instantiated entity object:
public void UpdateEntity(MyEntity myEntity, ICollection<int> ids)
{
var item = _context.Entry(myEntity);
item.State = EntityState.Modified;
item.Collection(x => x.RelatedEntities).Load();
myEntity.RelatedEntities.Clear();
foreach (var id in ids)
{
myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id));
}
_context.SaveChanges();
}
All of this was fine, my controller where I initialized the service was the problem. My controller originally looked like this:
private static NotificationService _service =
new NotificationService(new NotificationContext());
public void Dispose()
{
}
I changed it to this and the error went away:
private static NotificationService _service;
public TemplateController()
{
_service = new NotificationService(new NotificationContext());
}
public void Dispose()
{
_service.Dispose();
}
Here what I did in the similar case.
That sitatuation means that same entity has already been existed in the context.So following can help
First check from ChangeTracker if the entity is in the context
var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList();
var isAlreadyTracked =
trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id);
If it exists
if (isAlreadyTracked)
{
myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity;
}
else
{
//Attach or Modify depending on your needs
}
I solve this problem with a "using" block
using (SqlConnection conn = new SqlConnection(connectionString))
{
// stuff to do with data base
}
// or if you are using entity framework
using (DataBaseEntity data = new DataBaseEntity)
{
}
Here is where I get the idea https://social.msdn.microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework?forum=vcses is in spanish (look for the second answer)
you can use added method like;
_dbContext.Entry(modelclassname).State = EntityState.Added;
but in many case if you want to use more than one model at that time this won't work because entity is already attached to another entity. So, at that time you can use ADDOrUpdate Entity Migration method which simply migrates object from one to another and as a result you wouldn't get any error.
_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);
Clear all State
dbContextGlobalERP.ChangeTracker.Entries().Where(e => e.Entity != null).ToList().ForEach(e => e.State = EntityState.Detached);
Reasons I've encountered this error:
Did not use .AsNoTracking() when querying for existing entities. Especially when calling a helper function to check permissions.
Calling .Include() on a query and then trying to edit the parent. Example: var ent = repo.Query<Ent>().Include(e=>e.Ent2).First(); ...repo.Edit(e.Ent2); repo.Edit(e); If I'm going to edit a nested object, I try to separate these into separate query calls now. If you can't do that, set the child object to null and iterate through lists, detaching objects like this
Editing an old entity in a Put web call. The new item is already added to the repo, so modify that one and have it be saved in super.Put(). Example of what will throw an error: public void Put(key, newItem){ var old = repo.Query<Entity>().Where(e=>Id==key).First(); ... repo.Edit(old); super.Put(key,newItem); ... }
Multiple helper functions edit the same entity. Instead of passing the ID as a parameter into each function, pass a reference to the entity. Error solved!
In my case , I had wrote really two times an entity of same type . So I delete it and all things work correctly
This problem may also be seen during ViewModel to EntityModel mapping (by using AutoMapper, etc.) and trying to include context.Entry().State and context.SaveChanges() such a using block as shown below would solve the problem. Please keep in mind that context.SaveChanges() method is used two times instead of using just after if-block as it must be in using block also.
public void Save(YourEntity entity)
{
if (entity.Id == 0)
{
context.YourEntity.Add(entity);
context.SaveChanges();
}
else
{
using (var context = new YourDbContext())
{
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges(); //Must be in using block
}
}
}

Categories