Using always encrypted on a entity framework [code first] database - entity-framework

I have an MVC application that uses entity framework / code first. I'm trying to set up always encrypted in order to encrypt a column (social security number / SSN). I'm running everything in Azure, including using Azure vault to store keys.
I have two models, SystemUser and Person. SystemUser is essentially an account / login which can administer 1 or more People.
The definitions look a bit like:
public class Person
{
[StringLength(30)]
[Column(TypeName = "varchar")]
public string SSN { get; set; } // Social Security Number
...
[Required, MaxLength(128)]
public string SystemUserID { get; set; }
[ForeignKey("SystemUserID")]
public virtual SystemUser SystemUser { get; set; }
...
}
public class SystemUser
{
...
[ForeignKey("SystemUserID")]
public virtual HashSet<Person> People { get; set; }
...
}
I have a very basic page set up that just looks up a user and prints out their SSN. This works. I then adapted the page to update SSN and this also works. This to me implies that the Always Encrypted configuration and Azure Vault is set up correctly. I've got "Column Encryption Setting=Enabled" in the connection string and I encrypted the column SSN using SSMS (I'm using deterministic).
In my SystemUser class I have the following method as an implementation for Identity:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<SystemUser> manager)
{
...
if (this.People.Any())
{
...
}
...
}
This is used for user logins. Running the code results in a:
System.Data.Entity.Core.EntityCommandExecutionException: An error
occurred while executing the command definition. See the inner
exception for details. ---> System.Data.SqlClient.SqlException:
Operand type clash: varchar is incompatible with varchar(30) encrypted
with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name =
'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name =
'CEK_Auto11', column_encryption_key_database_name = 'xxx')
collation_name = 'Latin1_General_BIN2'
It seems to fail on the line above "if (this.People.Any())". Putting a break point just before that line reveals the following about this.People:
'((System.Data.Entity.DynamicProxies.SystemUser_9F939A0933F4A8A3724213CF7A287258E76B1C6775B69BD1823C0D0DB6A88360)this).People'
threw an exception of type
'System.Data.Entity.Core.EntityCommandExecutionException' System.Collections.Generic.HashSet
{System.Data.Entity.Core.EntityCommandExecutionException}
Any ideas here? Am I doing something that Always Encrypted does not support?

Always encryption is not having support in entity framework. MS still working.

This Blog Using Always Encrypted with Entity Framework 6 explains how to use Always Encrypted with Entity Framework 6 for DataBase first and Code First From existing database and Code first-Migrations with work arounds for different scenarios and problems.

According to https://blogs.msdn.microsoft.com/sqlsecurity/2015/08/27/using-always-encrypted-with-entity-framework-6/
Pass the constant argument as closure – this will force parametrization, producing >correct query:
var ssn = "123-45-6789";
context.Patients.Where(p => p.SSN == ssn);

Related

Navigation Property Not Saving with BulkInsert()

Using EFCore 3.1 with the library EFCore.BulkExtensions 3.6.1 (latest version for EFCore 3.1).
Database server is SQL Server 2019.
Here is code to reproduce the error.
A simple Customer class with a navigation property from another class:
public class Customer
{
public int ID { get; set; }
public String Name { get; set; }
public Cont Continent { get; set; }
}
public class Cont
{
public int ID { get; set; }
public String Name { get; set; }
}
When I try to insert entities into Customers with populated navigation properties
using the "BulkInsert" method from the EFCore.BulkExtension library, the value of the navigation props do not get saved to the database:
Customer cust1 = new Customer
{
Continent = contList.Find(x => x.Name == "Europe"),
Name = "Wesson Co, Ltd."
};
Customer cust2 = new Customer
{
Continent = contList.Find(x => x.Name == "Asia"),
Name = "Tiranha, Inc."
};
// (checking the "Continent" props here shows them to be properly populated)
List<Customer> CustomerList = new List<Customer> { cust1, cust2 };
dbContext.BulkInsert(CustomerList);
The result is that the "ContinentID" column in the database is NULL.
Alternate way, as usual with the EF Core SaveChanges() works - change the last two lines to:
dbContext.Customers.AddRange(cust1, cust2);
dbContext.SaveChanges();
This works totally fine. But I have to insert a million records and SaveChanges() has a horrible performance for that scenario.
Is there anything I am doing wrong?
Using another (lower) version of the BulkExtension does not help. Higher versions won't work as they all target EFCore 5 with NetStandard 2.1 which my project does not currently support.
Could not find any hint or mention of navigation props related info in the EFCore.BulkExtension documentation.
Looking for what SQL is being sent only shows me a query like this
INSERT INTO dbo.Customers (ContinentID, Name) VALUES #p1, #p2
so it is up to BulkExtensions.BulkInsert() to place the values correctly, which it seemingly does not.
The point is that similar code has been working for 6 months, and now with a simple scenario as the above it won't, for any version of the BulkExtension library. So it is likley there must be something wrong with my code or my approach, but cannot find it.
UPDATE
Downgrading the package EFCore.BulkExtensions to 3.1.6 gives me a different error. Still does not work but here is the error:
System.InvalidOperationException : The given value 'Customer' of type String from the data source cannot be converted to type int for Column 2 [ContinentID] Row 1.
----> System.FormatException : Failed to convert parameter value from a String to a Int32.
----> System.FormatException : Input string was not in a correct format.
As it stands right now, this is a bug in the EFCore.BulkExtensions library - versions 3.2.1 through 3.3.5 will handle it (mostly) correctly, versions 3.3.6 - 3.6.1 do not.
Use version 3.3.5 for the most stable result, as of this writing.
(No data on version 5.x for EFCore 5)

