Postgres plv8 - custom error - lose custom properties - postgresql

Custom error lose custom properties
What steps will reproduce the problem:
create function
CREATE OR REPLACE FUNCTION public.utils ()
RETURNS void AS
$body$
this.dbError = function(message){
this.message = (message || '');
};
dbError.prototype = Error.prototype;
$body$
LANGUAGE 'plv8'
VOLATILE
RETURNS NULL ON NULL INPUT
SECURITY DEFINER
COST 100;
create trigger function
CREATE OR REPLACE FUNCTION public.test_trigger func ()
RETURNS trigger AS
$body$
var fn = plv8.find_function('public.utils');
fn();
var err = new dbError('this is a dbError');
err.someProp = 'lalala';
throw err;
return NEW;
$body$
LANGUAGE 'plv8'
VOLATILE
CALLED ON NULL INPUT
SECURITY DEFINER
COST 100;
set this trigger on any table on insteadof event and try to execute it
DO $$
plv8.find_function('public.utils')();
try{
plv8.execute('insert into Temp (field) value ($1)', [100]);
} catch(ex){
plv8.elog(NOTICE, ex instanceof dbError);
plv8.elog(NOTICE, ex.message);
plv8.elog(NOTICE, ex.someProp);
}
$$ LANGUAGE plv8;
we will see
true
this is a dbError
undefined
What is the expected output?
true
this is a dbError
lalala
if I do this in one scope - I get right result
DO $$
this.dbError = function(message){
this.message = (message || '');
};
dbError.prototype = Error.prototype;
try{
var err = new dbError('this is a dbError');
err.someProp = 'lalala';
throw err;
} catch(ex){
plv8.elog(NOTICE, ex instanceof dbError);
plv8.elog(NOTICE, ex.message);
plv8.elog(NOTICE, ex.someProp);
}
$$ LANGUAGE plv8;
the result:
true
this is a dbError
lalala

Related

Using TfrxMailExport from Fast Reports

I've been having a problem while using Fast Reports in Delphi,
The Object I'm using is TfrxMailExport,
The problem I'm facing is that the values of the email server aren't getting filled properly.
The Code:
email := TfrxMailExport.Create(self);
email.Subject := 'Teste';
email.Lines.Clear;
email.Lines.Add('Linha 1');
email.Lines.Add('Linha 2');
email.Lines.Add('Linha 3');
email.Lines.Add('Linha 4');
email.Address := 'email#email.com';
email.SmtpHost := '0.0.0.0';
email.SmtpPort := 25;
email.FromMail := 'email.email#email.com';
email.FromName := 'NAME';
email.Login := 'Login';
email.Password := 'Password';
email.TimeOut := 30;
email.Report := Rela;
rela.Export(email);
email.Destroy;
Only the E-Mail side gets filled
Since the post is not likely to get an answer (if there is one), I'm gonna post my workaround as a solution in case someone is having the same problem.
I created a form similar to the one in Fast Reports, I export the FR file to PDF, this one works fine.
Procedure SomeProc();
var pdf : TfrxPDFExport;
begin
pdf := TfrxPDFExport.Create(self);
pdf.Compressed := True;
pdf.EmbeddedFonts := False;
pdf.Background := True;
pdf.PrintOptimized := False;
pdf.Outline := False;
pdf.Transparency := False;
pdf.Quality := 95;
pdf.ProtectionFlags := [eModify, eCopy, eAnnot];
pdf.OpenAfterExport := False;
pdf.ShowProgress := False;
pdf.ShowDialog := false;
pdf.FileName := 'C:\SomeFolder\'+fileName+'.pdf';
pdf.HideToolbar := False;
pdf.HideMenubar := False;
pdf.HideWindowUI := False;
pdf.FitWindow := False;
pdf.CenterWindow := False;
pdf.PrintScaling := False;
myReport.Export(pdf);
end;
Then for the email, I used a C# .Net DLL, and called it from the Delphi application.
The C# code:
using RGiesecke.DllExport;
[DllExport("SendEmail", CallingConvention = CallingConvention.StdCall)]
public static string SendEmail(string txtTo, string txtToCC, string txtToBCC, string txtSubject, string txtMessage, string txtFrom, string txtServer, string txtPort, string txtUtilizador, string txtPasse, string txtFile ,bool cbSSL)
{
MailMessage message = new MailMessage();
SmtpClient smtpClient = new SmtpClient();
string msg = string.Empty;
try
{
MailAddress fromAddress = new MailAddress(txtFrom);
Attachment attachment = new Attachment(txtFile, System.Net.Mime.MediaTypeNames.Application.Pdf);
message.From = fromAddress;
message.To.Add(txtTo);
if (txtToCC != "")
message.CC.Add(txtToCC);
if (txtToBCC != "")
message.Bcc.Add(txtToBCC);
message.Attachments.Add(attachment);
message.Subject = txtSubject;
message.IsBodyHtml = true;
message.Body = txtMessage;
smtpClient.Timeout = 5000;
smtpClient.Host = txtServer;
int.TryParse(txtPort, out int port);
smtpClient.Port = port;
smtpClient.UseDefaultCredentials = false;
smtpClient.Credentials = new System.Net.NetworkCredential(txtUtilizador, txtPasse);
smtpClient.EnableSsl = cbSSL;
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
smtpClient.Send(message);
message.Dispose();
return "Message Sent.";
}
catch (Exception ex)
{
return ex.Message;
}
}
And to finish, calling it in Delphi.
function SendEmail(txtTo, txtToCC, txtToBCC, txtSubject, txtMessage, txtFrom, txtServer, txtPort, txtUtilizador, txtPasse, txtFicheiro : PAnsiChar; cbSSL : Boolean) : PAnsiChar; stdcall; external 'SendEmail.dll';
showmessage(SendEmail(PAnsiChar(AnsiString('To'))
, PAnsiChar(AnsiString('ToCC'))
, PAnsiChar(AnsiString('ToBCC'))
, PAnsiChar(AnsiString('Subject'))
, PAnsiChar(AnsiString('Message'))
, PAnsiChar(AnsiString('From'))
, PAnsiChar(AnsiString('Server'))
, PAnsiChar(AnsiString('Port'))
, PAnsiChar(AnsiString('User'))
, PAnsiChar(AnsiString('Pass'))
, PAnsiChar(AnsiString('File'))
, SSL));
I made a couple mistakes, like using a function to convert the strings without having to write each one, it doesn't work for some reason.
The function needs to specify it's a stdcall.

