EF Core - Get select query results while migrating - c#

I am using EF Core and the Users table had a plain-text password column. Adding a new EncryptedPassword column instead to replace this. I cannot insert encrypted passwords by running a plain UPDATE script in the migration file since the encryption process is done in the code. Is there a way to read data from the migration methods, something like,
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "EncryptedPassword",
table: "Users",
type: "nvarchar(200)",
nullable: true);
var rows = migrationBuilder.Sql("SELECT * FROM Users WHERE EncryptedPassword = NULL");
foreach(var row in rows)
{
string encPwd = PasswordManage.Encrypt(row.Password);
migrationBuilder.Sql($"UPDATE Users SET EncryptedPassword = '{encPwd}' WHERE Id = '{row.Id}'")
}
}
What will be the best approach to insert the encrypted passwords for the existing users?
Update
I now understand that this is not the way to go. I was able to read data using context. But this means that the scripts generated for migration will only work for the current database that is used to generate it. So it would be useless for any other database.

Migrations are translated to Sql code upon migration.
There is a method called migrationBuilder.Sql() and has the option to be executed inside the transaction so if the sql fails, transaction is rolled back.
You could use a MERGE statement to achieve this.
Compute all the password conversions and store them in a temporary table. Can work too with variable table
CREATE TABLE [Temp].[Table] ([Password] nvarchar(8000), [Enc] nvarchar(8000));
INSERT INTO [Temp].[Table] ([Password], [Enc]) VALUES {CommaSeparatedValues}
Perform merge operations
MERGE INTO [Users] AS U USING [Temp].[Table] AS T
ON U.[Password] = T.[Password]
WHEN MATCHED AND U.[EncryptedPassword] IS NULL
THEN UPDATE SET U.[EncryptedPassword] = T.[Enc];
Delete the temp table
DROP TABLE [Temp].[Table]
Additionally, you can inject the ApplicationDbContext or create a new one inside the migration, so you can build the comma separated values inside the Up method.
public partial class PasswordEncryptMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
using AppDbContext _context = new (ConnectionString);
/* Query the server */
var a = _context.Users
.Where(x => x.EncryptedPassword == null)
.Select(x => x.Password)
.ToList();
/* Get the encrypted password */
var csv = a.Select(x => $"('{x}', '{PasswordManage.Encrypt(x)}')")
.Aggregate((a,b) => a + ", " + b);
migrationBuilder.Sql('{SQL from step 1 with csv added}');
migrationBuilder.sql('Step 2 ...')
//...
}
}
Reference: https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/operations

Related

Unusual behavior of Entity Framework

I have the following code in C#:
public int AddSynonymBL(String[] syns, String word, User user)
{
int dismissedCounter = 0;
foreach (var item in syns)
{
BusinessLayerStatus.StatusBL res = this.dataAccess.AddSynonymDA(item.Trim().ToLowerInvariant(), word.Trim().ToLowerInvariant(), user);
if (res == BusinessLayerStatus.StatusBL.SynonymNotAdded)
++dismissedCounter;
}
int numberOfFailures = dismissedCounter;
return numberOfFailures;
}
And the following code is for AddSynonymDA method:
internal BusinessLayerStatus.StatusBL AddSynonymDA(string synonym, string word, User user)
{
try
{
Synonym newSyn = new Synonym()
{
Meaning = synonym
};
//The following if means that the searched word does not exist int the Searched table
if (this.context.Users.Where(a => a.Mail.Equals(user.Mail)).FirstOrDefault().Searcheds.Where(b => b.RealWord.Equals(word)).Count() == validNumberForKeyValues)
{
this.context.Users.Where(a => a.Mail.Equals(user.Mail)).FirstOrDefault().Searcheds.Where(b => b.RealWord.Equals(word)).FirstOrDefault().Synonyms.Add(newSyn);
this.context.SaveChanges();
return BusinessLayerStatus.StatusBL.SynonymAdded;
}
else
return BusinessLayerStatus.StatusBL.SynonymNotAdded;
}
catch (Exception ex)
{
ExceptionAction(ex);
return BusinessLayerStatus.StatusBL.SynonymNotAdded;
}
}
I am using Entity Framework. I have a table which contains an Id, a word column. Both of them together have unique key constraint in the database. My main code is as follows:
public static void Main()
{
EngineEntities context = new EngineEntities();
BusinessLogic bl = new BusinessLogic();
String[] s = new String[] { "java", "DB" };
Console.WriteLine(bl.AddSynonymBL(s, "Java", new User() { Mail = "media" }));
}
When I add a value which does not exist in the table everything is fine but when I add a value which already exists in the table, calling this.context.SaveChanges(); in the AddSynonymDA method, always throws an exception which was for the previous first exception which caused the first exception and nothing is added to database even if they do not exist in the database. Why is that?
I get the following error which shows that Java already exists. The problem is that Java is for the first call, as the second call, I have passed DB not Java.
{"Violation of UNIQUE KEY constraint 'IX_Searched'. Cannot insert duplicate key in object 'dbo.Searched'. The duplicate key value is (java, 2).\r\nThe statement has been terminated."}
I suspect that you have not set a column to be an Identity column in your database
In other words when you are inserting an entity you need a column to be automatically incrementing.
The way I do this is for example using SQL server:
ALTER TABLE [User] DROP COLUMN [ID];
ALTER TABLE [User]
ADD [ID] integer identity not null;
If you do not have an ID column already you do not need the first line.
After this, update your EF model in your project by deleting the User table and right clicking and Updating Model from Database and select the table.
So now when you insert new entries in you EF model, the ID column will be automatically incremented and you won't get an error.
you must initially check whether the item exists or not, since you seem to have a unique constraint, then you should utilize the attributes of reference in your code .

