MetaFields are only part of the Commerce backing data storage. You'll have meta fields for the commerce customer with a number out of the box and the ability to define your own.
AspNetUsers is part of the asp.net identity table, there's no meta fields here this is down to the user data type which you use for identity. You'd just update the values that are properties of the user data type you use for your user record.
See
https://www.yogihosting.com/aspnet-core-identity-create-read-update-delete-users/
So you'll need to update both if you want to keep them in sync when changes happen but I usually just only keep commerce as the source of truth for non standard values
As far as username goes it should be something like
// get user object from the storage
var user = await userManager.FindByIdAsync(userId);
// change username and email
user.Username = "NewUsername";
user.Email = "New@email.com";
// Persiste the changes
await userManager.UpdateAsync(user);
Thanks Scott, however I'm not sure you have answered one of my questions:
However, user doesn't expose any of my new MetaField values: Gender, Salutation & Username? How do I access (and update) database fields that I have added myself?
After defining my own new fields for the [dbo].[cls_Contact] table, how do I then interact with those fields programmatically? At present, running the following code doesn't expose my new fields. I would expect to see Gender, Salutation & Username appear:
For the custom fields, you can access them by
contact["FieldName"] = "FieldValue";
Thanks Quan, will that update the database straight away or do you then need to run a save method after?
Hi again Quan, I am having issues updating a user from the [dbo].[cls_Contact] table.
I followed one of your blogs on how to grab a specific user from the table via their email address (annoyingly, I can't find it now). I now need to update some of the values of the user and then save the changes.
In your earlier comment you mention calling 'AcceptChanges()' but I can't see where that method is? You can see my attempt below with 'SaveChanges()' but I get the following error. Any thoughts?
nathano, are you using aspnet identity? Why not grab the commerce user from from ApplicationUserManager.FindByEmail instead?
inject ApplicationUserManager<SiteUser> into your ctor then call FindByEmail(). Try saving then.
Surjit, I need to update the [dbo].[cls_Contact] table specifically and update the fields. This is not using ASP.NET Identity because that does not give me access to the table.
In my simplified approach below I have take the UserId from a default user that comes with Commerce. You can see the highlighted user below from [dbo].[cls_Contact]
If I debug my code I can:
However, when I invoke SaveChanges() I keep receiving an 'Object reference not set to an instance of the object' error. Why does this keep happening? What is not being instantiated?
var placeholderEmail = "admin@example.com";
var placeholderId = new Guid("4DC8C10F-1902-42DD-874C-CA6684E327EC");
var placeholderAddress = CustomerAddress.CreateInstance();
var context = CustomerContext.Current.GetContactById(placeholderId);
context.LastName = "sampleName";
// the below fields have been added via Initialization Modules
context["AcademicTitle"] = "Dr.";
context["Salutation"] = "Mr";
// I have even added placeholder values below to avoid the error
context.PreferredBillingAddress = placeholderAddress;
context.PreferredShippingAddress = placeholderAddress;
context.SaveChanges();
I honestly cant see anything out of the ordinary with the code you provided.
The problem is how you are trying to create the address. Please see here for example on how to add new address to customer. Also note you should save the contact first before adding the address.
https://github.com/episerver/Foundation/blob/main/src/Foundation/Infrastructure/Commerce/Install/Steps/AddCustomers.cs#L396
Mark, I have been investigating the CustomerService class in Foundation and, more specifically, CreateFoundationContact()
and the SetPreferredAddresses()
methods to try and resolve this and I can see that they follow your guide on setting the addresses after the contact has been saved.
I have attempted to recreate this locally but I am still recieving the same error. The error arrives when I try to invoke SaveChanges().
You can see my code below:
private void CreateCommerceUser()
{
var contact = CommerceContact.New();
contact.FirstName = "Fname";
contact.LastName = "Lname";
contact.Email = "test@test.com";
contact.UserId = "test@test.com";
contact.RegistrationSource = "StartupUser";
// error arrives when the below is invoked: 'Object not set to an instance of an object'
contact.SaveChanges();
SetPreferredAddresses(contact.Contact);
}
// at present this code is never run
private void SetPreferredAddresses(CustomerContact contact)
{
var changed = false;
var publicAddress = contact.ContactAddresses.FirstOrDefault(a => a.AddressType == CustomerAddressTypeEnum.Public);
var preferredBillingAddress = contact.ContactAddresses.FirstOrDefault(a => a.AddressType == CustomerAddressTypeEnum.Billing);
var preferredShippingAddress = contact.ContactAddresses.FirstOrDefault(a => a.AddressType == CustomerAddressTypeEnum.Shipping);
if (publicAddress != null)
{
contact.PreferredShippingAddress = contact.PreferredBillingAddress = publicAddress;
changed = true;
}
if (preferredBillingAddress != null)
{
contact.PreferredBillingAddress = preferredBillingAddress;
changed = true;
}
if (preferredShippingAddress != null)
{
contact.PreferredShippingAddress = preferredShippingAddress;
changed = true;
}
if (changed)
{
contact.SaveChanges();
}
}
And then, my CommerceContact class is below and has been influenced by the FoundationContact class.
public class CommerceContact
{
public CommerceContact() => Contact = new CustomerContact();
public CommerceContact(CustomerContact contact) => Contact = contact ?? new CustomerContact();
public CustomerContact Contact { get;}
public Guid ContactId
{
get => Contact?.PrimaryKeyId ?? Guid.Empty;
set => Contact.PrimaryKeyId = new PrimaryKeyId(value);
}
public string FirstName
{
get => Contact.FirstName;
set => Contact.FirstName = value;
}
public string LastName
{
get => Contact.LastName;
set => Contact.LastName = value;
}
public string FullName
{
get => Contact.FullName;
set => Contact.FullName = value;
}
public DateTime? BirthDate
{
get => Contact.BirthDate;
set => Contact.BirthDate = value;
}
public string Email
{
get => Contact.Email;
set => Contact.Email = value;
}
public CommerceOrganization CommerceOrganization
{
get => Contact != null && Contact.ContactOrganization != null ? new CommerceOrganization(Contact.ContactOrganization) : null;
set => Contact.OwnerId = value.OrganizationEntity.PrimaryKeyId;
}
//public string UserLocationId
//{
// get => Contact.GetStringValue("UserLocation";
// set => Contact[Constant.Fields.UserLocation] = value;
//}
public string UserId
{
get => Contact.UserId;
set => Contact.UserId = $"String:{value}";
}
public string RegistrationSource
{
get => Contact.RegistrationSource;
set => Contact.RegistrationSource = value;
}
public bool AcceptMarketingEmail
{
get => Contact.AcceptMarketingEmail;
set => Contact.AcceptMarketingEmail = value;
}
public DateTime? ConsentUpdated
{
get => Contact.ConsentUpdated;
set => Contact.ConsentUpdated = value;
}
public void SaveChanges() => Contact.SaveChanges();
public static CommerceContact New() => new CommerceContact(CustomerContact.CreateInstance());
}
Nathano, I copy / pasted your code minus setting the address and it works just fine. Saves it to the database.
I suspect your issue isn't the code. I executed yours from a Home Controller, so I know all the commerce context has loaded up.
Where are you executing yours? mvc controller? schedule job? maybe a http endpoint?
Surjit, I had the code in its own `UserManager` class, so not in a Controller. I just tried the below code in an action method within a controller and I still get the error. Am I missing some additional setup to make this work? How do I know whether my 'commerce context' has loaded up correctly? Really bizarre.
public IActionResult UpdatePersonalDetails()
{
Guid contactId = Guid.NewGuid();
CustomerContact contact = CustomerContact.CreateInstance();
contact.FirstName = "Fname";
contact.LastName = "Lname";
contact.Email = "test@test.com";
contact.UserId = "test@test.com";
contact.RegistrationSource = "StartupUser";
contact.SaveChanges();
return Ok();
}
Hmm...well for starters you said you have an ecommerce project...can I assume it's LIVE? and also have other commerce features working? Such as pulling catalogue product info, adding to cart and checking out?
If the answer is yes then maybe we need to look at anything custom you've added to the CustomerContact object to see if anything is marked as required.
if the answer is no then I would look at the Startup.cs and compare it to Foundation. Make sure you have all the services called.
In your first post you make a reference to AspNetUsers which implies you have identity installed. In the startup.cs you may need to make sure the connectionstring setup against identity is the commerce db connection string, not the cms db.
This has been resolved now.
Whenever I was working with the [dbo].[cls_Contact] table I was receiving an 'Object not instantiated to an instance of an Object' error but no reference to what object.
After receiving help from a colleague it became clear that the new MetaField values that I added to the [dbo].[cls_Contact] table did not have 'isNullable' set to to true. Therefore, I had fields in my table without values that were, technically, not allowed to be null.
To fix this I did the following:
// the below code is technically to update a user but the concept is the same.
public bool UpdateCommerceUser(string salutation, string academicTitle, string firstName, string lastName, string birth, string gender)
{
var placeholderId = new Guid("4DC8C10F-1902-42DD-874C-CA6684E327EC");
try
{
var context = CustomerContext.Current.GetContactById(placeholderId);
context["Salutation"] = salutation;
context["AcademicTitle"] = academicTitle;
context.FirstName = firstName;
context.LastName = lastName;
context.FullName = $"{firstName} {lastName}";
context["Gender"] = gender;
context.SaveChanges();
return true;
}
catch (ObjectNotFoundException ex)
{
return false;
}
}
In our eCommerce project we have a 'Personal Details' section where a user can do a number of things:
**These fields are added to the [dbo].[mcmd_MetaEnum] and [dbo].[cls_Contact] tables in Commerce via an InitilizationModule (see previous posts here and here)
My question is, what steps do I need to take to ensure that I can successfully update a User's details and have it synchronize across both the CMS databases and the Commerce databases?
I am aware that I can get the current user via the below line
However, user doesn't expose any of my new MetaField values: Gender, Salutation & Username? How do I access (and update) database fields that I have added myself?
My follow up question will then be: once I am able to update the user's personal details in Commerce how do I then ensure that the CMS database will share the same values?
Below you can see a screenshot of the [dbo].[AspNetUsers] table. The only value that may change and need to be updated in the CMS as well Commerce is the UserName value.
Is this as straightforward as the below?