Entity Framework CTP 4 - Code First Custom Database Initializer - entity-framework

I would like to implement a custom database initialization strategy so that I can generate the database schema and apply it to an EXISTING EMPTY SQL database using a supplied User ID and Password.
Unfortunately the built-in strategies don’t provide what I’m looking for:
// The default strategy creates the DB only if it doesn't exist - but it does
// exist so this does nothing
Database.SetInitializer(new CreateDatabaseOnlyIfNotExists<DataContext>());
// Drops and re-creates the database but then this breaks my security mapping and
// only works if using a “Trusted" connection
Database.SetInitializer(new RecreateDatabaseIfModelChanges<DataContext>());
// Strategy for always recreating the DB every time the app is run. – no good for
// what I want
Database.SetInitializer(new AlwaysRecreateDatabase<DataContext>());
I have worked out the following but this does not create the ModelHash so I’m unable to use "context.Database.ModelMatchesDatabase()" to validate that the database schema has been created and prevent multiple initializations:
public class Initializer : IDatabaseInitializer<DataContext>
{
Public void InitializeDatabase(DataContext context)
{
// this generates the SQL script from my POCO Classes
var sql = context.ObjectContext.CreateDatabaseScript();
// As expected - when run the second time it bombs out here with "there is already an
// object named xxxxx in the database"
context.ObjectContext.ExecuteStoreCommand(sql);
this.seed(context)
context.SaveChanges();
}
}
Questions:
Does anyone know how I can get/create the model hash? (which is an EdmMetadata Entity)
-Or-
Is there a better way of doing this in general using the Code First CTP?

I ran into the same problem. I didn't really solve it, but I managed to get a little nasty workaround running, so i can deploy my solution to AppHarbor ;)
Its a IDatabaseInitializer implementation, that doesn't delete the db, but just nukes all the constraints and tables, and then uses the ObjectContext.CreateDatabaseScript() method to generate the sql, and then I execute it as a storecommand. A lot like the above implementation in the question.
But i also added functionality to create a hash from the model and save it in db, and when it runs again it checks if the current model-hash matches the one i db. Just like the real code-first implementation.
I couldn't make it work with the build in context.Database.CompatibleWithModel(true) - but this should work just as well, and seeing as its a temporary workaround it should be fine.
using System;
using System.Data.Entity;
using System.Data.Entity.Database;
using System.Data.Entity.Design;
using System.Data.Entity.Infrastructure;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using System.Linq;
namespace Devtalk
{
public class DontDropDbJustCreateTablesIfModelChanged<T> : IDatabaseInitializer<T> where T : DbContext
{
private EdmMetadata _edmMetaData;
public void InitializeDatabase(T context)
{
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
string modelHash = GetModelHash(objectContext);
if (CompatibleWithModel(modelHash, context, objectContext)) return;
DeleteExistingTables(objectContext);
CreateTables(objectContext);
SaveModelHashToDatabase(context, modelHash, objectContext);
}
private void SaveModelHashToDatabase(T context, string modelHash, ObjectContext objectContext)
{
if (_edmMetaData != null) objectContext.Detach(_edmMetaData);
_edmMetaData = new EdmMetadata();
context.Set<EdmMetadata>().Add(_edmMetaData);
_edmMetaData.ModelHash = modelHash;
context.SaveChanges();
}
private void CreateTables(ObjectContext objectContext)
{
string dataBaseCreateScript = objectContext.CreateDatabaseScript();
objectContext.ExecuteStoreCommand(dataBaseCreateScript);
}
private void DeleteExistingTables(ObjectContext objectContext)
{
objectContext.ExecuteStoreCommand(Dropallconstraintsscript);
objectContext.ExecuteStoreCommand(Deletealltablesscript);
}
private string GetModelHash(ObjectContext context)
{
var csdlXmlString = GetCsdlXmlString(context).ToString();
return ComputeSha256Hash(csdlXmlString);
}
private bool CompatibleWithModel(string modelHash, DbContext context, ObjectContext objectContext)
{
var isEdmMetaDataInStore = objectContext.ExecuteStoreQuery<int>(LookupEdmMetaDataTable).FirstOrDefault();
if (isEdmMetaDataInStore == 1)
{
_edmMetaData = context.Set<EdmMetadata>().FirstOrDefault();
if (_edmMetaData != null)
{
return modelHash == _edmMetaData.ModelHash;
}
}
return false;
}
private string GetCsdlXmlString(ObjectContext context)
{
if (context != null)
{
var entityContainerList = context.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.SSpace);
if (entityContainerList != null)
{
EntityContainer entityContainer = entityContainerList.FirstOrDefault();
var generator = new EntityModelSchemaGenerator(entityContainer);
var stringBuilder = new StringBuilder();
var xmlWRiter = XmlWriter.Create(stringBuilder);
generator.GenerateMetadata();
generator.WriteModelSchema(xmlWRiter);
xmlWRiter.Flush();
return stringBuilder.ToString();
}
}
return string.Empty;
}
private static string ComputeSha256Hash(string input)
{
byte[] buffer = new SHA256Managed().ComputeHash(Encoding.ASCII.GetBytes(input));
var builder = new StringBuilder(buffer.Length * 2);
foreach (byte num in buffer)
{
builder.Append(num.ToString("X2", CultureInfo.InvariantCulture));
}
return builder.ToString();
}
private const string Dropallconstraintsscript =
#"select
'ALTER TABLE ' + so.table_name + ' DROP CONSTRAINT ' + so.constraint_name
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS so";
private const string Deletealltablesscript =
#"declare #cmd varchar(4000)
declare cmds cursor for
Select
'drop table [' + Table_Name + ']'
From
INFORMATION_SCHEMA.TABLES
open cmds
while 1=1
begin
fetch cmds into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds
deallocate cmds";
private const string LookupEdmMetaDataTable =
#"Select COUNT(*)
FROM INFORMATION_SCHEMA.TABLES T
Where T.TABLE_NAME = 'EdmMetaData'";
}
}