Using REST to try and get Field Service Detail - InventoryID = SQL error: Multi-part identifier not found

I am getting SQL errors when trying to use REST to get to FSAppointmentDet.InventoryID, either as a Field Service service item or as an Inventory Item.
The InventoryID field exists in the table, however, it looks like the DACs have been inherited, for example as FSAppointmentDetService.
Other fields work, it just seems that the fields with an ID are causing the SQL error.
In this case, the SQL error is a multi-step identifier not found. Running a SQL Profiler trace and looking at the SQL, it looks like the table has been aliased in one part of the query and not in another. Obviously this is occurring at a level much lower than we can get to, so looking for a workaround or ideas on how to get the InventoryID for Field Service detail records.
I've seen this happen when one DAC herits (herits as in class inheritance not extend as in DAC extension) from another DAC without redeclaring it's key fields. The way to fix that is to add the parent keys abstract class fields in the children.
FSAppointmentDetService seems to be missing AppointmentID key declaration. When the ORM builds the SQL query it generates Alias for the herited DAC but it gets confused becaused the key fields of the parent were not all re-declared in the child.
In FSAppointmentDet you have 2 key fields:
#region AppointmentID
public abstract class appointmentID : PX.Data.IBqlField
{
}
[PXDBInt(IsKey = true)]
[PXParent(typeof(Select<FSAppointment, Where<FSAppointment.appointmentID, Equal<Current<FSAppointmentDet.appointmentID>>>>))]
[PXDBLiteDefault(typeof(FSAppointment.appointmentID))]
[PXUIField(DisplayName = "Appointment Nbr.")]
public virtual int? AppointmentID { get; set; }
#endregion
#region AppDetID
public abstract class appDetID : PX.Data.IBqlField
{
}
[PXDBIdentity(IsKey = true)]
public virtual int? AppDetID { get; set; }
#endregion
But in FSAppointmentDetService only one of them is redeclared. Notice how it's using 'override' to redeclare compared to FSAppointmentDet which do not override:
#region AppDetID
public new abstract class appDetID : PX.Data.IBqlField
{
}
[PXDBIdentity(IsKey = true)]
public override int? AppDetID { get; set; }
#endregion
In this case we can't add field to that DAC though because it's part of the base product. I think it would be possible to create a new DAC that herits from FSAppointmentDetService, add the missing key in there and use that new herited DAC instead of FSAppointmentDetService.
However I don't know if that would be possible when working with Web Services. If not the change will have to be made in Acumatica base product. You could fill a bug report with Acumatica support to have that done in future versions.

Simple contract for use with FromSql()

With its recent improvements, I'm looking to move from Dapper back to EF (Core).
The majority of our code currently uses the standard patterns of mapping entities to tables, however we'd also like to be able to make simple ad-hoc queries that map to a simple POCO.
For example, say I have a SQL statement which returns a result set of strings. I created a class as follows...
public class SimpleStringDTO
{
public string Result { get; set; }
}
.. and called it as such.
public DbSet<SimpleStringDTO> SingleStringResults { get; set; }
public IQueryable<SimpleStringDTO> Names()
{
var sql = $"select name [result] from names";
var result = this.SingleStringResults.FromSql(sql);
return result;
}
My thoughts are that I could use the same DBSet and POCO for other simple queries to other tables.
When I execute it, EF throws an error "The entity type 'SimpleStringDTO' requires a primary key to be defined.".
Do I really need to define another field as a PK? There'll be cases where there isn't a PK defined. I just want something simple and flexible. Ideally, I'd rather not define a DBSet or POCO at all, just return the results straight to an IEnumerable<string>.
Can someone please point me towards best practises here?
While I wait for EF Core 2.1 I've ended up adding a fake key to my model
[Key]
public Guid Id { get; set; }
and then returning a fake Guid from SQL.
var sql = $"select newid(), name [result] from names";

EF6: Table Splitting Not Working