Hide queries without ORM

So I have an application where I need to save data to database, but the data structure is based on user. Basically user inserts a .csv or .txt file to my application and it generates table with data from the file. It is great and sweet, but I want to hide my queries and make them more manageable. Currently I have manager classes like:
TableManager
public static void CreateDocumentTable(string tableName, object[] values)
{
var query = QueryManager.CreateTable(tableName, headers);
query.Execute();
}
QueryManager
public static DMQuery CreateTable(string tableName, DMAttribute[] values)
{
var query = new DMQuery() { Command = $"CREATE TABLE IF NOT EXISTS {tableName} (" };
query.Parameters = values;
query.Command += ValuesToQuery(values);
query.Command += ")";
return query;
}
The thing is I can't use Entity Framework, because most of this is not static. Table structure changes based on csv so I never know what kind of columns I will have to take care of.
Also it is not very secure right know. I'm not looking for best security system, but just something that can hide strings like "CREATE TABLE ...."

How to retrieve inserted identity in migration of FluentMigrator

During a migration how do i Insert into my table, then retrieve the ID, and then use it to insert a related data in another table.
what i have now is an hardoced ID to insert, but I don't know what it's gonna be when i'll run the migration.
var contactId = 2;
var phoneNumber = 2;
Insert.IntoTable("Contacts")
.WithIdentityInsert()
.Row(new
{
Id = contactId,
TimeZoneId = contact.TimeZoneId,
contact.CultureId,
Type = (byte)(int)contact.Type,
Email = contact.Email.ToString(),
EntityId = entityId
});
Insert.IntoTable("PhoneNumbers")
.WithIdentityInsert()
.Row(new
{
Id = phoneNumberId,
phone.Number,
Type = (byte)(int)phone.Type,
ContactId = contactId
});
I'd like to be able to retrieve the inserted ID and use it for the second insert instead of harcoding it.
I'm using SQL Server if it's any help...
I Thought this would be trivial, but seems like it's not, after googling for it, and not seing any answers here.
You are able to manually insert the Id by chaining .WithInsertIdentity() after your .Row() call.
This will let you keep it in memory for use within other objects as Foreign Keys. Unfortunately, FluentMigrator doesn't actually execute any SQL until after all code within the Up() or Down() methods finish executing.
I use Execute.Sql() with ##IDENTITY in sql-query for same cases

Trying to Query a SQL Server from ASP.net MVC 4 using C#