This is the easiest way to get EF Code First running on AppHarbor!
Using the EdmMetadata.TryGetModelHash(context) function to check when the model doesn't match the database and showing an error with the new code that needs to be used after you run alteration scripts.
PopulateOnly : Only creates objects when the database is empty
I thought I'd post my own version of the Initializer which I'm currently using on appharbor to populate an existing database. It will also try to do a create if the database doesn't exists and throws if a change is detected (sorry no automatic updating yet). I hope someone finds it useful.
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Transactions;
namespace Deskspace.EntityFramework
{
/// <summary> A Database Initializer for appharbor </summary>
/// <typeparam name="T">Code first context</typeparam>
public class PopulateOnly<T> : IDatabaseInitializer<T> where T : DbContext
{
private EdmMetadata metadata;
private enum Status
{
Compatable,
Invalid,
Missing
}
/// <summary> Initializer that supports creating or populating a missing or empty database </summary>
/// <param name="context"> Context to create for </param>
public void InitializeDatabase(T context)
{
// Get metadata hash
string hash = EdmMetadata.TryGetModelHash(context);
bool exists;
using (new TransactionScope( TransactionScopeOption.Suppress )) {
exists = context.Database.Exists();
}
if (exists) {
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
var dbHash = GetHashFromDatabase( objectContext );
Status compatability =
string.IsNullOrEmpty( dbHash )?
Status.Missing :
(dbHash != hash)?
Status.Invalid :
Status.Compatable;
if (compatability == Status.Missing) {
// Drop all database objects
ClearDatabase( objectContext );
// Recreate database objects
CreateTables( objectContext );
// Save the new hash
SaveHash( objectContext, hash );
} else if (compatability == Status.Invalid) {
throw new Exception(
"EdmMetadata does not match, manually update the database, expected: " +
Environment.NewLine +
"<[(" + hash + ")}>"
);
}
} else {
context.Database.Create();
context.SaveChanges();
}
}
private void ClearDatabase(ObjectContext objectContext)
{
objectContext.ExecuteStoreCommand( DropAllObjects );
}
private void CreateTables(ObjectContext objectContext)
{
string dataBaseCreateScript = objectContext.CreateDatabaseScript();
objectContext.ExecuteStoreCommand( dataBaseCreateScript );
}
private void SaveHash(ObjectContext objectContext, string hash)
{
objectContext.ExecuteStoreCommand( string.Format(UpdateEdmMetaDataTable, hash.Replace( "'", "''" )) );
}
private string GetHashFromDatabase(ObjectContext objectContext)
{
foreach (var item in objectContext.ExecuteStoreQuery<string>( GetEdmMetaDataTable )) {
return item;
}
return string.Empty;
}
private const string UpdateEdmMetaDataTable = #"
Delete From EdmMetadata;
Insert Into EdmMetadata (ModelHash) Values ('{0}');";
private const string GetEdmMetaDataTable = #"
If Exists (Select * From INFORMATION_SCHEMA.TABLES tables where tables.TABLE_NAME = 'EdmMetaData')
Select Top 1 ModelHash From EdmMetadata;
Else
Select '';";
private const string DropAllObjects = #"
declare #n char(1)
set #n = char(10)
declare #stmt nvarchar(max)
-- procedures
select #stmt = isnull( #stmt + #n, '' ) +
'drop procedure [' + name + ']'
from sys.procedures
-- check constraints
select #stmt = isnull( #stmt + #n, '' ) +
'alter table [' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
from sys.check_constraints
-- functions
select #stmt = isnull( #stmt + #n, '' ) +
'drop function [' + name + ']'
from sys.objects
where type in ( 'FN', 'IF', 'TF' )
-- views
select #stmt = isnull( #stmt + #n, '' ) +
'drop view [' + name + ']'
from sys.views
-- foreign keys
select #stmt = isnull( #stmt + #n, '' ) +
'alter table [' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
from sys.foreign_keys
-- tables
select #stmt = isnull( #stmt + #n, '' ) +
'drop table [' + name + ']'
from sys.tables
-- user defined types
select #stmt = isnull( #stmt + #n, '' ) +
'drop type [' + name + ']'
from sys.types
where is_user_defined = 1
exec sp_executesql #stmt";
}
}

