I've used AzureKeyVault to encrypt some Social Security # columns in SQL. These column definitions are varchar(11) NULL.
My models in my code have the following attributes:
[StringLength(11)]
[Column(TypeName = "varchar")]
[RegularExpression(RegExValidators.SSNRegex, ErrorMessage = "SSN must be a number")]
public string SSN { get; set; }
However, I'll occasionally see this error in my database logs:
System.Data.SqlClient.SqlException: Operand type clash: varchar is incompatible with varchar(11) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto2', column_encryption_key_database_name = 'DB NAME') collation_name = 'Latin1_General_BIN2'
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
The odd thing is that this doesn't ALWAYS happen... just every once in a while. I run this code in my Global.asax Application_Start() function:
public static class AzureKeyVaultInit
{
private static string _clientId = ConfigurationManager.AppSettings["AzureKeyVaultAppClientId"];
private static string _clientSecret = ConfigurationManager.AppSettings["AzureKeyVaultAppSecret"];
private static ClientCredential _clientCredential;
private static bool _isInitialized = false;
private static readonly object _isInitializedLock = new object();
public static void InitializeAzureKeyVaultProvider()
{
if (string.IsNullOrEmpty(_clientId)) return;
lock (_isInitializedLock)
{
if (!_isInitialized)
{
_clientCredential = new ClientCredential(_clientId, _clientSecret);
SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(GetToken);
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
providers.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
_isInitialized = true;
Core.Log.Info($"Initialized Azure Key Vault");
}
}
}
Anything glaring on why I'd get this error every once in a while?
Any value that targets an encrypted column needs to be encrypted inside the application. An attempt to insert/modify or to filter by a plaintext value on an encrypted column will result in an error like you.
To prevent such errors, make sure:
1.Always Encrypted is enabled for application queries targeting encrypted columns (Set Column Encryption Setting=enabled in the connection string or in the SqlCommand object for a specific query).
2.Use SqlParameter to send data targeting encrypted columns.
For more details, you could refer to this article.
Related
Is it possible to set a primary key value of your own choice ?
I'm working with data from an API and i'd like the objects to have the same id in my database as they have originally.
For example, i have an object with these attributes:
_context = new ApplicationDbContext();
Object
{
id = 1234,
Name = "Pitbull",
Owner = "Greg"
};
_context.saveChanges(Object);
id is the PK for object in the database. But, if i save this the id is discarded and the database creates it's own value.
Thanks for reading ! :)
Yes there is.
The proper way to do this is to override your three DbContext.SaveChanges methods. In these methods you'll give the primary keys of all added objects an Id.
In the example below this is done in method GenerateIds:
public override int SaveChanges()
{
GenerateIds();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync()
{
GenerateIds();
return await base.SaveChangesAsync();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
GenerateIds();
return await base.SaveChangesAsync(cancellationToken);
}
private void GenerateIds()
{
var addedEntries = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added)
foreach (var addedEntry in addedEntries)
{
((IId)addedEntry.Entity).Id = this.CreateId();
}
}
CreateId should create a unique Id.
One of the Id generators I use often is nuget package IDgen (by RobIII). It is simple to install and to use. It generates unique System.Int64 identifiers, which have the advantage of being much smaller than a GUID. It works even if you have generators for the same database on multiple servers. The method it uses is the method twitter uses to generate ids for all its servers
Code is a one liner. In your DbContext:
private static IdGen.IdGenerator idGenerator = new IdGen.IdGenerator(0);
private long CreatedId()
{
return idGenerator.CreateId();
}
Today I stumbled over some unexpected behaviour of EclipseLink. (I don't know if this is bound to EclipseLink or if this is the same for all JPA providers.)
I assumed that retrievals of a managed JPA bean always return references to the same object instance when issued inside the same transaction (using the same EntityManager).
If that is right, I don't know why I receive an error when I execute the following test case:
#Test
public void test_1() {
EntityManager em = newEntityManager();
em.getTransaction().begin();
// Given:
Product prod = newProduct();
// When:
em.persist(prod);
em.flush();
Product actual =
em.createQuery("SELECT x from Product x where x.id = "
+ prod.getId(), Product.class).getSingleResult();
// Then:
assertThat(actual).isSameAs(prod); // <-- FAILS
em.getTransaction().commit();
}
The statement marked with "FAILS" throws the following AssertionError:
java.lang.AssertionError:
Expecting:
<demo.Product#35dece42>
and actual:
<demo.Product#385dfb63>
to refer to the same object
Interestingly the following slightly modified test succeeds:
#Test
public void test_2() {
EntityManager em = newEntityManager();
em.getTransaction().begin();
// Given:
Product prod = newProduct();
// When:
em.persist(prod);
em.flush();
Product actual = em.find(Product.class, prod.getId());
// Then:
assertThat(actual).isSameAs(prod); // <-- SUCCEEDS
em.getTransaction().commit();
}
Obviously there is a difference between finding and querying objects.
Is that the expected behaviour? And why?
--Edit--
I think I found the source of the problem: Product has an ID of type ProductId.
Here is the relevant code:
#Entity
#Table(name = "PRODUCT")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID", nullable = false)
#Converter(name = "productIdConverter", converterClass = ProductIdConverter.class)
#Convert("productIdConverter")
private ProductId id;
#Column(name = "NAME", nullable = false)
private String name;
[...]
}
The #Convert and #Converter annotations are EclipseLink-specific.
Unlike JPA 2.1 Converters you may place them on ID fields.
But it seems that in certain circumstances EclipseLink has problems to find a managed bean in its session cache if that bean uses a custom type for its ID field.
I guess I have to file a bug for that.
I found the cause of the problem and a solution.
We are using a custom ID class (ProductId) for Product, together with a custom (EclipseLink-specific) Converter-Class ProductIdConverter which has a bad implementation of the convertObjectValueToDataValue(...) method.
Here is the relevant code:
/**
* Convert the object's representation of the value to the databases' data representation.
*/
#Override
public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return null;
}
Long longValue = ((ProductId) objectValue).getLong();
return longValue;
}
Please note that the method returns Long instances (or null).
But since we are using Oracle as our database backend and have declared the product's ID column as NUMBER, the JDBC Driver maps the column value as BigDecimal. This means, we have to make sure, that our convertObjectValueToDataValue(...) also returns BigDecimal instances.
So the correct implementation is:
/**
* Convert the object's representation of the value to the databases' data representation.
*/
#Override
public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return null;
}
Long longValue = ((ProductId) objectValue).getLong();
return BigDecimal.valueOf(longValue);
}
Now this method returns only BigDecimal instances.
I have a entity, name Product.It have two property is unit (byte) and unitName(String). unit property is mapped on database. Ex: 0:Kg ; 1:g;.... I want when input a valid unit, unit property is stored; unless, it save to unitName
Product
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id")
private int productId;
#Column(name = "product_name")
private String productName;
#Column(name = "unit")
private Byte unit;
#Transient
private String unitName;
}
In unit text field, I use a UnitConvert
UnitConvert
public class UnitConverter implements IConverter<Byte> {
private static final long serialVersionUID = 4798262219257031818L;
public UnitConverter() {
}
#Override
public Byte convertToObject(String value, Locale locale) {
return Text.isEmpty(value) ? 0 : UtilCommon.getTaniCode(value);
}
#Override
public String convertToString(Byte value, Locale locale) {
return (value == null || value==0 ) ? "" : UtilCommon.getTaniName(value);
}
}
I only think about HiddenField to do that, but I don't know how to do that.
Someone know how to use or anything can help me. Thank you very much
So from what I understood you want to save the input of a Model to a different database property depending on certain checks before hand. You can do that in your Form.onSubmit() method.
A very simple implementation could look like this:
public ProductPanel(String id, final IModel<Object> productModel) {
super(id, productModel);
// we're putting the productModel into the constructor.
// Therefore it's guaranteed to be detached
// -> it's okay to have it with final modifier.
IModel<String> formModel = Model.of("");
Form<String> form = new Form<String>("form", formModel) {
#Override
protected void onSubmit() {
super.onSubmit();
String productName = getModelObject();
Object realProduct = productModel.getObject();
if (isAcceptableUnit(productName)) {
realProduct.setUnit(parseUnit(productName));
} else {
realProduct.setUnitName(productName);
}
layer.saveProduct();
}
};
add(form);
TextField<String> productName = new TextField<String>("textField", formModel);
form.add(productName);
}
private boolean isAcceptableUnit(String productName) {
// your logic to determine if it's okay to cast to byte here...
return true;
}
private byte parseUnit(String productName) {
// your logic to parse the String to byte here...
return 0;
}
Some additional comments since I'm uncertain if the code snippets you provided are just for simplicity or actually code pieces:
You should try to avoid declaring your db object Serializable. Should you use normal Model objects to save your DTOs wicket will actually serialize them and you won't be able to do anything with them (well with hibernate at least).
Database object should use LoadableDetachableModel and save the primary key to load the entity in the load() method of it.
This would enable you now to work directly on those objects by using CompoundPropertyModel etc (which has it's pros and cons which I will not explain in detail here).
Still in your case I would add an Model<String> to the form and let the server decide how the input should be handled and mapped to the actual domain object.
i'm using MondoDB with Morphia 0.105 layer.
My User class is:
#Entity
public class User {
#Id
private ObjectId id = null;
private String userId = null;
private String fullName = null;
#Embedded
private UserType userType = null;
#Embedded
private Set<Rights> rights = new HashSet<Rights>();
and my test class is:
public class Test {
public static void main(String[] args) throws UnknownHostException {
Morphia m = new Morphia();
Datastore ds = m.createDatastore(new MongoClient("localhost"), "test");
m.map(User.class);
User u = new User();
u.setFullName("User Name");
u.setUserId("USERID");
//u.getRights().add(Rights.ADMIN); //NO rights added VS ONE right added
ds.save(u);
u = ds.find(User.class).filter("rights size", 0).get();
System.out.println(u);
System.out.println(u != null);
}
}
I got this unexpected result:
The type(s) for the query/update may be inconsistent; using an instance of type 'java.lang.Integer' for the field 'org.vts.sis2.entities.User.rights' which is declared as 'java.util.Set'
If i uncomment line //u.getRights().add(Rights.ADMIN); that adds to user a right, the query ds.find(User.class).filter("rights size", 1).get() returns the correct result (even if the warning is displayed, but it's a false positive since in my opinion it's correct to compare a result of size operator to an integer)!
What should i do to query users with empty rights list/set field?
Thanks
Try this:
ds.find(User.class).field("rights").sizeEq(0).get()
I have a configuration table in my database and it contains exactly one row.
ConfirmedScheduleColor | OverlappedScheduleColor | ColN
Currently, I'm retrieving the configuration like this:
var db = new SchedulingDbContext();
var config = db.Configurations.FirstOrDefault();
It's currently working fine and I can access my configurations and all. The thing is, the code looks awkward since I'm accessing the Configurations DbSet as if it contains many records (FirstOrDefault()); although actually, it contains only one record. I want to access my configurations like I'm accessing a static object. How to do that in EF?
You could simply add a property to your DbContext that returns Configurations.FirstOrDefault() and privatize the DbSet:
public class SchedulingDbContext : DbContext
{
private DbSet<Configuration> Configurations { get; set; }
public Configuration Configuration
{
get
{
return Configurations.FirstOrDefault();
}
}
}
I have a class in my project that has static methods to retrieve config settings. I use the ConfigurationManager rather than the database, but you could adapt it to get the setting from wherever you are storing the value.
In my example I have written a GetFromDb method for you that takes a key as parameter but that is because if I was storing my config settings in the database I wouldn't want to add a column every time I needed a new config setting. I would have a table with Key/Value columns. If you are wedded to the single row table then you might want to do without such a method.
public class Config
{
private _ConfirmedScheduleColor;
public static string ConfirmedScheduleColor
{
get
{
if(_ConfirmedScheduleColor == null)
_ConfirmedScheduleColor = GetFromDb("ConfirmedScheduleColor");
return _ConfirmedScheduleColor;
}
}
public static string OverlappedScheduleColor
{
get { return GetValue("OverlappedScheduleColor", "Pink"); }
}
public static int ColN
{
get { return GetValue("ColN", 2); }
}
private static string GetFromDb(string key)
{
if(key == "ConfirmedScheduleColor")
{
var config = db.Configurations.FirstOrDefault();
return config.ConfirmedScheduleColor;
}
}
private static string GetValue(string key, string defaultValue)
{
return ConfigurationManager.AppSettings[key] ?? defaultValue;
}
private static string GetValue(string key, int defaultValue)
{
int i;
if(int.TryParse(ConfigurationManager.AppSettings[key], out i))
return i;
return defaultValue;
}
}
In EF Core you can set the check constraint for the primary key. It enforces that column Id must have value that is equal to 1 which means only one record can exist in table if you have the primary key.
modelBuilder.Entity<YourTable>(e =>
{
e.HasCheckConstraint("CK_Table_Column", "[Id] = 1");
e.HasData(...) //optionally add some initial date for Id = 1
});