how to execute SQL code separatly in SqlServerMigrationSqlGenerator Generates method? - entity-framework-6

I want to run an SQL script after each new table created through Entity Framework.
I created an override for 'CreateTableOperation' like this:
protected override void Generate(CreateTableOperation createTableOperation)
{
string tenantID = "tenantID";
base.Generate(createTableOperation);
//If the table contain the column "tenantID",
if ((from x in createTableOperation.Columns
where x.Name == tenantID
select x).Any()
)
{
//Add the Security Policies for this table
using (var writer = Writer())
{
writer.WriteLine("ALTER SECURITY POLICY rls.tenantAccessPolicy ");
//Note: dbo. is already part of the .Name, don't add it manually before .Name
writer.WriteLine($"ADD FILTER PREDICATE rls.fn_tenantAccessPredicateWithSuperUser([{tenantID}]) ON [{createTableOperation.Name}], ");
writer.WriteLine($"ADD BLOCK PREDICATE rls.fn_tenantAccessPredicateWithSuperUser([{tenantID}]) ON [{createTableOperation.Name}] ");
Statement(writer);
}
}
}
Let's say I create a new class 'TEST'.
The problem is when I run my Update-Database in the Package Manager Console, the SQL is executed in one big block and create an error because the table is not yet created and I'm trying to get the table.name Package Manager result after Update-Database:
CREATE TABLE [dbo].[TEST] (
[ID] [int] NOT NULL IDENTITY,
[NameTEST] [nvarchar](max),
[TenantID] [int] NOT NULL,
CONSTRAINT [PK_dbo.TEST] PRIMARY KEY ([ID])
)
ALTER SECURITY POLICY rls.tenantAccessPolicy
ADD FILTER PREDICATE rls.fn_tenantAccessPredicateWithSuperUser([tenantID]) ON [dbo.TEST],
ADD BLOCK PREDICATE rls.fn_tenantAccessPredicateWithSuperUser([tenantID]) ON [dbo.TEST]
Cannot find the object "dbo.TEST" because it does not exist or you do not have permissions.
Is there a way to break between the base.Generate(createTableOperation); and my Statement(writer);? (I already tried multiple things with 'GO' without much success). Or is it that I'm not supposed to put code in that method?

I couldn't find a solution/documentation to separate the code/batch SQL in Generate(CreateTableOperation createTableOperation).
What I did instead was to
generate dynamic SQL scripts in the Generate(CreateTableOperation createTableOperation) method
/// <summary>
/// Generate dynamically named .sql file for CREATE and DROP Security Policies (Row-Level Security (Company can't see/insert data of another company))
/// </summary>
/// <param name="createTableOperation">Default parameter that comes with the override method.</param>
protected override void Generate(CreateTableOperation createTableOperation)
{
base.Generate(createTableOperation);
string fullFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..\\Migrations\\SQLscripts\\SecurityPolicies");
//Remove the schema for the dynamic creation of the .sql filename else it throw an exception:
int indexOfDot = createTableOperation.Name.IndexOf(".");
indexOfDot++; //Because it's 0 base, add 1 char
string tableNameWithoutSchema = (indexOfDot < 0) ? createTableOperation.Name : createTableOperation.Name.Remove(0, indexOfDot);
string filePathCreate = Path.Combine(fullFolderPath, $"CreateSecurityPolicies_{tableNameWithoutSchema}.sql");
string filePathDrop = Path.Combine(fullFolderPath, $"DropSecurityPolicies_{tableNameWithoutSchema}.sql");
//If .sql files doesn't exists, create them
if (!File.Exists(filePathCreate))
{
using (StreamWriter sw = new StreamWriter(filePathCreate, true))
{
sw.WriteLine("ALTER SECURITY POLICY rls.tenantAccessPolicy");
//Note: Don't concatenate 'dbo.{}' because 'createTableOperation.Name' already include 'dbo'
sw.WriteLine($" ADD FILTER PREDICATE rls.fn_tenantAccessPredicateWithSuperUser(CompanyID) ON {createTableOperation.Name},");
sw.WriteLine($" ADD BLOCK PREDICATE rls.fn_tenantAccessPredicateWithSuperUser(CompanyID) ON {createTableOperation.Name}");
sw.WriteLine("GO");
sw.Close();
}
}
if (!File.Exists(filePathDrop))
{
using (StreamWriter sw = new StreamWriter(filePathDrop, true))
{
sw.WriteLine("ALTER SECURITY POLICY rls.tenantAccessPolicy");
sw.WriteLine($" DROP FILTER PREDICATE ON {createTableOperation.Name},");
sw.WriteLine($" DROP BLOCK PREDICATE ON {createTableOperation.Name}");
sw.WriteLine("GO");
sw.Close();
}
}
}
Then, with my SQL scripts in a folder, add it manually to my solution.
Add-Migration just to run my SQL script by adding code into the empty (up-to-date) migration class.
public override void Up()
{
string sqlFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..\\Migrations\\SQLscripts\\CreateSecurityPolicies_Client.sql");
string sqlText = System.IO.File.ReadAllText(sqlFilePath);
Sql(sqlText);
}
public override void Down()
{
string sqlFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..\\Migrations\\SQLscripts\\DropSecurityPolicies_Client.sql");
string sqlText = System.IO.File.ReadAllText(sqlFilePath);
Sql(sqlText);
}
This is the best I can come up with for the moment. I hope it can helps other EntityFramework programmers!