I am trying to use a query to find the last ID (entry) in the table called ContributorUser in the database FPTContributorUsers and then add in a new entry thus assigning it the next available ID.
the below code allows me to add data to the table in the database however when I run it the ID (new entry to the table) shows as 0 and not 4. because I currently have three entries in my table
[HttpPost]
public ActionResult AddContributor(ContributorUsers AddCont)
{
if (AddCont.UserID == null)
{
throw new HttpException(404, "Please enter a valid RacfId");
}
else
{
FPTContributorUsers NewUser = new FPTContributorUsers();
NewUser.UserID = AddCont.UserID;
NewUser.ID = AddCont.ID;
db.ContributorUsers.Add(NewUser);
db.SaveChanges();
return RedirectToAction("index");
}
}
Have you thought about just making that the primary key and having it auto increment so you don't need to determine what it is yourself? That's usually the best way to go about handling actual ID's in my experience.
To do this in SQL Server follow these steps
To do this in MySQL follow these steps
As a note, if you do this you will need to update your EF model.
The other way to do it if you can't edit the database is to use MAX() for that column which will return the highest ID value, then just add one to it, no EF model updating required.
Try removing the "NewUser.ID = AddCont.ID" line and wait to get the NewUser.ID until after "db.SaveChanges()"
else
{
FPTContributorUsers NewUser = new FPTContributorUsers();
NewUser.UserID = AddCont.UserID;
db.ContributorUsers.Add(NewUser);
db.SaveChanges();
return RedirectToAction("index");
}
If the NewUser.ID property is an Identity Field in the database, it will not get populated until after the record is created during the SaveChanges() transaction commit.
Try Reload() the DB Context after SaveChanges() before you call the NewUser.ID so that the context is up-to-date

EF 5 Conditional Mapping

I'm using EF 5 Database first approach in my MVC application. all of my tables uses a Field called Deleted which is a boolean field to mark a record is deleted.
I'm trying to get rid of the requirement of having to check Deleted == false every time I query my database. The very straightforward way of doing this is to use a conditional mapping in the edmx file where EF always return data that are not deleted. That's all good.
But the problem of doing this condition mapping is that, when I want to allow the user to delete some record for e.g Address from their address book I don't have access to Delete field from EF as I used it in the conditional mapping and therefore I have to look for another option to allow user to delete a record.
The way I thought is to create a stored proc that handle the delete query and call it when I want to delete the record.
Is there a better way of doing this? Is it possible to make the Delete field accessible even it is used in the conditional mapping?
I have a working solution for Soft Delete in Entity Framework Code First that may help.
The key is that you add a discriminator to every model that you want to be able to soft delete. In code first that is done like this:
modelBuilder.Entity<Foo>().Map(m => m.Requires("IsDeleted").HasValue(false));
This makes it invisible to the context and therefore you have to do the deletes using sql.
If this is the equivalent of your "conditional mapping" in Database First then one way to modify the sql is to override SaveChanges and run sql from there:
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries()
.Where(p => p.State == EntityState.Deleted
&& p.Entity is ModelBase))//I do have a base class for entities with a single
//"ID" property - all my entities derive from this,
//but you could use ISoftDelete here
SoftDelete(entry);
return base.SaveChanges();
}
private void SoftDelete(DbEntityEntry entry)
{
var e = entry.Entity as ModelBase;
string tableName = GetTableName(e.GetType());
Database.ExecuteSqlCommand(
String.Format("UPDATE {0} SET IsDeleted = 1 WHERE ID = #id", tableName)
, new SqlParameter("id", e.ID));
//Marking it Unchanged prevents the hard delete
//entry.State = EntityState.Unchanged;
//So does setting it to Detached:
//And that is what EF does when it deletes an item
//http://msdn.microsoft.com/en-us/data/jj592676.aspx
entry.State = EntityState.Detached;
}
Method used to Get Table Name explained here
That is the way I used to do it. Probably irrelevant to your Database First approach in EF5, but I have now moved to doing it in stored procedures. EF6 Code First generates CreateStoredProcedure calls in Migration files. I replace these with this.CreateDeleteProcedure("dbo.Foo_Delete", "[dbo].[Foos]"); - which is a call to my own extension method:
public static class MigrationExtensions
{
internal static string DeleteSqlFormat
{
//I also hard delete anything deleted more than a day ago in the same table
get { return "DELETE FROM {0} WHERE IsDeleted = 1 AND DATEADD(DAY, 1, DeletedAt) < GETUTCDATE(); UPDATE {0} SET IsDeleted = 1, DeletedAt = GETUTCDATE() WHERE ID = #ID;"; }
}
internal static void CreateDeleteProcedure(this DbMigration migration, string procName, string tableName)
{
migration.CreateStoredProcedure(
procName,
p => new
{
ID = p.Int(),
},
body:
string.Format(MigrationExtensions.DeleteSqlFormat, tableName)
);
}
}

Categories