Entity Framework tracking issue with multiple objects of the same ID - c#

I'm learning.
I'm using Microsoft.EntityFrameworkCore in a .netcoreapp 2.1, the point is to expose static data through an API.
Currently, I have the data in a json object. Lets keep the examples simple, I'm trying to expose a list of sandwhiches and ingredients for those sandwhiches. Sandwhiches come in various type (bagels, long, short, etc.)
First of all, I'm not entirely sure that using EF is the correct tool, but as I'll have to manage those items later on (being able to order food) I started there. If there is a better tool for it I'm all ears, but for now my question is to expose that using EF.
I'm reading the json that is hardcoded in the app and using it as a starting point for my DbContext. I just deserialize it in my constructor and load it up in my context object, which would then be exposed through the API. Works like a charm with the todo-list template project.
Here's what it looks like, I just added more DBSets for my needs
public class EatupContext : DbContext
{
public DbSet<FoodType> Types { get; set; }
public DbSet<Ingredient> Ingredients { get; set; }
public DbSet<FoodItem> Items { get; set; }
}
FoodType is an int with an ID and a name. Same for Ingredients.
Items are the sandwhiches and look like this
public class FoodItem
{
public int Id { get; set; }
public string Name { get; set; }
public FoodType Type { get; set; }
public IEnumerable<Ingredient> Ingredients { get; set; }
}
In the json that I'm reading (which is mapped like the c# objects), all id's start at 0 for all objects. So Types go from 0 to 7, ingredients from 0 to 105, and food items from 0 to 60.
This is causing an id tracking issue for the entity framework, because there are multiple objets with the same ID. Even though they're in different DBSets, which is what confuses me. From my (flawed?) understanding, two tables (DBSets?) can have duplicate id's. I can have a sandwich id 0, of type 0, with ingredients 0, 1, 2 and 3. It would seem even more confusing to me to have types going from 0 to 7, then ingredients from 8 to 113, and sandwiches from 114 to 174. That'd be just really odd to me in a database point of view.
Here is the exact error I am getting :
An unhandled exception occurred while processing the request.
InvalidOperationException: The instance of entity type 'Ingredient' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.
When attaching existing entities, ensure that only one entity instance with a given key value is attached.
Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.ThrowIdentityConflict(InternalEntityEntry entry)
With the following stacktrace :
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.ThrowIdentityConflict(InternalEntityEntry entry)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.Add(TKey key, InternalEntityEntry entry, bool updateDuplicate)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, bool acceptChanges)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, bool acceptChanges, Nullable<EntityState> forceStateWhenUnknownKey)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node, bool force)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph<TState>(EntityEntryGraphNode node, TState state, Func<EntityEntryGraphNode, TState, bool> handleNode)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph<TState>(EntityEntryGraphNode node, TState state, Func<EntityEntryGraphNode, TState, bool> handleNode)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState entityState, bool forceStateWhenUnknownKey)
Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
Microsoft.EntityFrameworkCore.DbContext.SetEntityStates(IEnumerable<object> entities, EntityState entityState)
Microsoft.EntityFrameworkCore.DbContext.UpdateRange(IEnumerable<object> entities)
Microsoft.EntityFrameworkCore.Internal.InternalDbSet<TEntity>.UpdateRange(IEnumerable<TEntity> entities)
EatUp.Backend.Controllers.EatupController..ctor(EatupContext context) in EatupController.cs
+
_context.Items.UpdateRange(completeModel.Items);
lambda_method(Closure , IServiceProvider , object[] )
Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider+<>c__DisplayClass4_0.<CreateActivator>b__0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider+<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
And this happens in the controller, in the following code :
private readonly EatupContext _context;
public EatupController(EatupContext context)
{
_context = context;
var completeModel = JsonConvert.DeserializeObject<EatUpDataModel>(EatUpDataSet.Complete);
_context.Items.UpdateRange(completeModel.Items); //fails here
_context.Types.UpdateRange(completeModel.Types);
_context.Ingredients.UpdateRange(completeModel.Ingredients);
_context.SaveChanges();
}
It also fails if I use AddRange ; I'm using Update so I dont have to check if the set is empty or not, I just erase it with the latest data from the json.
I'm not sure what approach I should take from this, I really don't want to edit that json manually, but I don't see how I can tell EF that those are separate objects other than what I'm already doing.
EDIT:
I have edited all my id's manually to have only unique ones, and I still get the error.
The only time an ID appears twice, is when the same ingredient is used in different sandwiches, which should be acceptable.
Now i'm 200% confused, what am I missing ?