Related

How to use COLLATE Latin1_General_bin in Entity framework?

I have to use COLLATE in entity framework query. How to write SQL query equivalent in Entity Framework as show below code?
SQL query:
select * from AspNetUsers order by Email COLLATE Latin1_General_bin
Entity Framework:
using (var db = new testEntities())
{
var appUsers = await db.Users.OrderBy(x => x.Email).ToListAsync();
}
It's possible to use Entity Framework's interception hooks.
The first step it to define an interface:
interface ISortInterceptable
{
IEnumerable<string> AdaptableSortFieldNames { get; set; }
}
Then make your context implement it:
class TestEntities : DbContext, ISortInterceptable
{
...
public IEnumerable<string> AdaptableSortFieldNames { get; set; }
...
}
Next, create a command interceptor:
class SortCommandInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
if (interceptionContext.DbContexts.First() is ISortInterceptable interceptable
&& interceptable.AdaptableSortFieldNames != null)
{
var query = command.CommandText;
foreach (var fieldName in interceptable.AdaptableSortFieldNames)
{
var pattern = $#"(.*\s*ORDER BY\s*.*\.)(\[{fieldName}\])(.*)";
query = Regex.Replace(query, pattern, "$1$2 COLLATE Latin1_General_bin $3");
}
command.CommandText = query;
}
base.ReaderExecuting(command, interceptionContext);
}
}
This is where all the magic happens.
The interceptor first checks if it has to do with a ISortInterceptable (maybe this check can be refined by getting all ISortInterceptables from interceptionContext.DbContexts).
The command text in the command to be executed is analyzed on any occurence of strings like ORDER BY [Alias].[fieldName] where fieldName is a variable. This search pattern is in keeping with the pattern EF always follows to generates queries.
The field name part of the ORDER BY clause, which is in the third group ($2) of the regex match, is extended by the collation phrase.
The replacement is repeated for all field names.
Finally, an example of how to use this interceptor:
DbInterception.Add(new SortCommandInterceptor());
using (var db = new TestEntities())
{
db.AdaptableSortFieldNames = new[] { "LastName", "Email" };
var users = db.AspNetUsers
.OrderBy(u => u.LastName)
.ThenBy(u => U.Email)
.ToList();
}
As always with string manipulation, one caveat: this works in a couple of my own tests, but I can't guarantee it to be rock solid. For one, the sorting fields should be text fields, of course.
One last note. EF core 3 also offers interception hooks that can be used in a similar way.

.NET mvc adding more then 1 entity to db

