I finally got the RegistrySearch approach working. I hope this may help some other people in the future.
I checked the MSI documentation. Registry table actually doesn't contain a column called type. Instead, it stores the Value in a weird way so that MSI knows what type of registry value it should create. In my case, I want to create a DWORD type registry value. The value should be stored as #1 in the Registry table if I want to put numeric value 1 to the registry value. Here is a link to Registry table
So, why does Wix RegistryValue has a mandatory attribute Type? I think it's because Wix is trying to be nice to you. If you mark the type as int, you can simply put in "1" in the value attribute. You don't need to remember you have to type in "#1" instead of "1". When you compile the Wix source code, the compiler will do the dirty work for you and translate "1" into "#1" for you.
Similarly, RegistrySearch is a direct mapping to the MSI database table RegLocator. It returns #1 to me because my registry value type is DWORD. It's too bad that Wix didn't do the translation for me this time. The following code returns the raw data from the table to me. So, my property LOG_LEVEL is storing #1 instead of 1.
<Property Id='LOG_LEVEL' Value='3'>
<RegistrySearch Id='LogLevelRegistry' Type='raw' Root='HKLM' Key='Software\Company\Product' Name='LogLevel' Win64='$(var.Win64)'/>
</Property>
Here is the link for RegistrySearch
My code stored the registry value into a property. Then, I was trying to put it back using the following code.
<Component Id="RegistryKey">
<RegistryKey Root='HKLM' Key='Software\Company\Product' Action='createAndRemoveOnUninstall'>
<RegistryValue Type='int' Name='LogLevel' Value='[LOG_LEVEL]'/>
</RegistryKey>
</Component>
As I said, Wix would append a # for me when seeing the type "int". So, the value in the Registry table now is ##1. If you check the MSDN document, ##1 would be interpreted as a string value "#1". Therefore, my registry value is recreated with a new type SZ and new value "#1"
To fix this problem, I changed my code to this
<Property Id='LOG_LEVEL' Value='#3'>
<RegistrySearch Id='LogLevelRegistry' Type='raw' Root='HKLM' Key='Software\Company\Product' Name='LogLevel' Win64='$(var.Win64)'/>
</Property>
<Component Id="RegistryKey">
<RegistryKey Root='HKLM' Key='Software\Company\Product' Action='createAndRemoveOnUninstall'>
<RegistryValue Type='string' Name='LogLevel' Value='[LOG_LEVEL]'/>
</RegistryKey>
</Component>
Note that althought I specify type "string" here, I still have a registry value with type DWORD created for me. It's because this is how the MSI interprets the value inside the Registry table. If registry value LogLevel didn't exist (brand new installation), I will set a default value #3 to it. If registry value LogLevel exists, it will preserve the existing LogLevel.
Also note that this method works because Wix 3.0 doesn't do any processing on the property value. It puts the value stored inside the property directly into the Registry table.
Just out of curiosity, I also tried the following.
<RegistryValue Type='string' Name='LogLevel' Value='#1'/>
This time, Wix correctly escape the # character and put ##1 into the Registry table. If later Wix chooses to escape the # character inside the property value too, my solution here won't work.