Unexpected Behaviour while trying to update database in consecutive requests - EF 6 - c#

I have been dealing with this for days
Summary
I am creating a Social site that will be the back bone for another web application. The hangup is when I submit a request to create a group all goes well, but if I attempt to submit this form again with different data I get a DbEntityValidationException. The exception is related to the ApplicationUser entry.
Details
When I start the Application in Debug mode and submit the Group creation form for the first time it will succeed, adding all the entities into the database as excepted. I have verified this and all looks good. While in the same Debug session, I change the information in the form, to create another group, and submit the form, which leads to the DbEntityValidationException.
The error is related the when I try to insert a SocialGroupMemberModel which contains a reference to the User, and other details related to the users status in the group. The User entry is being marked as added and EntityFramework is trying to insert the User instead of updating. I have attempted to set the Navigation (User) and set the ForeignKey (UserId), both lead to the same error.
I am using HttpContext.Current.GetOwinContext().Get<ApplicationDbContext>();
In the Controller I use ApplicationUserManager to get the User Entity, I then pass this to the Repository to create the group (in either case, either passing the ID, or Entity itself, doesn't work the second time)
Group Creation Code:
var groupInfo = new SocialGroupInfo
{
Created = DateTime.Now,
Description = model.Description,
ShortDescription = model.ShortDescription,
Name = model.Name,
Tags = TagRepo.GetTags(),
Members = new List<SocialGroupMember>()// { member }
};
var groupModel = new SocialGroupModel
{
Slug = model.Slug,
Info = groupInfo
};
Context.SocialGroups.Add(groupModel);
var member = new SocialGroupOwnerModel
{
Joined = DateTime.Now,
UserId = creator
//User = null
//Group = groupInfo
};
groupInfo.Members.Add(member);
//creator.SocialGroups.Add(member);
SaveChanges();
The Validation Error is: "User name ** is already taken" so this leads me to believe that on the second attempt to add the new group, it is attempting to add a new user.
Please ask for any additional information needed, thanks.

This issue was caused by the IoC holding a reference to the previous DbContext, unsure as to why, but removing all usage of Autofac fixed the issue.
Very anticlimactic solution, but issue fixed...
Now the issue is to figure out why Autofac was behaving this way, all Debugging showed that the classes were created each request... but that is another question.

Related

How can I retrieve all users that have been assigned to a Service Principal in one call with navigation properties

How can I retrieve all users within a Service Principal with navigation properties such as Manager and properties such as Full name using Microsoft Graph API in as few as possible calls?
I'm using the Microsoft.Graph package and I've tried:
Attempt 1:
await graphClient.ServicePrincipals["objectId"].AppRoleAssignedTo.Request().GetAsync();
But this gives me only the Ids, which is 'okay' but then I would have to get all users separately, which would make for a lot of calls to the Graph API.
I could use the DirectoryObjects to get all users at once but that does not allow me to expand on the Manager property.
For example:
var users = await graphClient.DirectoryObjects.GetByIds(principals.Select(p => p.PrincipalId.ToString()), new string[1] { "User" }).Request().Expand("manager").PostAsync();
Gives me a error:
Could not find a property named 'manager' on type 'microsoft.graph.directoryObject'.
Attempt 2:
I've also tried to get the users by using a filter
await graphClient.Users.Request().Filter("appRoleAssignments/any(u:u/principalId eq objectId)").Expand(u => u.Manager).GetAsync();
But that gives me a error:
Request_UnsupportedQuery
You can write the code like this:
var user = await graphClient.Users.Request().Filter("id in ('{objectId1}', '{objectId2}', ..., '{objectIdn}')").Expand(u => u.Manager).Select("displayName").GetAsync();
Ideally, this will use just one call to return the data you need.
BUT based on the requirement "in as few as possible calls", we will have to face a problem, that is, we need to put all the object ids into the request, which may cause the request to be too long and eventually fail.
I didn't test this scene and it's just a direction which may be helpful.
I still recommend that you use loop to get the information.

How do I fix "The user Id(s) is invalid" when creating an application user?

I'm automating the creation of an application user in Dynamics 365. The user does get created, but when I try to assign a role to it later in the code, I get this message: The user Id(s) [a guid here] is invalid. When I try assigning the user a role from the Dynamics web UI, I get a variant of the same error: The user ID associated with the current record is not valid..
I am using a CrmServiceClient in C# to connect to the organization.
I am supplying the following fields when creating the user programmatically:
firstname
lastname
businessunitid
applicationid
internalemailaddress
One thing I noticed is that when I look at the user via the web UI, the app id I assigned in the creation does not show up.
This is my current code:
private void SetUpPermissions()
{
var roleId = GetRoleId();
var userId = CreateApplicationUser();
CrmSvc.Associate(
"systemuser",
userId,
new Relationship("systemuserroles_association"),
new EntityReferenceCollection {
new EntityReference("role", roleId)
});
}
private Guid CreateApplicationUser() => CrmSvc.CreateNewRecord("systemuser", new Dictionary<string, CrmDataTypeWrapper>
{
{ "firstname", new CrmDataTypeWrapper(ProposalManagerApplicationName, CrmFieldType.String) },
{ "lastname", new CrmDataTypeWrapper("Application", CrmFieldType.String) },
{ "businessunitid", new CrmDataTypeWrapper(BusinessUnitId.Value, CrmFieldType.Lookup) },
{ "applicationid", new CrmDataTypeWrapper(ApplicationId, CrmFieldType.Key) },
{ "internalemailaddress", new CrmDataTypeWrapper("testcrm3#mydomain.com", CrmFieldType.String) }
});
I would expect this user to show up in the Web UI the same way an application user created via the UI would, but it does not. It does not show the application id I assigned to it in the code, and it throws the aforementioned error when trying to assign a role to it.
Am I doing something wrong? If the answer is backed up by existing documentation please point me to it as well so I can learn why I did not find it.
Your associate looks correct. I'd troubleshoot your code by attempting to associate a role to an already exiting user created normally. If that works, use the FetchXml Builder or some other method to compare the user you create and the user that is created via the UI for potential differences, and attempt to resolve those until you've determined what is causing your error.

Attempting to create ASP.Net Core Identity data in an existing table with existing user records

I have a new ASP.Net Core website that uses an existing database, to which
I have added ASP.Net Core Identity for user management.
Since there is an existing user table, I simply added the which I have properly added the ASP.Net identity columns.
The additional columns in the existing table are in my IdentityUser-derived class. The site logic works well when I create new users, as well as logging in, logging out, and the like.
The problem is that all of the existing 'user' records (which I will call MyUser) have blank Core Identity fields, and thus are invisible to the identity system calls.
I would like to write a utility that goes through the existing MyUser records, and if the AspIdentityId field is null, set it up as a record manageable by the Identity subsystem.
I'm essentially looking for the 'Create' call, but I can't use UserManager.
CreateAsync because this creates a new record in the table, it doesn't update an existing one.
I do have two DB contexts (IdentityDBContext and OriginalDBContext), so if there is something that allows me to generate the field data, I can then add this to the columns in the table for the associated record.
Ok, I got this to work, not entirely sure why it works, seems like the AspIdentity fields are populated before they are committed. I have only added the specific changes that were the crux of the question, the DI, setup of the contexts, etc are elsewhere in the project.
Essentially, for each 'domain' record, I create a new ApplicationUser (IdentityUser-derived) record, and add the values for the custom fields in the domain record into the ApplicationUser record. Once this object is created, it has the necessary Asp.Net Identity fields already populated. I then get those fields and populate them back onto the domain specific record, then I save this. Once that is done, I re-find the record (this may not be necessary), and add the password based on the plain text password already in the domain record.
var clients = new List<Client>(myService.myContext.Clients);
foreach (var client in clients)
{
var user = new ApplicationUser // Derived from IdentityUser
{
UserID = client.UserID,
FirstName = client.FirstName,
LastName = client.LastName,
UserName = client.UserName,
PhoneNumber = client.Phone,
Email = client.Email // Other fields omitted for brevity.
};
var idUser = user as IdentityUser;
client.AspIdentityId = user.Id;
client.ConcurrencyStamp = user.ConcurrencyStamp;
client.NormalizedEmail = user.NormalizedEmail;
client.NormalizedUserName = user.NormalizedUserName;
client.PasswordHash = user.PasswordHash;
client.SecurityStamp = user.SecurityStamp;
myService.myContext.SaveChanges();
// Can we just use the 'user' object here?
var newUser = await myService.UserManager.FindByIdAsync(client.AspIdentityId);
var result = await myService.UserManager.AddPasswordAsync(newUser, client.Password);
}
Hope this helps someone, this was important to me to get done this way.

FullAuditedEntity, ObjectMapper: CreatorUserId Null when Creating New Entry or Deleting

I have been posting data to a Full Audited Entity via the API. As it is FullAuditedEntity, it should automatically be created with creatorId, creationTime and a couple other column values. But when I checked in the database, CreatorUserID is null even though CreationTime is there. It should be 1 cos I posted with the default admin. Furthermore, when I delete the rows, the same happens: I can only see DeletionTime but not DeleterUserId.
Below is the data captured by the API Endpoints that I can see using breakpoints:
I experimented with two Object Mapping Methods creating output and output2 but both of them gives the same null value for CreatorUserId. By right, both CreatorUserId and CreationTime should have values by this stage.
[AbpAuthorize]
public async Task<Rule> CreateAsync(CreateRuleInput input)
{
Rule output = Mapper.Map<CreateRuleInput, Rule>(input);
Rule output2 = ObjectMapper.Map< Rule>(input);
return await _ruleManager.Create(output);
}
Is there anything wrong with my object mapping functions?
There is nothing wrong with your object mapping functions.
I experimented with two Object Mapping Methods... By right, both CreatorUserId and CreationTime should have values by this stage.
CreatorUserId and CreationTime only have values after SaveChanges() is called.
I have to put [TenantId] manually in the JSON post object, and it shows in the database.
You should not do that. CreatorUserId is only set if the entity belongs to the current tenant.
I can see UserId which is 1 in breakpoint. Somehow it gets ignored during mapping.
The proper way to create an entity as "Default" tenant admin, is to log in as the tenant admin.
UserId = 1 means you are logged in as host admin. "Default" tenant admin has UserId = 2.
If I don't specify tenantId or put 0, it gives Server Internal Error: 500.
If you want host to create Rule, make sure it implements IMayHaveTenant not IMustHaveTenant.
The problem is with the default Admin because it has no tenant.
From another tenant, I created a new user and post data using the new user's credentials. Then I post the data by leaving out tenantId in the post body. And it works just fine.
I used to specify tenantId in the post data object for the default Admin (Host tenant). If I don't specify tenantId or put 0, it gives Server Internal Error: 500. So, I have to specify tenantId in the post body object. I think because of that it somehow messes with the mappings inside the API because default Admin has no tenant.

ASP.NET Entity Framework trying to update single column

I am using ASP.NET Entity Framework and I am trying to update a single column with the following code:
[HttpGet]
public void MarkOffline(string online)
{
Users user = new Users { email = online, isOnline = false };
db.Entry(user).Property("isOnline").IsModified = true;
db.SaveChanges();
}
But I get this error:
Member 'IsModified' cannot be called for property 'isOnline' because
the entity of type 'Users' does not exist in the context. To add an
entity to the context call the Add or Attach method of DbSet<Users>.
The part I don't know how to do:
To add an entity to the context call the Add or Attach method of
DbSet<Users>.
How do I fix my problem?
If you want to update like this, you'll need to Attach the entity where the entity has its primary key set.
Given you don't have its primary key but only one of its (unique, hopefully) fields, you need to query the record first and then update it:
var existing = db.Users.FirstOrDefault(u => u.email == email);
existing.IsOnline = true;
db.SaveChanges();
That being said, this is not how you record a user's online status in a web application. You rather update a user's LastAction timestamp with each action they perform, and mark a user as offline at runtime when their LastAction is more than N seconds or minutes ago.

Categories

Resources