I'm trying to add several entities to the db but I get this error:
"Saving or accepting changes failed because more than one entity of type 'Schema.Domain.DataModels.ActivitySummery' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration."
public void CreateNewGeneratedSchema(List<WeekDayViewModel> weekDays, int userId)
{
List<ActivitySummery> savedActivities = _schemaRepository.GetAllActivitySummeries(userId).ToList();
foreach(ActivitySummery activitySummery in savedActivities)
{
_schemaRepository.DeleteActivitySummery(activitySummery.ActivitySummeryId);
}
foreach (WeekDayViewModel weekDay in weekDays)
{
foreach (ActivitySummeryViewModel activitySummeryViewModel in weekDay.ActivitiySummeries)
{
try
{
ActivitySummery activitySummery = new ActivitySummery()
{
ActivityId = activitySummeryViewModel.ActivityId,
WeekDayId = activitySummeryViewModel.WeekDayId,
//Change userId
UserId = 1,
StartTime = activitySummeryViewModel.StartTime,
EndTime = activitySummeryViewModel.EndTime,
ActivityDescription = activitySummeryViewModel.Description,
Activity = _schemaRepository.GetSpecificActivity(activitySummeryViewModel.ActivityId),
WeekDay = _schemaRepository.GetSpecificWeekDay(activitySummeryViewModel.WeekDayId)
};
_schemaRepository.CreateActivitySummery(activitySummery);
}
catch (Exception)
{
throw new Exception("Something went wrong when trying to save the activity summery");
}
}
}
_schemaRepository.Save();
_schemaRepository.Dispose();
}
I know one solution that might work and it's to save and dispose the _schemaRepository after every time i added one model to the db and create a new instace of _schemaRepository. But im not sure if that is the right way. I know that every model that im trying to save has the pk of 0 and i think that might be the problem. However it still works and the db accepts the new entities but I still get the exception.
If your model (which you didn't show) has the primary key as ActivityId (which you also didn't indicate) and it is setup to auto-generate the primary key:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ActivityId { get; set; }
Then you must not include the primary key in the CreateActivitySummery method (also which you haven't provided).
// Remove this line...
// ActivityId = activitySummary.ActivityId;
Do note that a (reasonable) alternative to having the database generate the primary key automatically is to use Guid/UniqueIdentifier without using DatabaseGenerated. Then your application code can use Guid.NewGuid to generate new (presumably unique) primary keys that it can insert with no problem and track related entities easily.

How to improve my Entity Framework , to join several database queries into single query

I have the following ActionFilter class, to implement my custom authorization system:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
Repository repository = new Repository();
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string ADusername = filterContext.HttpContext.User.Identity.Name.Substring(filterContext.HttpContext.User.Identity.Name.IndexOf("\\") + 1);
if (!repository.can(ADusername,Model,Action))
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
The above class will call the following repository method:-
public bool can(string user, string Model, string Action)
{
bool result;
bool result2;
int size =tms.PermisionLevels.Where(a5 => a5.Name == Action).SingleOrDefault().PermisionSize;
var securityrole = tms.SecurityroleTypePermisions.Where(a => a.PermisionLevel.PermisionSize >= size && a.TechnologyType.Name == Model).Select(a => a.SecurityRole).Include(w=>w.Groups).Include(w2=>w2.SecurityRoleUsers).ToList();
foreach (var item in securityrole)
{
result = item.SecurityRoleUsers.Any(a => a.UserName.ToLower() == user.ToLower());
var no = item.Groups.Select(a=>a.TMSUserGroups.Where(a2=>a2.UserName.ToLower() == user.ToLower()));
result2 = no.Count() == 1;
if (result || result2) {
return true;
}}
return false;
}
But inside my repository method , I am doing the following:-
Query the database and include all the Groups & SecurityRoleUsers when executing the .tolist()
Then filter the returned records insdie the server, based on the foreach loop.
But this will cause the following drawbacks:-
If I have many Groups and SecurityRoleUsers, then I will be getting them all from the DB, and then filter the result on the server.
And since this code will be executed whenever an action method is called, as it Is a security attribute at the begging of the controller class. So this might not be very efficient.
So my question is whether I can join all the queries inside the repository method to be single query , and do all the work on the Database and just return true or false to the server ?
The associated tables looks as follow:-
Ideally remove this foreach.
Try riding with Linq to Sql.
You should be more comfortable because it resembles SQL.
This link has several examples.
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
Att
Julio Spader
wessolucoes.com.br
Use linq.
Ideally you should only have one line of code after you got the size value. e.g.
int size =tms.PermisionLevels.Where(a5 => a5.Name == Action).SingleOrDefault().PermisionSize;
var result = //One line of code to determine user authenticity
return result;
I think you should design you database in the way that join queries are easy to do. So you don't have to perform more than one select.
Try code-first EF, which links tables very easily.
You need to take care with Lazy Loading. If not used correctly, it will make a query to the database each object segmentation, especially in your foreach. With that already has a good improvement.
Take a look at this article. I think it will help you too.
http://www.sql-server-performance.com/2012/entity-framework-performance-optimization/
Att
Julio Spader
wessolucoes.com.br

Is there an equivalent of Rails ActiveRecord::Callbacks in ASP MVC?

Is there an equivalent of Rails ActiveRecord::Callbacks in ASP MVC?
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
I'm in a situation where we are not using identities for our primary key. We do this for reasons specific to our DB sharding design. Because of this we have a lookup table to find the next ID for a specific table. I'd like to automatically get this value and set it in an abstract class whenever a model is created/updated and before it is saved. I also need to update the lookup table with an incremented 'nextID' after the save is successful.
I'm open to other solutions on how to do this without callbacks as well.
So you need the callback just to increment ID in the lookup table? AFAIK there is no equivalent in ASP.NET, may be you could try with Async Controllers (http://msdn.microsoft.com/en-us/library/ee728598%28v=vs.100%29.aspx) and wait for a state change from the successful save, but I would prefer use a service specifically for this like Snowflake (https://github.com/twitter/snowflake/).
I found a solution using overrides as opposed to callbacks. It's my hope that ASP mvc adds support for callbacks as the framework continues to mature because callbacks allow for cleaner code by allowing the OnSave event to exist in the model[s] that the event is concerned with rather than the centralized DbContext class (separation of concerns).
Solution:
The SaveChanges method can be overridden in the Context Class (Entity Framework Power Tools creates the Context class is the 'Models' directory).
public override int SaveChanges()
{
// create a cache for id values in case their are multiple added entries in the dbcontext for the same entitytype
Dictionary<string, UniqueID> idCache = new Dictionary<string, UniqueID>();
IEnumerable<DbEntityEntry> changes = this.ChangeTracker.Entries();
foreach (var entry in changes)
{
//check if this is a new row (do nothing if its only a row update because there is no id change)
if (entry.State == System.Data.EntityState.Added)
{
//determine the table name and ID field (by convention)
string tableName = entry.Entity.GetType().Name;
string idField = entry.Entity.GetType().Name + "ID";
UniqueID id = null;
//if we've already looked this up, then use the cache
if (idCache.ContainsKey(tableName))
{
id = idCache[tableName];
}
//if we havn't looked this up before get it and add it to the cache
else
{
id = this.UniqueIDs.Find(tableName, idField);
//if it doesn't already exist in the lookup table create a new row
if (id == null)
{
id = new UniqueID(tableName, idField, 1);
// since this is a new entry add it
this.UniqueIDs.Add(id);
}
else
{
// set the state to modified
this.Entry(id).State = System.Data.EntityState.Modified;
}
}
entry.CurrentValues[tableName + "ID"] = id.NextID;
id.NextID = id.NextID + 1;
}
}
return base.SaveChanges();
}

Possible to default DateTime field to GETDATE() with Entity Framework Migrations?

I added EntityFramework.Migrations (Beta 1) to an existing Code-First app that is going through some changes (for both migration capabilities and more fine-tuning of the tables I am generating from my code-first API) and ran into the GETDATE() scenario.
I was already using a custom initializer class in my DbContext to run SQL scripts to set some fields and create indexes in my database. A handful of my AlterTable scripts are primary just to setup fields with default values(such as certain DateTime fields being set to GETDATE()). I was really hoping EntityFramework.Migrations would have an answer for this since you can easily specify defaultValue, but so far I'm not seeing one.
Any ideas? I was really hoping that doing the following would magically work. (It is 'magic unicorn', after all)
DateCreated = c.DateTime(nullable: false, defaultValue: DateTime.Now)
Unfortunately, and logically, it set my default value to the time when the Update-Database command was executed.
You can use
DateCreated = c.DateTime(nullable: false, defaultValueSql: "GETDATE()")
Usage:
public partial class MyMigration : DbMigration
{
public override void Up()
{
CreateTable("dbo.Users",
c => new
{
Created = c.DateTime(nullable: false, defaultValueSql: "GETDATE()"),
})
.PrimaryKey(t => t.ID);
...
Update 2012-10-10:
As requested by Thiago in his comment, I add a little extra context.
The code above is a migration-file generated by EF Migrations by running Add-Migration MyMigration as a command in the package manager console. The generated code is based on the models in the DbContext associated with migrations. The answer suggests that you modify the generated script so that a default value is added when the database is created.
You can read more about Entity Framework Code First Migrations here.
I recently ran in to this issue in EF6 (since they still haven't fixed it). The easiest way I found to do it without having to manually modify the Migration class is to override the CodeGenerator in your Configuration class.
By creating a class that implements MigrationCodeGenerator and then overriding the Generate method you can iterate through all of the operations and apply what ever modifications you want.
Once your modifications have been made, you can then initialize your the CSharpMigrationCodeGenerator and return its default value.
public class ExtendedMigrationCodeGenerator : MigrationCodeGenerator
{
public override ScaffoldedMigration Generate(string migrationId, IEnumerable<MigrationOperation> operations, string sourceModel, string targetModel, string #namespace, string className)
{
foreach (MigrationOperation operation in operations)
{
if (operation is CreateTableOperation)
{
foreach (var column in ((CreateTableOperation)operation).Columns)
if (column.ClrType == typeof(DateTime) && column.IsNullable.HasValue && !column.IsNullable.Value && string.IsNullOrEmpty(column.DefaultValueSql))
column.DefaultValueSql = "GETDATE()";
}
else if (operation is AddColumnOperation)
{
ColumnModel column = ((AddColumnOperation)operation).Column;
if (column.ClrType == typeof(DateTime) && column.IsNullable.HasValue && !column.IsNullable.Value && string.IsNullOrEmpty(column.DefaultValueSql))
column.DefaultValueSql = "GETDATE()";
}
}
CSharpMigrationCodeGenerator generator = new CSharpMigrationCodeGenerator();
return generator.Generate(migrationId, operations, sourceModel, targetModel, #namespace, className);
}
}
internal sealed class Configuration : DbMigrationsConfiguration<Project.Models.Context.DatabaseContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
MigrationsDirectory = #"Migrations";
this.CodeGenerator = new ExtendedMigrationCodeGenerator();
}
}
I hope this helps
You must use custom SQL script in Up method for setting default value:
Sql("ALTER TABLE TableName ADD CONSTRAINT ConstraintName DEFAULT GETDATE() FOR ColumnName");
Setting default value in code allows only static values - no database level functions.
Anyway setting it in POCO constructor is correct way if you are going to use code first. Also if you want to set the value in the application for some special cases you cannot use a default value in the database because the default value in the database requires either DatabaseGeneratedOption.Identity or DatabaseGeneratedOption.Computed. Both these options allow setting the property only in the database.
Edit:
Since the product is still in development my answer is no longer valid. Check #gius answer for actual way to achieve this requirement by using defaultValueSql (it wasn't available in EF Migrations Beta 1 but was added in EF 4.3 Beta 1 which already includes migrations).
Create a migration:
public partial class Table_Alter : DbMigration
{
public override void Up()
{
AddColumn("dbo.tableName", "columnName",
c => c.DateTime(nullable: false, defaultValueSql: "GETDATE()"));
}
public override void Down()
{
DropColumn("dbo.tableName", "columnName");
}
}
For existing records it will set the datetime when you will run Update-Database command, for new records it will be set the datetime of creation
Alternatively if your entities inherit from a common interface you can override the SaveChanges method on the DbContext and set or update properties at that point (great for Created Date and Last Changed Date)
This is the most simple way.
First Add DatabaseGeneratedOption.Computed DataAnnotion to your property
and now you can modify de SqlServerMigrationSqlGenarator, override Genarate method and set the DefaultValueSql = "GETDATE()" or "GETUTCDATE()";
Using Entity Framework with .net 6 I was able to make the migration add the default time by changing the defaultValue to defaultValueSql:
public partial class ReportQueueAddCreatedAt : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "ReportQueue",
type: "datetime2",
nullable: false,
defaultValueSql: "GETDATE()");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "ReportQueue");
}
}
An improvement: check if the constraint exists:
Sql(#"
if not exists (
select *
from sys.all_columns c
join sys.tables t on t.object_id = c.object_id
join sys.schemas s on s.schema_id = t.schema_id
join sys.default_constraints d on c.default_object_id = d.object_id
where
d.name = 'DF_ThubOutputEmail_Created'
)
begin
ALTER TABLE dbo.ThubOutputEmails ADD CONSTRAINT DF_ThubOutputEmail_Created default getdate() for Created;
end");

Resources