I am trying to create an EF6 database where two tables, Addresses and Visits, share the same values as primary keys. Visits, conceptually, is an extension of Addresses. I'm splitting the tables because most of the records in Addresses don't require the fields contained in Visits.
I'm using the code first approach. Here's the relevant code for the Addresses:
public class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey( "ID" )]
public virtual Visit Visit { get; set; }
and for Visits:
public class Visit
{
[Key]
[DatabaseGenerated( DatabaseGeneratedOption.Identity )]
public int ID { get; set; }
[ForeignKey("ID")]
public virtual Address Address { get; set; }
Based on my research, I also needed to include the following in my datacontext's OnModelCreating method:
modelBuilder.Entity<Visit>()
.HasOptional( v => v.Address )
.WithRequired();
Unfortunately, this doesn't work. I can update the database alright, after eliminating scaffolding calls to drop the primary index from Addresses (probably because the add-migration code thinks the primary key is "merely" a foreign key field). But when I run the application I get the following error:
Invalid column name 'Address_ID'.
Invalid column name 'Address_ID'.
From my limited experience with EF6 this looks like someplace deep inside the framework it's expecting there to be fields named 'Address_ID', probably in the Visits table (based on the 'table name'_'field name' naming structure I've seen for other implicitly added fields).
Is what I'm trying to do possible? If so, what am I missing in the configuration?
Additional Info
In trying out bubi's proposed solution, which unfortunately still generates the same error, that I could eliminate the OnModelCreating code and still get functional migration code generated.
Resolution
I finally did what I should've done earlier, which is examine the actual T-SQL code generated by the query which was blowing up. It turns out the problem was not in the Visit/Address linkage, but in a completely separate relationship involving another table. Apparently, somewhere along the way I did something to cause EF to think that other table (Voters) had an Address_ID foreign key field. In reality, the Address/Voter relationship should've been, and originally was, tied to a Voter.AddressID field.
Rather than try to unwind a large number of migrations I opted to blow away the database, blow away the migrations and start from scratch. After recreating the database -- but using bubi's suggestion -- I reloaded the data from backup and, voila, I was back in business.
For the sake of completeness, here's the code I ended up having to put into the OnModelCreating method call to get the Address/Visit relationship to work correctly:
modelBuilder.Entity<Visit>()
.HasRequired( v => v.Address )
.WithRequiredDependent( a => a.Visit );
modelBuilder.Entity<Address>()
.HasRequired( a => a.Visit )
.WithRequiredPrincipal( v => v.Address );
I am a little confused about why I have to use HasRequired in order to be able to use WithRequiredPrincipal/WithRequiredDependent, since not every entry in the Address table has an entry in the Visit table. That would seem to be "optional", not "required". But it appears to work, and maybe the "required" part is just internal to EF's model of the database, not the database itself.
There are 2 problems in the model:
- Only one of the Keys can be autonumbering, the other must get the same Id (this independently by EF).
- A mapping problem.
This model should work.
public class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Description { get; set; }
public virtual Visit Visit { get; set; }
}
public class Visit
{
public Visit()
{
Address = new Address();
}
[Key]
[ForeignKey("Address")]
public int Id { get; set; }
public string Description { get; set; }
public virtual Address Address { get; set; }
}
Example of use
var visit = new Visit
{
Description = "Visit",
Address = {Description = "AddressDescription"}
};
db.Visits.Add(visit);
db.SaveChanges();
In addition to what bubi mentioned, your modelBuilder statement contradicts the model in that it doesn't mention Address.Visit as the inverse property. So it thinks that the property represents a separate relationship and tries to create the Address_ID column for that relationship.
You need to have
modelBuilder.Entity<Visit>()
// from your description sounds like every Visit needs an Address
.HasRequired(v => v.Address )
// need to mention the inverse property here if you have one
.WithOptional(a => a.Visit);
...or just remove the statement completely since you're already using attributes, and EF should be able to figure it out by convention.

Entity Framework: alternatives to using MultipleActiveResultSets

I'm using ASP.NET WebAPI and ran into a problem with a nested model that should be communicated via a WebAPI Controller:
The entities "bond, stock etc." each have a list of entities "price". Server-side, I use the following class to match this requirement..
public class Bond : BaseAsset
{
public int ID { get; set; }
public string Name { get; set; }
public virtual List<Price> Prices { get; set; }
}
This leads to the table "Price" having a column for bond, stock etc. and, in case a price is attached to a bond, an entry in its column for bond foreign key.
The error I initially got was
There is already an open DataReader associated with this Command
I fixed that by altering the Connection String to allow MultipleActiveResultSets.
However, I feel there must be better options or at least alternatives when handling nested models. Is it, e.g., a sign for bad model design when one runs into such a problem? Would eager loading change anything?
One alternative to mars is to disable lazy loading
In your DbContext
Configuration.LazyLoadingEnabled = false;
plus when you are loading your data you can explicit load your child tables
context.Bonds.Include(b => b.Prices)