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
");
Related
I am dealing with creating billings that uses a fairly massive amount of data (2+ million records overall), so I was forced to use some direct SQL to speed up the data loading as using pure EF was too slow.
Using this entity:
[Keyless]
public class BillingAggregate
{
public List<Customer> Customers { get; set; }
public List<Debt> Debts { get; set; }
public List<Payment> Payments { get; set; }
public List<Credit> Credits { get; set; }
}
Added to the DataContext as:
public DbSet<BillingAggregate> BillingAggregate { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BillingAggregate>().ToSqlQuery("EXEC [dto].[GetForMonthlyBilling]");
}
With my Stored Procedure as:
CREATE PROCEDURE [dbo].[GetForMonthlyBilling]
AS
BEGIN
SET NOCOUNT ON;
CREATE TABLE #TempCustomerIDsForBilling([CustomerId] bigint)
INSERT INTO #TempCustomerIDsForBilling([CustomerId])
SELECT DISTINCT [CustomerId]
FROM [dbo].[Debt] debt
INNER JOIN [dbo].[DebtType] debtType ON debt.[DebtTypeId] = debtType.[DebtTypeId]
WHERE debtType.[IsCollectible] = 1
SELECT * FROM [dbo].[Customer] WHERE [CustomerId] IN (SELECT [CustomerId] FROM #TempCustomerIDsForBilling)
SELECT * FROM [dbo].[Debt] WHERE [CustomerId] IN (SELECT [CustomerId] FROM #TempCustomerIDsForBilling)
SELECT * FROM [dbo].[Payment] WHERE [CustomerId] IN (SELECT [CustomerId] FROM #TempCustomerIDsForBilling)
SELECT *
FROM [dbo].[Credit] credit
INNER JOIN [dbo].[Debt] debt ON credit.[DebtId] = debt.[DebtId]
WHERE [CustomerId] IN (SELECT [CustomerId] FROM #TempCustomerIDsForBilling)
DROP TABLE #TempCustomerIDsForBilling
END
GO
This seems to be all that the documentation that I was able to find requires me to do... however, when I do a standard query:
var billingAggregate = await dbContext.BillingAggregate.FirstOrDefaultAsync();
It immediately throws the error:
{"Sequence contains no elements"}
The immediacy of the error makes me think that the Stored Procedure fails to even run, as running it in SQL alone takes 40+ seconds... what am I missing?
For whomever this may help, as per Svyatoslav Danyliv's link, it seems this is currently not possible with EF Core (though, was possible with EF 6). As a workaround, I had to drop down to a lower lever API that EF Core uses. I created it as an Extension Method so that I never have to see this "garbage" again :)
public static class DbContextExtensions
{
public static async Task<List<BillingHelper>> GetBillingsAsync(this MyDataContext dbContext)
{
var customers = new List<Customer>();
var customerAddresses = new List<CustomerAddress>();
var debts = new List<Debt>();
var payments = new List<Payment>();
var credits = new List<Credit>();
using (var command = dbContext.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "[dbo].[GetForMonthlyBilling]";
command.CommandType = System.Data.CommandType.StoredProcedure;
if (command.Connection.State != System.Data.ConnectionState.Open)
command.Connection.Open();
using (var reader = await command.ExecuteReaderAsync())
{
if (reader != null && reader.HasRows)
{
var customerType = typeof(Customer);
var customerProperties = customerType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
var customerDictionary = customerProperties.ToDictionary(property => property.Name.ToUpper(), property => property);
while (await reader.ReadAsync())
{
var customer = new Customer();
for (int index = 0; index < reader.FieldCount; index++)
{
var currentColumnName = reader.GetName(index).ToUpper();
if (customerDictionary.ContainsKey(currentColumnName))
{
var property = customerDictionary[currentColumnName];
if ((property != null) && property.CanWrite)
{
var value = reader.GetValue(index);
property.SetValue(claimant, (value == DBNull.Value ? null : value), null);
}
}
}
customers.Add(claimant);
}
// This loads the next "SELECT", that is Result Set
await reader.NextResultAsync();
var customerAddressType = typeof(CustomerAddress);
// ... similar as above for Customer
await reader.NextResultAsync();
var debtType = typeof(Debt);
// ... similar as above for Customer
await reader.NextResultAsync();
// ...
}
}
}
// From here to the end of this method takes C# compiler a long time to do
// opportunity for optimization perhaps.
var billings = customers.Select(customer => new BillingHelper
{
CustomerId = customer.CustomerId,
Customer = customer,
Debts = debts.Where(x => x.CustomerId == customer.CustomerId).ToList(),
Payments = payments.Where(x => x.CustomerId == customer.CustomerId).ToList()
}).ToList();
foreach (var billing in billings)
{
var debtIds = billing.Debts.Select(x => x.DebtId);
billing.Credits = credits.Where(x => debtIds.Contains(x.DebtId)).ToList();
billing.Customer.Addresses = customerAddresses.Where(x => x.CustomerId == billing.CustomerId).ToList();
}
return billings;
}
}
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();
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).
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.
Please look at my sourcecode.
public class EntityQuery
{
public static Func<AdventureWork2008Container, IQueryable<SalesPerson>> selectQuery = CompiledQuery.Compile
(
(AdventureWork2008Container aw) =>
(
from s in aw.SalesPerson
join e in aw.Employee on s.BusinessEntityID equals e.BusinessEntityID
join p in aw.Person on s.BusinessEntityID equals p.BusinessEntityID
join bea in aw.BusinessEntityAddress on s.BusinessEntityID equals bea.BusinessEntityID
join a in aw.Address on bea.AddressID equals a.AddressID
join sp in aw.StateProvince on a.StateProvince equals sp
select s
)
);
public decimal Select(AdventureWork2008Container aw)
{
SalesPerson result = selectQuery(aw).First();
return result.SalesYTD;
}
public decimal Select2(AdventureWork2008Container aw)
{
SalesPerson result =
(
from s in aw.SalesPerson
join e in aw.Employee on s.BusinessEntityID equals e.BusinessEntityID
join p in aw.Person on s.BusinessEntityID equals p.BusinessEntityID
join bea in aw.BusinessEntityAddress on s.BusinessEntityID equals bea.BusinessEntityID
join a in aw.Address on bea.AddressID equals a.AddressID
join sp in aw.StateProvince on a.StateProvince equals sp
select s
).First();
return result.SalesYTD;
}
}
I try to call Select method about 1000 times and call Select2 method about 1000 times. But the result shows me that Select2 method is a bit faster than Select method about 0.005 s.(0.052/0.057 s.) Moreover, this capture doesn't include time for creating EntityQuery object.
What's wrong with my source code?
PS. the following code show how to call methods.
private void button1_Click(object sender, EventArgs e)
{
using (AdventureWork2008Container aw = new AdventureWork2008Container())
{
EntityQuery eq = new EntityQuery();
eq.Select(aw);
long lastTime = DateTime.Now.Ticks;
for (int i = 0; i < 1000; i++)
{
eq.Select(aw);
}
listBox1.Items.Add("Select 1 : " + ShowTime(lastTime));
}
GC.Collect();
}
private void button2_Click(object sender, EventArgs e)
{
using (AdventureWork2008Container aw = new AdventureWork2008Container())
{
EntityQuery eq = new EntityQuery();
eq.Select2(aw);
long lastTime = DateTime.Now.Ticks;
for (int i = 0; i < 1000; i++)
{
eq.Select2(aw);
}
listBox1.Items.Add("Select 2 : " + ShowTime(lastTime));
}
GC.Collect();
}
In Select2() generated code is
SELECT TOP 1 * FROM ...
In Select() aggregating is done on top of request. Don't know for sure, but it could lead to .Compile() results being discarded.
Try to move .First() into the compiled query.
public static Func<AdventureWork2008Container, SalesPerson> selectQuery = CompiledQuery.Compile
(
(AdventureWork2008Container aw) =>
(
from s in aw.SalesPerson
join e in aw.Employee on s.BusinessEntityID equals e.BusinessEntityID
join p in aw.Person on s.BusinessEntityID equals p.BusinessEntityID
join bea in aw.BusinessEntityAddress on s.BusinessEntityID equals bea.BusinessEntityID
join a in aw.Address on bea.AddressID equals a.AddressID
join sp in aw.StateProvince on a.StateProvince equals sp
select s
).First();
);
Hope this will help.