Just to contribute to #Luhmann's solution, here's mine but slightly changed to drop the FK and PK properly.
using System.Data.Entity;
using System.Data.Entity.Design;
using System.Data.Entity.Infrastructure;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
namespace SISQuote.Server.Persistence
{
public class DontDropExistingDbCreateTablesIfModelChanged<T> : IDatabaseInitializer<T> where T : DbContext
{
private EdmMetadata edmMetaData;
public bool TryInitializeDatabase(T context)
{
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
string modelHash = GetModelHash(objectContext);
if (CompatibleWithModel(modelHash, context, objectContext))
return false;
DeleteExistingTables(objectContext);
CreateTables(objectContext);
SaveModelHashToDatabase(context, modelHash, objectContext);
return true;
}
public void InitializeDatabase(T context)
{
TryInitializeDatabase(context);
}
private void SaveModelHashToDatabase(T context, string modelHash, ObjectContext objectContext)
{
if (edmMetaData != null)
objectContext.Detach(edmMetaData);
edmMetaData = new EdmMetadata();
context.Set<EdmMetadata>().Add(edmMetaData);
edmMetaData.ModelHash = modelHash;
context.SaveChanges();
}
private void CreateTables(ObjectContext objectContext)
{
string dataBaseCreateScript = objectContext.CreateDatabaseScript();
objectContext.ExecuteStoreCommand(dataBaseCreateScript);
}
private void DeleteExistingTables(ObjectContext objectContext)
{
objectContext.ExecuteStoreCommand(DeleteAllTablesScript);
}
private string GetModelHash(ObjectContext context)
{
var csdlXmlString = GetCsdlXmlString(context).ToString();
return ComputeSha256Hash(csdlXmlString);
}
public bool CompatibleWithModel(DbContext context)
{
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
return CompatibleWithModel(GetModelHash(objectContext), context, objectContext);
}
private bool CompatibleWithModel(string modelHash, DbContext context, ObjectContext objectContext)
{
var isEdmMetaDataInStore = objectContext.ExecuteStoreQuery<int>(LookupEdmMetaDataTable).FirstOrDefault();
if (isEdmMetaDataInStore == 1)
{
edmMetaData = context.Set<EdmMetadata>().FirstOrDefault();
if (edmMetaData != null)
{
return modelHash == edmMetaData.ModelHash;
}
}
return false;
}
private string GetCsdlXmlString(ObjectContext context)
{
if (context != null)
{
var entityContainerList = context.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.SSpace);
if (entityContainerList != null)
{
EntityContainer entityContainer = entityContainerList.FirstOrDefault();
var generator = new EntityModelSchemaGenerator(entityContainer);
var stringBuilder = new StringBuilder();
var xmlWRiter = XmlWriter.Create(stringBuilder);
generator.GenerateMetadata();
generator.WriteModelSchema(xmlWRiter);
xmlWRiter.Flush();
return stringBuilder.ToString();
}
}
return string.Empty;
}
private static string ComputeSha256Hash(string input)
{
byte[] buffer = new SHA256Managed().ComputeHash(Encoding.ASCII.GetBytes(input));
var builder = new StringBuilder(buffer.Length * 2);
foreach (byte num in buffer)
{
builder.Append(num.ToString("X2", CultureInfo.InvariantCulture));
}
return builder.ToString();
}
private const string DeleteAllTablesScript =
#"declare #cmd varchar(4000)
DECLARE cmds0 CURSOR FOR
SELECT 'ALTER TABLE ' + TABLE_NAME + ' DROP CONSTRAINT ' + CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'
DECLARE cmds1 CURSOR FOR
SELECT 'ALTER TABLE ' + TABLE_NAME + ' DROP CONSTRAINT ' + CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
DECLARE cmds2 CURSOR FOR
SELECT 'TRUNCATE TABLE ' + TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
DECLARE cmds3 CURSOR FOR
SELECT 'DROP TABLE [' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES
open cmds0
while 1=1
begin
fetch cmds0 into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds0
deallocate cmds0
open cmds1
while 1=1
begin
fetch cmds1 into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds1
deallocate cmds1
open cmds2
while 1=1
begin
fetch cmds2 into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds2
deallocate cmds2
open cmds3
while 1=1
begin
fetch cmds3 into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds3
deallocate cmds3";
private const string LookupEdmMetaDataTable =
#"Select COUNT(*)
FROM INFORMATION_SCHEMA.TABLES T
Where T.TABLE_NAME = 'EdmMetaData'";
}
}