How to get inserted/updated row also with notification

class receiver : public pqxx::notification_receiver
{
std::optional<std::string> status;
public:
receiver(pqxx::connection_base & c, const std::string & channel)
: pqxx::notification_receiver(c, channel) { }
void operator() (const std::string & payload, int pid) override
{
status = payload;
std::cout << "Received notification: " << channel() << std::endl;
};
std::optional<std::string> get() {
auto s = status;
status = std::nullopt;
return s;
}
};
I have above class defined and have created required function and trigger as belows in postgresql
CREATE OR REPLACE FUNCTION notify_FN()
RETURNS trigger AS
$BODY$
BEGIN
PERFORM pg_notify('SendContactsList',
json_build_object(
'operation', 'TG_OP', 'record', row_to_json(NEW)
)::text
);
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER "Notify_Update"
AFTER INSERT OR DELETE OR UPDATE
ON monitoring_service.monitor_rules
FOR EACH ROW
EXECUTE PROCEDURE public.notify_FN();
I have defined object for the SendContactList in a function running in separate thread.Every time table is update console prints Received notification: SendContactList, Can I also print inserted/updated row also and How?

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

return a boolean - jdbcTemplate

I would like to return a boolean value using in this method:
public Boolean isSizeOk(String transactionId){
String sqlQuery = "SELECT true FROM customer_pool WHERE id = "+ transactionID + " AND level = 13)";
//The next line is the problem.
//If I am returning a Boolean List, I can write
List <Boolean> sizeResult = jdbcTemplate.queryForList(sqlQuery, Boolean.class, transactionId);
//But since I only want a boolean value, what will the statement be?
Boolean sizeResult = jdbcTemplate......?
return sizeResult;
}
Kindly help. Thanks.
If you want to write a method that checks that a record exists in the database you can use the following code:
Integer cnt = jdbcTemplate.queryForObject(
"SELECT count(*) FROM customer_pool WHERE id = ? AND level = 13)", Integer.class, id);
return cnt != null && cnt > 0
Counting rows in SQL just in order to get simple information about non-emptiness of result may be unnecessary overkill, you want just ask result set for first row and finish. For simple queries by primary key or other index the performance might be similar, however, for complex queries, or full table scan queries it might be slow. In Spring I prefer simple utility method
public boolean exists(String sql, Object... args) {
boolean result = query(sql, args, new ResultSetExtractor<Boolean>() {
#Override
public Boolean extractData(ResultSet rs) throws SQLException,DataAccessException {
boolean result = rs.next();
return result;
}
});
return result;
}
(Google "sql exists vs count" for more info.)
What about
// Change query accordingly
String query = "SELECT 1 FROM " + tableName + " WHERE " + idColumnName + " = ? LIMIT 1";
try {
jdbcTemplate.queryForObject(query, new Object[]{id}, Long.class);
return true;
} catch (EmptyResultDataAccessException e) {
return false;
}
Case 1: In case you are returning boolean:
Just check the size of sizeResult List, if the size is greater than 0 return true else return false.
Case 2: If you are returning boolean list then return type must be a boolean List.You must
write the method as:
public List<Boolean> isSizeOk(String transactionId, int sizeLimit){
String sqlQuery = "SELECT true FROM customer_pool WHERE id = ? AND level = 13)";
List <Boolean> sizeResult = jdbcTemplate.queryForList(sqlQuery, Boolean.class, transactionId);
Boolean sizeResult = jdbcTemplate......?
return sizeResult;
}

Entity Framework CTP 4 - Code First Custom Database Initializer

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.