Generally you shouldn't need to tell EF that those are separate objects. It should work it out. Can you copy and paste your migration?

Related

EF Core 5 - executing stored procedure using FromSqlRaw results in "An items with the same key has already been added"

Pulling my hair out on this one. I am executing SQL Server stored procedures using FromSqlRaw in various places of my code and all its working... except one.
This is the structure of the data returned from that stored procedure:
[site_no] [missing_date] [missing_reason] [area_no]
I have a class for this data:
public class MissingData
{
public short site_no { get; set; }
public DateTime missing_date { get; set; } = DateTime.MinValue;
public string missing_reason { get; set; } = string.Empty;
public int area_no { get; set; }
}
In my context I have this.
public DbSet<MissingData> MissingData { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MissingData>().HasNoKey();
}
and the code to execute it all is this
var dbSet = connection.Set<MissingData>();
var items = dbSet.FromSqlRaw(sql).ToList(); // SQL is string with query
I get an exception on the last line of code:
An item with the same key has already been added. Key: site_no
Any ideas?
UPDATE:
In regards to primary keys of the table. Both site_no and area_no are primary keys of their respected tables. The SQL does a join from 2 tables. So I did try this
modelBuilder.Entity<MissingData>().HasKey(x => new {x.site_no, x.area_no});
This made no difference. I have even edited the stored procedure to create a temporary table with no primary keys. Fill the table and then return this table. Still the same error.
Exception Stack trace, no inner exceptions
at System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException[T](T key)
at System.Collections.Generic.Dictionary2.TryInsert(TKey key, TValue value, InsertionBehavior behavior) at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable1 source, Func2 keySelector, Func2 elementSelector, IEqualityComparer1 comparer) at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.BuildIndexMap(IReadOnlyList1 columnNames, DbDataReader dataReader) at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.Enumerator.InitializeReader(DbContext _, Boolean result)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func3 operation, Func3 verifySucceeded)
at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.Enumerator.MoveNext() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)
An example of data returned
[site_no] [missing_date] [missing_reason] [area_no]
[56] [2021-06-10 00:00:00] [FTP From Site Failed] [53]
[56] [2021-06-10 00:00:00] [FTP From Site Failed] [58]
[56] [2021-06-10 00:00:00] [FTP From Site Failed] [3]
[56] [2021-06-10 00:00:00] [FTP From Site Failed] [55]
[9] [2021-06-10 00:00:00] [FTP From Site Failed] [11]
From the stack trace, I think the error is throw by EF Core from :
GitHub source :
public static int[] BuildIndexMap(IReadOnlyList<string> columnNames, DbDataReader dataReader)
{
var readerColumns = Enumerable.Range(0, dataReader.FieldCount)
.ToDictionary(dataReader.GetName, i => i, StringComparer.OrdinalIgnoreCase);
...
The error isn't about a duplicate row, but a duplicate column name. Maybe you stored procedure return two (or more) column with the name "site_no".

stoping ef-core to insert referenced record in to master table

I have a setup as follows.
Supermaster has a collection of Master.
Master having FK reference to Reference PK Id
Reference table has readonly reference data.
In ef code when i load records from Supermaster table i add reference to each Master based on some condition.
on submission of Supermaster i expect Supermaster to be saved with all Master with reference to Reference.
But on dbContext.SaveChanges() EF tries to insert record to Reference and fails with PK constrain.
What i have tried till now
I have tried creating one to one relationship with foreign Key using Fluent API.
I have tried Detaching Entity before saving the context
_context.Entry(programmingRecord.ProgrammingRecordParameters.Select(x=>x.ValidationRule)).State = EntityState.Detached;
.
Here is code.
Entity
Public class Supermaster
{
public virtual ICollection<Master> ProgrammingRecordParameters { get; set; }
}
public class Reference
{
public int Id { get; set; }
public string Description { get; set; }
}
public class Master
{
public int Id { get; set; }
public string DataGroup { get; set; }
[ForeignKey("ValidationRuleId")]
public Reference ValidationRule { get; set; }
public int? ValidationRuleId { get; set; }
}
API
private void AddParameter(
Supermaster rec,
Master programmableParameter)
{
var param = new Master
{
DataGroup = programmableParameter.DataGroup,
ValidationRule = _context.References.FirstOrDefault(x=>x.Description==programmableParameter.DataGroup
};
rec.ProgrammingRecordParameters.Add(param);
}
public IActionResult PostProgrammingRecord([FromBody] Master programmingRecord)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var repo = new ProgrammingRepository(_context);
_context.ProgrammingRecords.Add(programmingRecord);
_context.SaveChanges();
}
Following is the Error stack
HResult=0x80131500
Message=An error occurred while updating the entries. See the inner exception for details.
Source=Microsoft.EntityFrameworkCore.Relational
StackTrace:
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(DbContext _, ValueTuple`2 parameters)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IReadOnlyList`1 entries)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList`1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at RadioTMS.Web.Controllers.API.ProgrammingController.PostProgrammingRecord(ProgrammingRecord programmingRecord) in D:\TMS_Git_Repo\radio-tms\RadioTMS.Web\Controllers\API\ProgrammingController.cs:line 181
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
Inner Exception 1:
SqlException: Cannot insert explicit value for identity column in table 'ValidationRules' when IDENTITY_INSERT is set to OFF.
EF is trying to insert record in reference table because entity context does not have reference data loaded.You should get record of reference loaded in context before calling save changes.

EF Core, table-per-hierarchy and referencing entity of the same base class generates error

We have contacts are stored via table-per-hierarchy,
A contact is either a company or a person and a person always belongs to a company.
Both inherit from contact.
EF Core 2.1 is used.
It looks like this
public abstract class Contact {
public virtual Guid Id { get; set; }
public virtual ICollection<Source> Sources{ set; get; } = new Collection<Source>();
}
public class Company : Contact {
public string CompanyName { set; get; }
public virtual ICollection<Person> People { set; get; } = new Collection<Person>();
}
public class Person: Contact {
public string Name { set; get; }
public DateTime Birthday { set; get; }
public virtual Company Company { set; get; }
}
So far so good, what we now want to do is query the Sources and include the contacts (all of them, doesn't matter if person or company)
context.Contact.Include(c => c.Contact).FirstOrDefault();
This generates the following exception
Unable to cast object of type 'System.DateTime' to type 'System.Nullable``1[System.Guid]'.'
StackTrace
at lambda_method(Closure , ValueBuffer )
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalMixedEntityEntry..ctor(IStateManager stateManager, IEntityType entityType, Object entity, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryFactory.NewInternalEntityEntry(IStateManager stateManager, IEntityType entityType, Object entity, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTrackingFromQuery(IEntityType baseEntityType, Object entity, ValueBuffer& valueBuffer, ISet`1 handledForeignKeys)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryBuffer.StartTracking(Object entity, IEntityType entityType)
at lambda_method(Closure , QueryContext , AdSourceCrm , Object[] )
at Microsoft.EntityFrameworkCore.Query.Internal.IncludeCompiler._Include[TEntity](QueryContext queryContext, TEntity entity, Object[] included, Action`3 fixup)
at lambda_method(Closure , TransparentIdentifier`2 )
at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
at lambda_method(Closure )
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ResultEnumerable`1.GetEnumerator()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._TrackEntities[TOut,TIn](IEnumerable`1 results, QueryContext queryContext, IList`1 entityTrackingInfos, IList`1 entityAccessors)+MoveNext()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Boolean& found)
at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass15_1`1.<CompileQueryCore>b__0(QueryContext qc)
at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
at EfTest.Program.Main(String[] args) in Program.cs:line 18
We already spent hours (more like days) trying to get to the root of the cause and a fix but to no avail.
EF Core somehow trips with our construct (maybe the construct itself is wrong)
If you disable LazyLoading the problem disappears.
Any idea what's the cause and what would fix this issue?
EDIT 2
I remove Sources since they don't seem to be involved in the problem
EDIT 3
I added a sample repo
Just change the ConnectionString in DbContextFactory to your local path.
I was able to reproduce it with the provided repo in both EF Core 2.1.4 and 2.2 preview.
As you mentioned in the last update, the problem is somehow related to lazy loading (proxies?), because w/o UseLazyLoadingProxies() the code works as expected (that's why I wasn't able to reproduce it initially).
Since this is apparently EF Core bug, there is nothing you can do than reporting it to the EF Core Issue Tracker and wait to be fixed. Unfortunately mist likely you won't get that included in the upcoming 2.2 release, but who knows, it's worth trying.
Seems to me like a bug in Entity Framework, you can however prevent this from happening by explicitly specifying the auto-generated properties, for example add the CompanyId property to the Person class explicitly
That will prevent the faulty mapping of the Birthday column to the CompanyId column done by EF Core
public class Person : Base
{
// ...
public virtual Company Company { set; get; }
public Guid? CompanyId { get; set; }
}

identitysever and AspNetUsers

I have a set up where I have
services.AddIdentity<AppUser, AppRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserManager<userManage>()
.AddDefaultTokenProviders();
where AppUser and AppRole is used, but it seems to fail because of that. I keep getting
ArgumentNullException: Value cannot be null. Parameter name: type for the claims at System.Security.Claims.Claim..ctor
after
Microsoft.AspNetCore.Identity.IdentityUserClaim1.ToClaim()
Whole log at the bottom
Everything was working before I introduced the extension for the IdentityUser and IdentityRole
for the IDS4 set up I have:
services.AddIdentityServer(options => {
options.UserInteraction.LoginUrl = "/Account/Login";
})
.AddSigningCredential(new X509Certificate2(Path.Combine(".", "certs"
, "IdentityServer4Auth.pfx")))
.AddAspNetIdentity<AppUser>()
.AddConfigurationStore(options => {
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connection_string, sql => sql.MigrationsAssembly(migrations_assembly));
})
.AddOperationalStore(options => {
//options.DefaultSchema = "token";
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connection_string, sql => sql.MigrationsAssembly(migrations_assembly));
})
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddJwtBearerClientAuthentication()
.AddProfileService<IdentityProfileService>();
that was working fine, but the switch from
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{ })
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddDistributedMemoryCache();
to
services.AddIdentity<AppUser, AppRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserManager<userManage>()
.AddDefaultTokenProviders();
and that kills it now. I know it works as far as the AppUser, AppRole, and userManage are set up because they the same set up used in many of my apps, but as soon as it is mix with the IDS4 it is now failing. When it was working i had extended the IdentityUser
public class ApplicationUser : IdentityUser {}
The was working too, it was when I mixed the 2 apps together, so both had the AppUser, AppRole, and userManage, is when it went bad. I will put the models below
Side note too, I have followed the logs, and what is killing me here is the query run in the DB is correct. When I run it I see no nulls for any type values. I have even scrubbed the DB for any nulls in any of the claim areas, like the roles or user level just to be safe. I have put a break point on the area that causes the fault,
var signin_result = await _signInManager.PasswordSignInAsync(_user, test, model.RememberMe, false);
which when I look at the _user I see that it has the security stamp and all values correctly filled
it is on the user claims
SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId]
FROM [AspNetUserClaims] AS [uc]
WHERE [uc].[UserId] = #__user_Id_0
but I don't see where the issue is when that returns nothing with a null
the log
2018-10-08 13:17:47.164 -07:00 [Information] Entity Framework Core "2.1.4-rtm-31024" initialized '"ApplicationDbContext"' using provider '"Microsoft.EntityFrameworkCore.SqlServer"' with options: "SensitiveDataLoggingEnabled "
2018-10-08 13:17:47.189 -07:00 [Information] Executed DbCommand ("1"ms) [Parameters=["#__user_Id_0='11325643' (Size = 450)"], CommandType='Text', CommandTimeout='30']"
""SELECT [uc].[Id], [uc].[ClaimType], [uc].[ClaimValue], [uc].[UserId]
FROM [AspNetUserClaims] AS [uc]
WHERE [uc].[UserId] = #__user_Id_0"
2018-10-08 13:17:47.370 -07:00 [Error] An exception occurred in the database while iterating the results of a query for context type '"test.app.Data.ApplicationDbContext"'."
""System.ArgumentNullException: Value cannot be null.
Parameter name: type
at System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue)
at System.Security.Claims.Claim..ctor(String type, String value)
at Microsoft.AspNetCore.Identity.IdentityUserClaim`1.ToClaim()
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IShaper<TOut>.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.BufferlessMoveNext(DbContext _, Boolean buffer, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNext(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)"
System.ArgumentNullException: Value cannot be null.
Parameter name: type
at System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue)
at System.Security.Claims.Claim..ctor(String type, String value)
at Microsoft.AspNetCore.Identity.IdentityUserClaim`1.ToClaim()
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.IShaper<TOut>.Shape(QueryContext queryContext, ValueBuffer& valueBuffer)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.BufferlessMoveNext(DbContext _, Boolean buffer, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNext(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)
2018-10-08 13:17:48.680 -07:00 [Information] Executed action "WSU.Sso.Controllers.AccountController.Login (test.app)" in 2022.4589ms
The Models, and set up (note in the none IDS4 app these are working just fine)
public class AppUser : IdentityUser {}
public class AppRole : IdentityRole {}
public class AppUserClaim : IdentityUserClaim<string> {}
public class AppUserRole : IdentityUserRole<string> {}
public class AppRoleClaims : IdentityRoleClaim<string> {}
public partial class CoreDbContext : IdentityDbContext
<
AppUser, // TUser
AppRole, // TRole
string, // TKey
AppUserClaim, // TUserClaim
AppUserRole, // TUserRole,
IdentityUserLogin<string>, // TUserLogin
AppRoleClaims, // TRoleClaim
IdentityUserToken<string> // TUserToken
>//, ICoreDbContext
{
//etc..
}
Update
I believe the issue is here, https://github.com/IdentityServer/IdentityServer4.AspNetIdentity/blob/dev/src/UserClaimsFactory.cs where UserManager<TUser> userManager needs to be userManage because I extended that. I am guessing I need to role my own it seems?
Update 2
Turns out, after removing the userManage extending UserManager<TUser> that it still fails with
System.Security.Claims.Claim..ctor(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject, string propertyKey, string propertyValue)
System.Security.Claims.Claim..ctor(string type, string value)
Microsoft.AspNetCore.Identity.IdentityUserClaim.ToClaim()
There was hope that simplifying it would be the anwser so i didn't have to rewrite the .AddAspNetIdentity<AppUser>() but that didn't work. It is clear that it is not something that comes from the DB that is null, but must be something added that it's value is null. I can't tell what, just that it must be between the Microsoft.AspNetCore.Identity.IdentityUserClaim<TKey>.ToClaim() and the IdentityServer4.AspNetIdentity.UserClaimsFactory<TUser>.CreateAsync(TUser user) in UserClaimsFactory.cs ... What I can say for sure is that in https://github.com/IdentityServer/IdentityServer4.AspNetIdentity/blob/dev/src/UserClaimsFactory.cs all of the parts that is is trying to set is verifaied in the DB as not null.
It is also worth nothing that the IDS4 was working before I added the the custom role. I am not 100% sure that is not still in play here.
Side note
So I am left wondering, as i rule things out, if it is the name of the ID that is the issue here. In the update the tables for AspNetUser has the table set as user_id not Id (Microsoft Owin Security - Claims Constructor Value cannot be null is what is leading me to think about this). With that said, I believe there is nothing wrong with the set up, here is what I have in my OnModelCreating(ModelBuilder builder),
builder.Entity<AppUser>().Property(p => p.Id).HasColumnName("user_id");
We have not had any issue up to this point on using it, but I'm running down all differences in the user that would be an issue root.
Update 3
after stepping through public class IdentityUserClaim<TKey> where TKey : IEquatable<TKey> and putting a break point on Claim ToClaim() the type is the null. Which is a duh on the log, but I can't seem to back track in the call stack where that roots from. My question sits at, if the DB query returns a proper set of types and values, then why is there a null type and value set to process first? The very first time that breakpoint is hit, the type is null.
UPDATE 4 major stop
after hitting every break point I am now stuck and wounder if i am hitting a bug here. I get in to the UserManager and follow the stack and can see that the main claims are there and ok.
And then the next part is to run the claims store which executes the query above, and at that point is when it fails. I can't see why. I run the query in SQL Manager and it is ok, not one NULL.
Does anyone see what is going on? I am at a loss at this moment.
UPDATE 5 - narrowing down
So failing to notice the locals when i hit the claim being set as null, I see that the values I want are in the scope of this
The issue here is now how is the scope off and I have the values wanted in the wrong place
The issue took a bit to walk around, but it is clear now why there was a failing. AppUserClaim is the real issue. It was hard to tell, and over looking the locals at that step, is why I had to go the long way around here.
The extended IdentityUserClaim<string> I had was
public class AppUserClaim : IdentityUserClaim<string>
{
public int Id { get; set; }
/// <summary>
/// Gets or sets the primary key of the user associated with this claim.
/// </summary>
public virtual string UserId { get; set; }
public virtual AppUser user { get; set; }
/// <summary>
/// Gets or sets the claim type for this claim.
/// </summary>
public virtual string ClaimType { get; set; }
/// <summary>
/// Gets or sets the claim value for this claim.
/// </summary>
public virtual string ClaimValue { get; set; }
}
But the properties of ClaimType and ClaimValue where not needing to be overwrote. Once I had done that, it set the values where they where out of scope for the methods. Changing the model to be
public class AppUserClaim : IdentityUserClaim<string> {
public virtual AppUser user { get; set; } // note this is the reason why i had to extend
}
Solves the issue. It did't appear in the other apps because claims where set from the SSO, so that method was always skipped.
Lessons for me to remember, always make sure you look at the locals when stepping through. It took me stepping away for a day before I saw the duh moment.

Unable to Set Foreign Key to Null

I have an optional foreign key that I'm attempting to set to null. No matter what I've tried, on SaveChanges(), the update statement sets the foreign key to the previous value instead of null.
Simplified Child Class:
public class Child
{
[Key, Column(Order = 0), ScaffoldColumn(false)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("Parent")]
public int? ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
Simplified Parent Class:
public class Parent
{
[Key, Column(Order = 0), ScaffoldColumn(false)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
Things I've tried:
Load the Child object and set the ParentId null and set the Parent to null
Load the Child object and set the ParentId null and force the entity state to be modified
Load the Child object including the Parent object, then setting the values to null and forcing the entity state to be modified
Load the Parent object, then the Child object and .Remove(child) from the Parent object
Load the Parent object, then the Child object and .Remove(child) from the Parent and set the Child.ParentId to null and the Child.Parent to null.
Currently I have:
public void RemoveChildFromParent(int childId, int parentId)
{
Parent parent = _context.Parents.Include(x => x.Children).FirstOrDefault(u => u.Id == parentId);
Child child = parent.Children.SingleOrDefault(u => u.Id == childId);
parent.Children.Remove(child);
child.ParentId = null;
child.Parent = null;
child.StateOfEntity = StateOfEntity.Modified;
_context.ApplyStateChanges();
_context.SaveChanges();
}
On Save Changes, the SQL Update Statement still sets the ParentId on the Child object to the old value and I get this error:
System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
Source=System.Data.Entity
StackTrace:
at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
at Insight.DataLayer.InsightContext.SaveChanges()
at Insight.DataLayer.ChildRepository.RemoveChildFromParent(Int32 childId, Int32 parentId)
at Insight.BusinessLayer.ParentManager.RemoveChild(Int32 id, Int32 parentId)
at Insight.PresentationLayer.Controllers.ParentController.RemoveChild(Int32 id, Int32 parentId)
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()
InnerException:
Also, not sure if it matters, but I have LazyLoadingEnabled = false and AutoDetectChangesEnabled = false.
I'm not sure if there is an "elegant" solution to this (maybe changing the table structure?), but instead of spending more time on this small issue, I've decided to use DbContext.Database.ExecuteSqlCommand() and manually write the update statement.
It definitely feels like a work around in the Entity Framework methodology, but it's limited to this scenario, takes little time to do, and works as intended.
Also, not sure if it matters, but I have ... AutoDetectChangesEnabled
= false.
Yes, it matters. Did you disable auto change detection by default (in your context constructor for example)? That is dangerous because you have to know and understand when you need to call change detection manually if you disable the automatic detection - which is not a trivial thing. Usually AutoDetectChangesEnabled = false should only be set if you are sure that it doesn't cause unexpected results and when you really need it (normally for performance reasons when running bulk operations with many entity updates, inserts or deletes). I would definitely leave in at true by default.
I don't know what _context.ApplyStateChanges exactly does (seems to be a custom method) but all other lines of your code after the query don't call any EF methods until SaveChanges (and the 5 described procedures neither) which is exactly one of the situations (as described in the linked blog post above) where disabling automatic change detection does not work without further care.
To fix the problem, you could try to call _context.DetectChanges(); in your code snippet before SaveChanges (or maybe before ApplyStateChanges). However, the procedure to load the parent with all children is far to expensive and the simplest solution would be just loading the child, set the FK to null and save the changes - all this with enabled automatic change detection:
using (var context = new MyContext())
{
// as said, the following line should be your default
context.Configuration.AutoDetectChangesEnabled = true;
var child = context.Children.SingleOrDefault(c => c.Id == childId);
if (child != null)
{
child.ParentId = null;
context.SaveChanges();
}
}

Categories