I took a slightly different approach to this problem. This seems like as good a place as any to share the results.
I want to only create tables that don't already exist in the database. This has the benefit of being able to roll out new tables without erasing the rest of the database.
This also helps if you have multiple data contexts in an inheritance chain. For example, if you split your application into different assemblies. You might have a data context in a "core" module, and then inherit it in a different assembly for add-on modules. This configuration works fine, but the built-in Drop/Create initializers don't like it because the model hash is changing all the time. By checking table existance, initialization takes slightly longer, but then you have none of these issues.
Anyway, here's the code:
/// <summary>
/// Database Initializer to create tables only if they don't already exist.
/// It will never drop the database. Does not check the model for compatibility.
/// </summary>
/// <typeparam name="TContext">The data context</typeparam>
public class CreateTablesOnlyIfTheyDontExist<TContext> : IDatabaseInitializer<TContext>
where TContext : DataContext
{
public void InitializeDatabase(TContext context)
{
using (new TransactionScope(TransactionScopeOption.Suppress))
{
// If the database doesn't exist at all then just create it like normal.
if (!context.Database.Exists())
{
context.Database.Create();
return;
}
// get the object context
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
// get the database creation script
var script = objectContext.CreateDatabaseScript();
if (context.Database.Connection is SqlConnection)
{
// for SQL Server, we'll just alter the script
// add existance checks to the table creation statements
script = Regex.Replace(script,
#"create table \[(\w+)\]\.\[(\w+)\]",
"if not exists (select * from INFORMATION_SCHEMA.TABLES " +
"where TABLE_SCHEMA='$1' and TABLE_NAME = '$2')\n$&");
// add existance checks to the table constraint creation statements
script = Regex.Replace(script,
#"alter table \[(\w+)\]\.\[(\w+)\] add constraint \[(\w+)\]",
"if not exists (select * from INFORMATION_SCHEMA.TABLE_CONSTRAINTS " +
"where TABLE_SCHEMA='$1' and TABLE_NAME = '$2' " +
"and CONSTRAINT_NAME = '$3')\n$&");
// run the modified script
objectContext.ExecuteStoreCommand(script);
}
else if (context.Database.Connection is SqlCeConnection)
{
// SQL CE doesn't let you use inline existance checks,
// so we have to parse each statement out and check separately.
var statements = script.Split(new[] { ";\r\n" },
StringSplitOptions.RemoveEmptyEntries);
foreach (var statement in statements)
{
var quoteSplitStrings = statement.Split('"');
if (statement.StartsWith("CREATE TABLE"))
{
// Create a table if it does not exist.
var tableName = quoteSplitStrings[1];
const string sql =
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " +
"WHERE TABLE_NAME='{0}'"
var checkScript = string.Format(sql, tableName);
if (objectContext.ExecuteStoreQuery<int>(checkScript).First() == 0)
objectContext.ExecuteStoreCommand(statement);
}
else if (statement.Contains("ADD CONSTRAINT"))
{
// Add a table constraint if it does not exist.
var tableName = quoteSplitStrings[1];
var constraintName = quoteSplitStrings[3];
const string sql =
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS " +
"WHERE TABLE_NAME='{0}' AND CONSTRAINT_NAME='{1}'";
var checkScript = string.Format(sql, tableName, constraintName);
if (objectContext.ExecuteStoreQuery<int>(checkScript).First() == 0)
objectContext.ExecuteStoreCommand(statement);
}
else
{
// Not sure what else it could be. Just run it.
objectContext.ExecuteStoreCommand(statement);
}
}
}
else
{
throw new InvalidOperationException(
"This initializer is only compatible with SQL Server or SQL Compact Edition"
);
}
}
}
}

I too was looking for a good solution since godaddy does not allow drop/creation of database and thus no tables created. Since the newer version of Entity Framework has obsoleted EDMData, I modified Alex's code to see if a DropMeToRecreateDatabase table exists or not, if it doesnt exist, it deletes all tables and recreates new tables.
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Linq;
namespace LadyTreble.DatabaseInitializer
{
public class DontDropExistingDbCreateTablesIfTableDropped<T> : IDatabaseInitializer<T> where T : DbContext
{
public bool TryInitializeDatabase(T context)
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
if (objectContext.ExecuteStoreQuery<int>(GetTableCount).FirstOrDefault() == 0)
{
this.DeleteExistingTables(objectContext);
this.CreateTables(objectContext);
}
return true;
}
public void InitializeDatabase(T context)
{
this.TryInitializeDatabase(context);
}
private void CreateTables(ObjectContext objectContext)
{
string dataBaseCreateScript = objectContext.CreateDatabaseScript();
objectContext.ExecuteStoreCommand(dataBaseCreateScript);
}
private void DeleteExistingTables(ObjectContext objectContext)
{
objectContext.ExecuteStoreCommand(DeleteAllTablesScript);
}
private const string DeleteAllTablesScript =
#"declare #cmd varchar(4000)
DECLARE cmds0 CURSOR FOR
SELECT 'ALTER TABLE ' + TABLE_NAME + ' DROP CONSTRAINT ' + CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'
DECLARE cmds1 CURSOR FOR
SELECT 'ALTER TABLE ' + TABLE_NAME + ' DROP CONSTRAINT ' + CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
DECLARE cmds2 CURSOR FOR
SELECT 'TRUNCATE TABLE ' + TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
DECLARE cmds3 CURSOR FOR
SELECT 'DROP TABLE [' + TABLE_NAME + ']' FROM INFORMATION_SCHEMA.TABLES
open cmds0
while 1=1
begin
fetch cmds0 into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds0
deallocate cmds0
open cmds1
while 1=1
begin
fetch cmds1 into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds1
deallocate cmds1
open cmds2
while 1=1
begin
fetch cmds2 into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds2
deallocate cmds2
open cmds3
while 1=1
begin
fetch cmds3 into #cmd
if ##fetch_status != 0 break
print #cmd
exec(#cmd)
end
close cmds3
deallocate cmds3
CREATE TABLE DropMeToRecreateDatabase(id int IDENTITY(1,1) NOT NULL)";
private const string GetTableCount =
#"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME = 'DropMeToRecreateDatabase'";
}
}

The Entity Designer Database Generation Power Pack will do this. Not sure if it works with Code First yet, but worth a shot.

Related

How can I listen postgresql database with SignalR Core in .net core project?

I'm working on .net core web application. I want to listen my PostgreSQL database. And if there are any changes on table, I have to got it.
So according to my research, I have to use SignalR Core. I did some example application with SignalR like chat app but none of them listen database. I couldn't find any example for this.
-Does It have to be trigger on PostgreSQL database?
-Does It have to be listener on code side?
-How can I use SignalR Core?
Please show me a way.
Thanks a lot.
This example is work asp.net core 3.0+. Full code is below.
Step 1. Create a trigger on PostgreSql for listening actions
create trigger any_after_alarm_speed after
insert
or
delete
or
update
on
public.alarm_speed for each row execute procedure alarm_speedf();
Step 2. Create Procedur on Postgresql
CREATE OR REPLACE FUNCTION public.alarm_speedf()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
IF TG_OP = 'INSERT' then
PERFORM pg_notify('notifyalarmspeed', format('INSERT %s %s', NEW.alarm_speed_id,
NEW.alarm_speed_date));
ELSIF TG_OP = 'UPDATE' then
PERFORM pg_notify('notifyalarmspeed', format('UPDATE %s %s', OLD.alarm_speed_id,
OLD.alarm_speed_date));
ELSIF TG_OP = 'DELETE' then
PERFORM pg_notify('notifyalarmspeed', format('DELETE %s %s', OLD.alarm_speed_id,
OLD.alarm_speed_date));
END IF;
RETURN NULL;
END;
$function$;
Step 3. Create Hub
public class speedalarmhub : Hub
{
private IMemoryCache _cache;
`private IHubContext<speedalarmhub> _hubContext;
public speedalarmhub(IMemoryCache cache, IHubContext<speedalarmhub> hubContext)
{
_cache = cache;
_hubContext = hubContext;
}
public async Task SendMessage()
{
if (!_cache.TryGetValue("SpeedAlarm", out string response))
{
SpeedListener speedlist = new SpeedListener(_hubContext,_cache);
speedlist.ListenForAlarmNotifications();
string jsonspeedalarm = speedlist.GetAlarmList();
_cache.Set("SpeedAlarm", jsonspeedalarm);
await Clients.All.SendAsync("ReceiveMessage", _cache.Get("SpeedAlarm").ToString());
}
else
{
await Clients.All.SendAsync("ReceiveMessage", _cache.Get("SpeedAlarm").ToString());
}
}
}
Step 4. Create Listener Controller
public class SpeedListener :Controller
{
private IHubContext<speedalarmhub> _hubContext;
private IMemoryCache _cache;
public SpeedListener(IHubContext<speedalarmhub> hubContext,IMemoryCache cache)
{
_hubContext = hubContext;
_cache = cache;
}
static string GetConnectionString()
{
var csb = new NpgsqlConnectionStringBuilder
{
Host = "yourip",
Database = "yourdatabase",
Username = "yourusername",
Password = "yourpassword",
Port = 5432,
KeepAlive = 30
};
return csb.ConnectionString;
}
public void ListenForAlarmNotifications()
{
NpgsqlConnection conn = new NpgsqlConnection(GetConnectionString());
conn.StateChange += conn_StateChange;
conn.Open();
var listenCommand = conn.CreateCommand();
listenCommand.CommandText = $"listen notifyalarmspeed;";
listenCommand.ExecuteNonQuery();
conn.Notification += PostgresNotificationReceived;
_hubContext.Clients.All.SendAsync(this.GetAlarmList());
while (true)
{
conn.Wait();
}
}
private void PostgresNotificationReceived(object sender, NpgsqlNotificationEventArgs e)
{
string actionName = e.Payload.ToString();
string actionType = "";
if (actionName.Contains("DELETE"))
{
actionType = "Delete";
}
if (actionName.Contains("UPDATE"))
{
actionType = "Update";
}
if (actionName.Contains("INSERT"))
{
actionType = "Insert";
}
_hubContext.Clients.All.SendAsync("ReceiveMessage", this.GetAlarmList());
}
public string GetAlarmList()
{
var AlarmList = new List<AlarmSpeedViewModel>();
using (NpgsqlCommand sqlCmd = new NpgsqlCommand())
{
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.CommandText = "sp_alarm_speed_process_get";
NpgsqlConnection conn = new NpgsqlConnection(GetConnectionString());
conn.Open();
sqlCmd.Connection = conn;
using (NpgsqlDataReader reader = sqlCmd.ExecuteReader())
{
while (reader.Read())
{
AlarmSpeedViewModel model = new AlarmSpeedViewModel();
model.alarm_speed_id = reader.GetInt32(0);
// you must fill your model items
AlarmList.Add(model);
}
reader.Close();
conn.Close();
}
}
_cache.Set("SpeedAlarm", SerializeObjectToJson(AlarmList));
return _cache.Get("SpeedAlarm").ToString();
}
public String SerializeObjectToJson(Object alarmspeed)
{
try
{
var jss = new JavaScriptSerializer();
return jss.Serialize(alarmspeed);
}
catch (Exception) { return null; }
}
private void conn_StateChange(object sender, System.Data.StateChangeEventArgs e)
{
_hubContext.Clients.All.SendAsync("Current State: " + e.CurrentState.ToString() + " Original State: " + e.OriginalState.ToString(), "connection state changed");
}
}
Step 5 Calling Hub
<script src="~/lib/signalr.js"></script>
<script type="text/javascript">
// Start the connection.
var connection = new signalR.HubConnectionBuilder()
.withUrl('/speedalarmhub')
.build();
connection.on('ReceiveMessage', function (message) {
var encodedMsg = message;
// Add the message to the page.
});
// Transport fallback functionality is now built into start.
connection.start()
.then(function () {
console.log('connection started');
connection.invoke('SendMessage');
})
.catch(error => {
console.error(error.message);
});
Step 6. Add below code Configuration Services at Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSignalR();
services.AddMemoryCache();
}
Step 7. add below code in Configure method
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapHub<speedalarmhub>("/speedalarmhub");
});
I want to listen my PostgreSQL database. And if there are any changes on table, I have to got it.
You can create a trigger associated with your specified table, and use the function pg_notify(text, text) to send a notification, like below.
Function
CREATE OR REPLACE FUNCTION mytestfunc() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' then
PERFORM pg_notify('notifytesttable', 'new record inserted');
ELSIF TG_OP = 'UPDATE' then
PERFORM pg_notify('notifytesttable', 'updated');
ELSIF TG_OP = 'DELETE' then
PERFORM pg_notify('notifytesttable', 'deleted');
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Trigger
CREATE TRIGGER any_after_testtable
AFTER INSERT OR DELETE OR UPDATE
ON testtable
FOR EACH ROW
EXECUTE PROCEDURE mytestfunc();
In your client application code, you can listen and receive notifications from PostgreSQL.
conn.Open();
conn.Notification += Conn_Notification;
using (var cmd = new NpgsqlCommand("LISTEN notifytesttable", conn))
{
cmd.ExecuteNonQuery();
}
In Notification event handler, you can call SignalR hub method to push notifications to SignalR clients.
private static void Conn_Notification(object sender, NpgsqlNotificationEventArgs e)
{
var notification_payload = e.Payload;
//code logic here
//call hub method to push PostgreSQL notifications that you received to SignalR client users
}
Test result
For detailed information about PostgreSQL LISTEN and NOTIFY features, you can check following links.
https://www.postgresql.org/docs/current/sql-notify.html
https://www.npgsql.org/doc/wait.html#processing-of-notifications

Executing Raw sql with Entity Framework Core

The below I can have a condition to execute view/table, which would load into AccountDataModel class.
dbcontext.AccountDataModel.FromSql($"select * from account where id=123").FirstOrDefault();
How can I retrieve without using a class model, if I want to retrieve just 1 or 2 columns
example: select name from account where id=123
Do I always need a class model?
ADO.NET works in EFCore =)
using Microsoft.EntityFrameworkCore;
using System.Data.Common;
using System.Data.SqlClient;
using System;
public void ExampleMethod(DbContext context)
{
SomeObjectResult result = null;
DbCommand cmd = context.Database.GetDbConnection().CreateCommand();
cmd.CommandText = "Select C.ID, C.CarModelID as ModelID, C.VIN, C.RegNumber,cast(C.CountValue as int) as Course,A.BrandID from A inner join C on A.ID = C.KeyID Where A.ID = #appID";
cmd.Parameters.Add(new SqlParameter("#appID", appointmentID));
if (cmd.Connection.State != ConnectionState.Open)
{
cmd.Connection.Open();
}
using (var reader = await cmd.ExecuteReaderAsync())
{
if (reader.Read())
{
result = new SomeObjectResult()
{
BrandID = (int)reader["BrandID"],
Course = (int)reader["Course"],
ID = (int)reader["ID"],
ModelID = (int?)reader["ModelID"],
RegNumber = (string)reader["RegNumber"],
VIN = (string)reader["VIN"]
};
}
}
}
Yes. like this :
var account = dbcontext.AccountDataModel.FromSql(#"select a.id, a.name from account a where a.id=123");
source : FromSql for non entity type
This queries the database
var name = dbcontext.GetDBConnection().Query("select name from account where id=123").FirstOrDefault();

How to check if table exists in a migration?

This is as close as I've got...
public static class Helpers
{
public static bool TableExists(this MigrationBuilder builder, string tableName)
{
bool exists = builder.Sql($#"SELECT 1 FROM sys.tables AS T
INNER JOIN sys.schemas AS S ON T.schema_id = S.schema_id
WHERE S.Name = 'SchemaName' AND T.Name = '{tableName}'");
return exists;
}
}
But how does one get a result form the SQL call?
Here is one solution...
public override void Up()
{
if (!Exists("dbo.MyTable"))
{
... do something
}
}
private static bool Exists(string tableName)
{
using (var context = new DbContext(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
var count = context.Database.SqlQuery<int>("SELECT COUNT(OBJECT_ID(#p0, 'U'))", tableName);
return count.Any() && count.First() > 0;
}
}
This query runs immediately rather than being defered like other DbMigration commands are - but that's why it works. The result is known straight away so that other commands can be queued (or not queued) as required.
It's not a perfect solution, but you could use an IF in SQL:
builder.Sql(#"
IF (EXISTS(SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'MySchema'
AND TABLE_NAME = 'TableName'))
BEGIN
--DO SOMETHING
END
");

Entity Framework 6 ToString(), formatting (DateTime format), query intercept

I`m not found correct way to search with linq2sql in DateTime (DateTime?) fields.
db.Items.Where(x => x.DateTime1.ToString().Contains("2014.08"))
Not work, because in linq2sql create CAST([XXXX.DateTime1] AS NVARCHAR(MAX)) = '04 Aug 2014' NOT 2014.08
I try use custom function mapping, but no result
Why don't you just use the Year and Month property? You should be able to convert the string input into Year and Month number. Then you do something like:
db.Items.Where(x =>
x.DateTime1.Value.Year == 2014
&& x.DateTime1.Value.Month == 8)
It will simply be converted to:
WHERE (2014 = (DATEPART (year, [Extent1].[Date])))
AND (8 = (DATEPART (month, [Extent1].[Date])))
update
You can use SqlFunctions.DatePart and DbFunctions.Right to produce following format yyyy.mm.dd.
db.Items.Where(x =>
(SqlFunctions.DatePart("yyyy", x.DateTime) + "."
+ DbFunctions.Right("0" + SqlFunctions.DatePart("m", x.DateTime1), 2) + "."
+ DbFunctions.Right("0" + SqlFunctions.DatePart("d", x.DateTime1), 2))
.Contains("2014.08"));
Function in MS SQL
CREATE FUNCTION [dbo].[ToString](#P sql_variant)
RETURNS NVARCHAR(20)
AS
BEGIN
IF (sql_variant_property(#P, 'BaseType') = 'datetime')
RETURN CONVERT(NVARCHAR(10), #P, 102) + ' ' + CONVERT(NVARCHAR(8), #P, 108);
RETURN CAST(#P as NVARCHAR(max));
END
Create sql execution Interceptor
public class DbCommandInterceptor : IDbCommandInterceptor
{
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
if (command.CommandText.IndexOf("CAST") != -1)
{
command.CommandText = command.CommandText.Replace("CAST(", "dbo.ToString(");
command.CommandText = command.CommandText.Replace("] AS nvarchar(max))", "])");
}
}
}
Add Interceptor to DbContext
public class DB : DbContext
{
public DB(): base(#"Data Source=localhost\SQLEXPRESS;Initial Catalog=EFTest")
{
DbInterception.Add(new DbCommandInterceptor ());
}
}

Entity Framework Firebird MigrationSqlGenerator: Deactivate Transaction for Create and Insert in one Table?

As mentioned here Entity Framework Code First - Firebird migration: No MigrationSqlGenerator? I'm trying to enable Migrations on my Firebird-Database.
for that purpose I'm writing the following implementation of the "MigrationSqlGenerator" (not finished!):
public class FirebirdMigrationSQLGenerator : SqlServerMigrationSqlGenerator
{
protected override DbConnection CreateConnection()
{
return DbProviderFactories.GetFactory("FirebirdSql.Data.FirebirdClient").CreateConnection();
}
protected override void Generate(CreateTableOperation createTableOperation)
{
using (var writer = Writer())
{
WriteCreateTable(createTableOperation, writer);
Statement(writer.InnerWriter.ToString(), true);
}
}
private void WriteCreateTable(CreateTableOperation createTableOperation, IndentedTextWriter writer)
{
writer.WriteLine("CREATE TABLE " + Name(createTableOperation.Name) + " (");
writer.Indent++;
var columnCount = createTableOperation.Columns.Count();
createTableOperation.Columns.Each(
(c, i) =>
{
Generate(c, writer);
if (i < columnCount - 1)
{
writer.WriteLine(",");
}
});
if (createTableOperation.PrimaryKey != null)
{
writer.Write(", PRIMARY KEY ");
writer.Write("(");
writer.Write(createTableOperation.PrimaryKey.Columns.Join(Quote));
writer.WriteLine(")");
}
else
{
writer.WriteLine();
}
writer.Indent--;
writer.Write(")");
}
private void Generate(ColumnModel column, IndentedTextWriter writer)
{
writer.Write(Quote(column.Name));
writer.Write(" ");
writer.Write(BuildColumnType(column));
if ((column.IsNullable != null)
&& !column.IsNullable.Value)
{
writer.Write(" NOT NULL");
}
if (column.DefaultValue != null)
{
writer.Write(" DEFAULT ");
writer.Write(Generate((dynamic)column.DefaultValue));
}
else if (!string.IsNullOrWhiteSpace(column.DefaultValueSql))
{
writer.Write(" DEFAULT ");
writer.Write(column.DefaultValueSql);
}
}
protected override void Generate(InsertHistoryOperation op)
{
using (var writer = Writer())
{
WriteinsertHistory(op, writer);
Statement(writer.InnerWriter.ToString(), true);
}
}
private void WriteinsertHistory(InsertHistoryOperation insertHistoryOperation, IndentedTextWriter writer)
{
StringBuilder model = new StringBuilder();
foreach (byte item in insertHistoryOperation.Model)
model.Append(item.ToString("X2"));
writer.Write("INSERT INTO \"" + insertHistoryOperation.Table.ToUpper() + "\" (migrationId, model, productVersion) ");
writer.Write(" values ( '{0}', '{1}', '{2}') ",
insertHistoryOperation.MigrationId,
"0x" + model.ToString(),
insertHistoryOperation.ProductVersion);
}
protected override string Quote(string identifier)
{
return identifier.Replace("PK_dbo.", "").ToUpper();
}
protected override string Name(string inString)
{
return "\"" + inString.Replace("dbo.", "").ToUpper() + "\"";
}
protected override string BuildColumnType(ColumnModel column)
{
String colType = base.BuildColumnType(column);
if (colType == "INT")
colType = "INTEGER";
return colType;
}
}
My problem is that the __MigrationHistory table is created uppercase. But since the "HistoryContext" is not, the first SELECT-Statement is throwing an Exception:
SELECT
"A"."A1" AS "C1"
FROM ( SELECT
COUNT("B"."A1") AS "A1"
FROM ( SELECT
1 AS "A1"
FROM "__MigrationHistory" AS "B"
) AS "B"
) AS "A"
Normaly I would insert "modelBuilder.Conventions.Remove()"
into the Context, but the HistroyContext is part of the framework and can't be changed...
Any ideas?
My Environment: EntityFramework 5.0.0 .NET 4.5 FirebirdClient 3.0.2.0
In Firebird tablenames are only case sensitive when quoted, so you either need to stop quoting tablenames (both on creation and in queries), or you need to stop upper casing tablenames and use them as is.
For example issueing a CREATE TABLE xyz ( ... ) will create a table XYZ that can be accessed using SELECT * FROM xyz, but also with XyZ,XYZ and "XYZ". It cannot be accessed using "xyz".
Creating a table as CREATE TABLE "xyz" ( ... ) wil create a table xyz, which can only be accessed using SELECT * FROM "xyz", but not with xyz (no quotes) or any other combination of casing and with or without quotes. On the other hand CREATE TABLE "XYZ" ( ... ) can be accessed using SELECT * FROM xyz and "XYZ", and any other case without quoting, but not with SELECT * FROM "xyz".
As far as I can tell from your code, you are creating tables unquoted in WriteCreateTable, therefor the name is stored uppercase, but you are inserting into them quoted. You may also want to look into the contract/expectations of the Quote and Name methods, as it looks to me like you got their implementation reversed (Quote does what Name should do and vice versa).