I have started an EF6 project to store measurement results from analytical instruments. Each instrument has a built-in PC with and it's own results database.
Initially, the database initializer CreateDatabaseIfNotExists was used. On database creation, it creates an entry in the __MigrationHistory table with a non-unique MigrationId entry (timestamp differs from instrument to instrument, e.g. 201706011336597_InitialCreate), the ContextKey if the fully qualified type of my derived DbContext.
After a while, it was decided to add more result data to the database... Furtunately, only three new tables are required. There are no changes in the existing tables.
For that, I wanted to use the MigrateDatabaseToLatestVersion initializer. But I have to support the following two scenarios:
Existing database with the non-unique MigrationId, that has to be migrated to the extended version with the three new tables.
No database, create the database with the the MigrateDatabaseToLatestVersion initializer.
How can I do this?
I have created an initial migration using the add-migration PM console command from the initial DbContext. That works well with scenario 2 (no database exists). From that starting point I can update my DbContext and create a new migration with the three new tables.
But how to support scenario 1? The Up() method of the initial migration contains the table creation code, that is not nessessary, because the tables already exist. Is an empty migration (add-migration -IgnoreChanges) helpful, maybe with a later timestamp than the initial migration?
Note: I have no access from the PM console to the target database(s), only on my developer machine to a test database.
Thanks and best regards
Karsten
Update:
I have modified the created initial migration with the static flag TablesAlreadyCreated.
public partial class InitialMigraCreate : DbMigration
{
/// <summary>
/// Set this field to true, if the tables are already created by the
/// CreateDatabaseIfNotExists database initializer. Then, the Up()
/// and Down() methods do nothing, but the
/// migration is added to the __MigrationHistory table.
/// </summary>
public static bool TablesAlreadyCreated = false;
public override void Up()
{
if (TablesAlreadyCreated)
return;
// several CreateTable calls here
}
/// <inheritdoc/>
public override void Down()
{
if (TablesAlreadyCreated)
return;
// several Drop... calls here
}
}
I have also implemented a new database initializer class as follows:
public class MigrateDatabaseToLatestVersionEx<TContext, TMigrationsConfiguration> : IDatabaseInitializer<TContext>
where TContext : DbContext
where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
...
/// <inheritdoc />
public virtual void InitializeDatabase(TContext context)
{
if (context == null)
throw new ArgumentNullException("context");
// check whether a first migration exists from the CreateDatabaseIfNotExists database initializer
var firstConfig = new ConfigurationAutoCreatedDatabase();
firstConfig.TargetDatabase = _config.TargetDatabase;
var firstMigrator = new DbMigrator(firstConfig);
var firstDbMigrations = firstMigrator.GetDatabaseMigrations();
// create the default migrator with the current configuration
var migrator = new DbMigrator(_config);
if (1 == firstDbMigrations.Count(migra => migra.EndsWith("_InitialCreate", StringComparison.InvariantCultureIgnoreCase)))
{ // This database was created using the CreateDatabaseIfNotExists database initializer.
// That's an indication whether it's an old database
// Do the custom migration here!
InitialMigraCreate.TablesAlreadyCreated = true;
migrator.Update();
}
else
{ // do the default migration the database was created with this MigrateDatabaseToLatestVersionEx initializer
InitialMigraCreate.TablesAlreadyCreated = false;
migrator.Update();
}
}
}
It checks, whether the initial migration entry is from the CreateDatabaseIfNotExists initializer and disables the table creation/drop calls in the Up()/Down() methods in that case. ConfigurationAutoCreatedDatabase is a manually created derived DbMigrationsConfiguration class:
internal sealed class ConfigurationAutoCreatedDatabase : DbMigrationsConfiguration<MyNamespace.MyDbContext>
{
/// <summary>
/// Creates a <c>ConfigurationAutoCreated</c> object (default constructor).
/// </summary>
public ConfigurationAutoCreatedDatabase()
{
this.AutomaticMigrationsEnabled = false;
this.AutomaticMigrationDataLossAllowed = false;
this.ContextKey = "MyNamespace.MyDbContext";
}
}
So, it works for both scenarios. I hope that helps other guys with a similar problem. It would be interesting, if there is an out-of-the-box EF workflow for that task.
This is a very common scenario that EF handles nicely. The non-migration initializers (CreateDatabaseIfNotExists, etc) should be used in very early development (when you don't care about the data except for stuff that is seeded).
Once you are switching to migrations you should generate a baseline migration that takes a snapshot of your current model as you indicate (add-migration MyStartPoint -IgnoreChanges). This adds a migration with no Up() code and stores the current state of your code first model so that when you change the model only those changes are reflected. You could accomplish the same thing by commenting out the items that exist from the Up() code.
Now when you run against an existing database, it will check __MigrationHistory to see which migrations have been applied. If the database does not exist, it will be created. See here and here for more info.
Not sure what you are talking about with the MigrationId. EF handles that automatically unless you change your namespace (there is a workaround for that as well).
Related
I am migrating a legacy database to a new database which we need to access and "manage" (as oxymoronic as it might sound) primarily through Entity Framework Code-First.
We are using MS SQL Server 2014.
The legacy database contained some tables with computed columns. Typical GUID and DateTime stuff.
Technically speaking, these columns did not have a computed column specification, but rather where given a default value with NEWID() and GETDATE()
We all know that it is very easy to configure the DbContext to deal with those properties as follows:
modelBuilder.Entity<Foo>()
.Property(t => t.Guid)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
modelBuilder.Entity<Bar>()
.Property(t => t.DTS)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
The above would instruct the Entity Framework to ignore submitting any supplied values for such properties during INSERTs and UPDATEs.
But now we need to allow for import of legacy records and maintain the OLD values, including the PRIMARY KEY, which is marked as IDENTITY
This means we would have to set the Id, Guid and DTS properties to DatabaseGeneratedOption.None while inserting those records.
For the case of Id, we would have to somehow execute SET IDENTITY_INSERT ... ON/OFF within the connection session.
And we want to do this
importing process via Code-First as well.
If I modify the model and "temporarily" and set those properties to DatabaseGeneratedOption.None after the database has been created, we would get the typical:
The model backing the context has changed since the database was created. Consider using Code First Migrations to update the database.
I understand that we could generate an empty coded-migration with -IgnoreChanges so as to "establish" this latest version of the context, but this wouldn't be an acceptable strategy as we would have to be run empty migrations back-and-forth solely for this purpose.
Half an answer:
We have considered giving these properties nullable types, i.e.
public class Foo
{
...
public Guid? Guid { get; set; }
}
public class Bar
{
...
public DateTime? DTS { get; set; }
}
While caring about the default values in an initial DbMigration:
CreateTable(
"dbo.Foos",
c => new
{
Id = c.Int(nullable: false, identity: true),
Guid = c.Guid(nullable: false, defaultValueSql: "NEWID()"),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.Bars",
c => new
{
Id = c.Int(nullable: false, identity: true),
DTS = c.Guid(nullable: false, defaultValueSql: "GETDATE()"),
})
.PrimaryKey(t => t.Id);
The Question:
But the question remains: Is there a way to switch between DatabaseGeneratedOption.Identity, DatabaseGeneratedOption.Computed and DatabaseGeneratedOption.None at runtime?
At the very least, how could we turn DatabaseGeneratedOption.Identity on/off at runtime?
A certain amount of the configuration of the context is always going to be dependent on the runtime environment - for example, proxy generation and validation. As such, runtime configuration of the Entity Framework DbContext is something I leverage quite heavily.
Although I've never used this approach to switch the configuration of the context on a per use-case basis, I see no reason why this would not work.
In its simplest form, this can be achieved by having a set of EntityTypeConfiguration classes for each environment. Each configuration set is then wired to the DbContext on a per-environment basis. Again, in its simplest form this could be achieved by having a DbContext type per environment. In your case, this would be per use-case.
Less naively, I usually encapsulate the configuration of the context in an environment-specific unit of work. For example, the unit of work for an Asp.Net environment has an underlying DbContext configured to delegate validation to the web framework, as well as to turn off proxy generation to prevent serialisation issues. I imagine this approach would have similar usefulness to your problem.
For example (using brute force code):
// Foo Configuration which enforces computed columns
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
Property(p => p.DateTime).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
Property(p => p.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
}
}
// Foo configuration that allows computed columns to be overridden
public class FooConfiguration2 : EntityTypeConfiguration<Foo>
{
public FooConfiguration2()
{
Property(p => p.DateTime).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Property(p => p.Guid).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
}
// DbContext that enforces computed columns
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new FooConfiguration());
}
}
// DbContext that allows computed columns to be overridden
public class MyContext2 : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new FooConfiguration2());
}
}
This can obviously be tidied up - we usually use a combination of factory and strategy patterns to encapsulate the creation of a runtime specific context. In combination with a DI container this allows the correct set up configuration classes to be injected on a per-environment basis.
Example usage:
[Fact]
public void CanConfigureContextAtRuntime()
{
// Enforce computed columns
using (var context = new EfContext())
{
var foo1 = new Foo();
context.Foos.Add(foo1);
context.SaveChanges();
}
// Allow overridden computed columns
using (var context = new EfContext2())
{
var foo2 = new Foo { DateTime = DateTime.Now.AddYears(-3) };
context.Foos.Add(foo2);
context.SaveChanges();
}
// etc
}
I am using Entity Framework on a project, but am finding the large queries, especially those which use LEFT joins, to be very tedious to write, and hard to debug.
Is it common, or accepted practice, to make use of Views in the database, and then use those views within the EntityFramework? Or is this a bad practice?
the question is not very clear but there is no absolute right or wrong in Software. it all depends on your case.
there is native support for views in ef core but there is no native support for views in EF < 6. at least not in the current latest version 6.3. there is, however, a work around to this. in database first you would create your view via sql normally and when you reverse engineer your database, EF will treat your view as a normal model and will allow you to consume it regularly as you would do in a normal table scenario. in Code First it's a bit more tedious. you would create a POCO object that maps to the columns in your view. notice that you need to include an Id in this POCO class. for example
public class ViewPOCO
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id {get;set;}
public string ViewColumn1 {get;set;}
... etc.
}
you would add this POCO class in your DbContext
public class MyDbContext : DbContext
{
public virtual DbSet<ViewPOCO> MyView {get;set;}
}
now you will normally apply the command of adding migration through the package manager console
Add-Migration <MigrationName> <ConnectionString and provider Name>
now in the migration up and down you will notice that EF treats your Model as table. you would clear all of this and write your own sql to add/alter the view in the up and drop the view in the down method using the Sql function.
public override void Up()
{
Sql("CREATE OR ALTER VIEW <ViewName> AS SELECT NEWID() AS Id, ...");
}
public override void Down()
{
Sql("DROP VIEW <ViewName>");
}
First create your view.
Update Your .edmx File.
then use like this.
using (ManishTempEntities obj = new ManishTempEntities())
{
var a = obj.View_1.ToList();
}
I've enabled code-first migrations on my entity framework project, and have added several migrations which do things like rename tables. However, I have now deleted the database and caused entity framework to generate a brand new database based on my latest data model. If I try to run:
PM> Add-Migration TestMigration
... it tells me that I need to apply the existing migrations first. So I run:
PM> Update-Database
... but the trouble is that it's trying to update a database that doesn't need updating; it's already based on the latest data model. So I get an error when it tries to rename a table that now doesn't exist.
Is there some way I can indicate to data migrations that my database is up-to-date and doesn't need any migrations running on it? What should I do?
I've found a way to indicate that my database is up-to-date, and it's (unsurprisingly) based on modifying the __MigrationHistory table, which code-first migrations uses to determine which migrations to apply to the DB when you run Update-Database.
By the way, whilst researching this answer I came across a very nice reference for code-first migrations commands, which can be found at: http://dotnet.dzone.com/articles/ef-migrations-command
When the database is created from scratch automatically by EF, it will always put a single entry into the __MigrationHistory table, and that entry will have the MigrationId (currentDateTime)_InitialCreate. This represents the initial creation of the database that EF has just carried out. However, your migration history is not going to begin with that MigrationId because you will have started with something else.
To "fool" code-first migrations into thinking that you're on the latest migration, you need to delete that (currentDateTime)_InitialCreate entry from the __MigrationHistory table of the newly-created DB, and insert what would have been there if you still had the old DB which had had the migrations applied to it.
So, first delete everything from the newly-generated DB's __MigrationHistory table. Then, go into package manager console and run:
PM> Update-Database -Script
From the resulting SQL script, pull out all the lines beginning with:
INSERT INTO [__MigrationHistory]...
Then, run those INSERT statements within the context of the newly-created database. Check that each of those rows now exists in your __MigrationHistory table. When you next run:
PM> Update-Database
... you should get a message saying "No pending code-based migrations." Congratulations - you've fooled code-first migrations into thinking you're now on the latest migration, and you can continue where you left off adding new migrations from here.
I do think there should be some automated way of doing this built into EF code-first, though... maybe they should add something like:
PM> Update-Database -MigrationsTableOnly
... whereby it would clobber the existing entries in the migrations table, and just insert the new entries into migrations history for each migration defined in your project, but not actually try and run the migrations. Ah well.
UPDATE
I've found a way to automate this nicely, using a custom initializer's Seed method. Basically the Seed method deletes the existing migration history data when the DB is created, and inserts your migrations history. In my database context constructor, I register the custom initializer like so:
public class MyDatabaseContext : DbContext {
public MyDatabaseContext() : base() {
Database.SetInitializer(new MyDatabaseContextMigrationHistoryInitializer());
}
The custom initializer itself looks like this:
/// <summary>
/// This initializer clears the __MigrationHistory table contents created by EF code-first when it first
/// generates the database, and inserts all the migration history entries for migrations that have been
/// created in this project, indicating to EF code-first data migrations that the database is
/// "up-to-date" and that no migrations need to be run when "Update-Database" is run, because we're
/// already at the latest schema by virtue of the fact that the database has just been created from
/// scratch using the latest schema.
///
/// The Seed method needs to be updated each time a new migration is added with "Add-Migration". In
/// the package manager console, run "Update-Database -Script", and in the SQL script which is generated,
/// find the INSERT statement that inserts the row for that new migration into the __MigrationHistory
/// table. Add that INSERT statement as a new "ExecuteSqlCommand" to the end of the Seed method.
/// </summary>
public class MyDatabaseContextMigrationHistoryInitializer : CreateDatabaseIfNotExists<MyDatabaseContext> {
/// <summary>
/// Sets up this context's migration history with the entries for all migrations that have been created in this project.
/// </summary>
/// <param name="context">The context of the database in which the seed code is to be executed.</param>
protected override void Seed(MyDatabaseContext context) {
// Delete existing content from migration history table, and insert our project's migrations
context.Database.ExecuteSqlCommand("DELETE FROM __MigrationHistory");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210091606260_InitialCreate', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF54AD7E074A..., '5.0.0.net40')");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210102218467_MakeConceptUserIdNullable', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4..., '5.0.0.net40')");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210231418163_ChangeDateTimesToDateTimeOffsets', 0x1F8B0800000000000400ECBD07601C499625262F6D..., '5.0.0.net40')");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210251833252_AddConfigSettings', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF54AD7E..., '5.0.0.net40')");
context.Database.ExecuteSqlCommand("INSERT INTO __MigrationHistory (MigrationId, Model, ProductVersion) VALUES ('201210260822485_RenamingOfSomeEntities', 0x1F8B0800000000000400ECBD07601C499625262F6DCA7B7F4AF5..., '5.0.0.net40')");
}
}
Go to MigrationHistory table in sql server (under system folder)
it has row for your migration and db hash in it which must be the same with one in your migration file, just copy it from db to file.
In other words you need to sync your MigrationHistory table with actual migrations.
This implementation does not need to manually maintain records to be inserted to __MigrationHistory. Migrations are determined from assembly specified.
Maybe this helps.
My thanks goes to #Jez for initial idea.
/// <summary>
/// An implementation of IDatabaseInitializer that will:
/// 1. recreate database only if the database does not exist
/// 2. actualize __MigrationHistory to match current model state (i.e. latest migration)
/// </summary>
/// <typeparam name="TContext">The type of the context.</typeparam>
public class CreateDatabaseIfNotExistsAndMigrateToLatest<TContext> : CreateDatabaseIfNotExists<TContext>
where TContext : DbContext
{
private readonly Assembly migrationsAssembly;
/// <summary>
/// Gets the migration metadata for types retrieved from <paramref name="assembly"/>. Types must implement <see cref="IMigrationMetadata"/>.
/// </summary>
/// <param name="assembly">The assembly.</param>
/// <returns></returns>
private static IEnumerable<IMigrationMetadata> GetMigrationMetadata(Assembly assembly)
{
var types = assembly.GetTypes().Where(t => typeof(IMigrationMetadata).IsAssignableFrom(t));
var migrationMetadata = new List<IMigrationMetadata>();
foreach (var type in types)
{
migrationMetadata.Add(
(IMigrationMetadata)Activator.CreateInstance(type));
}
return migrationMetadata.OrderBy(m => m.Id);
}
/// <summary>
/// Gets the provider manifest token.
/// </summary>
/// <param name="db">The db.</param>
/// <returns></returns>
private static string GetProviderManifestToken(TContext db)
{
var connection = db.Database.Connection;
var token = DbProviderServices.GetProviderServices(connection).GetProviderManifestToken(connection);
return token;
}
/// <summary>
/// Gets the migration SQL generator. Currently it is <see cref="SqlServerMigrationSqlGenerator"/>.
/// </summary>
/// <returns></returns>
private static MigrationSqlGenerator GetMigrationSqlGenerator()
{
return new SqlServerMigrationSqlGenerator();
}
/// <summary>
/// Creates the operation for inserting into migration history. Operation is created for one <paramref name="migrationMetadatum"/>.
/// </summary>
/// <param name="migrationMetadatum">The migration metadatum.</param>
/// <returns></returns>
private static InsertHistoryOperation CreateInsertHistoryOperation(IMigrationMetadata migrationMetadatum)
{
var model = Convert.FromBase64String(migrationMetadatum.Target);
var op = new InsertHistoryOperation(
"__MigrationHistory",
migrationMetadatum.Id,
model,
null);
return op;
}
/// <summary>
/// Generates the SQL statements for inserting migration into history table.
/// </summary>
/// <param name="generator">The generator.</param>
/// <param name="op">The operation.</param>
/// <param name="token">The token.</param>
/// <returns></returns>
private static IEnumerable<MigrationStatement> GenerateInsertHistoryStatements(
MigrationSqlGenerator generator,
InsertHistoryOperation op,
string token)
{
return generator.Generate(new[] { op }, token);
}
/// <summary>
/// Runs the SQL statements on database specified by <paramref name="db"/> (<see cref="DbContext.Database"/>).
/// </summary>
/// <param name="statements">The statements.</param>
/// <param name="db">The db.</param>
private static void RunSqlStatements(IEnumerable<MigrationStatement> statements, TContext db)
{
foreach (var statement in statements)
{
db.Database.ExecuteSqlCommand(statement.Sql);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CreateDatabaseIfNotExistsAndMigrateToLatest{TContext}"/> class.
/// </summary>
/// <param name="migrationsAssembly">The migrations assembly.</param>
public CreateDatabaseIfNotExistsAndMigrateToLatest(Assembly migrationsAssembly)
{
this.migrationsAssembly = migrationsAssembly;
}
protected override void Seed(TContext context)
{
base.Seed(context);
// Get migration metadata for migrationAssembly
var migrationMetadata = GetMigrationMetadata(migrationsAssembly);
// Crate DbContext
var db = Activator.CreateInstance<TContext>();
// Remove newly created record in __MigrationHistory
db.Database.ExecuteSqlCommand("DELETE FROM __MigrationHistory");
// Get provider manifest token
var token = GetProviderManifestToken(db);
// Get sql generator
var generator = GetMigrationSqlGenerator();
foreach (var migrationMetadatum in migrationMetadata)
{
// Create history operation
var op = CreateInsertHistoryOperation(migrationMetadatum);
// Generate history insert statements
var statements = GenerateInsertHistoryStatements(generator, op, token);
// Run statements (SQL) over database (db)
RunSqlStatements(statements, db);
}
}
}
I have a database, 90% created with EF 4.1 + Code First approach on a SQL Server 2012; the rest is generated by some SQL code (FUNCTIONS, COMPUTED COLUMNS, VIEWS, INDEXES, ETC.).
Now, I needed to use ObjectContext and at the same time optimize performance, so I created some SQL Views directly in the db, which basically do some calculations (count, mix, sum, etc.) on the already CF's generated tables.
I'd like to use the above "external" SQL views inside my solution, possibly pointing to the same connectionstring of my CF context and using with the same repository I created.
I succeed to make an ADO.NET EDM of the Views (is this the right approach?), so now I have the Entity Model generated from db.
For the reasons described above, in first instance I used the existing data connection and I choose to do not save the additional connection string inside my web.config.
Now I have the edmx containing myModel and myModel.Store of the "external" views. For example, here's an extract of mymodel.Designer.cs, which seems to be the standard one I've seen in other edmx of other projects:
public partial class Entities : ObjectContext
{
#region Constructors
/// <summary>
/// Initializes a new Entities object using the connection string found in the 'Entities' section of the application configuration file.
/// </summary>
public Entities() : base("name=Entities", "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialize a new Entities object.
/// </summary>
public Entities(string connectionString) : base(connectionString, "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
/// <summary>
/// Initialize a new Entities object.
/// </summary>
public Entities(EntityConnection connection) : base(connection, "Entities")
{
this.ContextOptions.LazyLoadingEnabled = true;
OnContextCreated();
}
#endregion
.............
I'd like to query the "external" entities. I did several tests, but I did not succeed.
Could you tell me the right approach to the problem, please?
1) This is one of the tests I made. In this case I get an exception "The specified named connection is either not found in the configuration, not intended to be used with the EntityClient provider, or not valid":
public class TManagerRepository : ITManagerRepository, IDisposable
{
private TManagerContext context; // the context pointing to CF entities
private TManager.Models.Entities.SQL_Views.Entities entities; // the context pointing to the SQL views by the EDM
public TManagerRepository(TManagerContext context)
{
this.context = context;
this.entities = new TManager.Models.Entities.SQL_Views.Entities();
var test = (from d in this.entities.myview
select d);
}
2) Then I tried to make a specific connection too, but I get an exception which says "Could not find the conceptual model to validate".
Thank you very much for your precious help!
Best Regards
You cannot use same connection string for code first approach and for EDMX. Required connection strings have different format. So if you don't want to store connection string for EDMX context in your configuration file you must to built it manually.
I'm using nHibernate to update 2 columns in a table that has 3 encrypted triggers on it. The triggers are not owned by me and I can not make changes to them, so unfortunately I can't SET NOCOUNT ON inside of them.
Is there another way to get around the TooManyRowsAffectedException that is thrown on commit?
Update 1
So far only way I've gotten around the issue is to step around the .Save routine with
var query = session.CreateSQLQuery("update Orders set Notes = :Notes, Status = :Status where OrderId = :Order");
query.SetString("Notes", orderHeader.Notes);
query.SetString("Status", orderHeader.OrderStatus);
query.SetInt32("Order", orderHeader.OrderHeaderId);
query.ExecuteUpdate();
It feels dirty and is not easily to extend, but it doesn't crater.
We had the same problem with a 3rd party Sybase database. Fortunately, after some digging into the NHibernate code and brief discussion with the developers, it seems that there is a straightforward solution that doesn't require changes to NHibernate. The solution is given by Fabio Maulo in this thread in the NHibernate developer group.
To implement this for Sybase we created our own implementation of IBatcherFactory, inherited from NonBatchingBatcher and overrode the AddToBatch() method to remove the call to VerifyOutcomeNonBatched() on the provided IExpectation object:
public class NonVerifyingBatcherFactory : IBatcherFactory
{
public virtual IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
{
return new NonBatchingBatcherWithoutVerification(connectionManager, interceptor);
}
}
public class NonBatchingBatcherWithoutVerification : NonBatchingBatcher
{
public NonBatchingBatcherWithoutVerification(ConnectionManager connectionManager, IInterceptor interceptor) : base(connectionManager, interceptor)
{}
public override void AddToBatch(IExpectation expectation)
{
IDbCommand cmd = CurrentCommand;
ExecuteNonQuery(cmd);
// Removed the following line
//expectation.VerifyOutcomeNonBatched(rowCount, cmd);
}
}
To do the same for SQL Server you would need to inherit from SqlClientBatchingBatcher, override DoExectuteBatch() and remove the call to VerifyOutcomeBatched() from the Expectations object:
public class NonBatchingBatcherWithoutVerification : SqlClientBatchingBatcher
{
public NonBatchingBatcherWithoutVerification(ConnectionManager connectionManager, IInterceptor interceptor) : base(connectionManager, interceptor)
{}
protected override void DoExecuteBatch(IDbCommand ps)
{
log.DebugFormat("Executing batch");
CheckReaders();
Prepare(currentBatch.BatchCommand);
if (Factory.Settings.SqlStatementLogger.IsDebugEnabled)
{
Factory.Settings.SqlStatementLogger.LogBatchCommand(currentBatchCommandsLog.ToString());
currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
}
int rowsAffected = currentBatch.ExecuteNonQuery();
// Removed the following line
//Expectations.VerifyOutcomeBatched(totalExpectedRowsAffected, rowsAffected);
currentBatch.Dispose();
totalExpectedRowsAffected = 0;
currentBatch = new SqlClientSqlCommandSet();
}
}
Now you need to inject your new classes into NHibernate. There are at two ways to do this that I am aware of:
Provide the name of your IBatcherFactory implementation in the adonet.factory_class configuration property
Create a custom driver that implements the IEmbeddedBatcherFactoryProvider interface
Given that we already had a custom driver in our project to work around Sybase 12 ANSI string problems it was a straightforward change to implement the interface as follows:
public class DriverWithCustomBatcherFactory : SybaseAdoNet12ClientDriver, IEmbeddedBatcherFactoryProvider
{
public Type BatcherFactoryClass
{
get { return typeof(NonVerifyingBatcherFactory); }
}
//...other driver code for our project...
}
The driver can be configured by providing the driver name using the connection.driver_class configuration property. We wanted to use Fluent NHibernate and it can be done using Fluent as follows:
public class SybaseConfiguration : PersistenceConfiguration<SybaseConfiguration, SybaseConnectionStringBuilder>
{
SybaseConfiguration()
{
Driver<DriverWithCustomBatcherFactory>();
AdoNetBatchSize(1); // This is required to use our new batcher
}
/// <summary>
/// The dialect to use
/// </summary>
public static SybaseConfiguration SybaseDialect
{
get
{
return new SybaseConfiguration()
.Dialect<SybaseAdoNet12Dialect>();
}
}
}
and when creating the session factory we use this new class as follows:
var sf = Fluently.Configure()
.Database(SybaseConfiguration.SybaseDialect.ConnectionString(_connectionString))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<MyEntity>())
.BuildSessionFactory();
Finally you need to set the adonet.batch_size property to 1 to ensure that your new batcher class is used. In Fluent NHibernate this is done using the AdoNetBatchSize() method in a class that inherits from PersistenceConfiguration (see the SybaseConfiguration class constructor above for an example of this).
er... you might be able to decrypt them...
Edit: if you can't change code, decrypt, or disable then you have no code options on the SQL Server side.
However, You could try "disallow results from triggers Option" which is OK for SQL 2005 and SQL 2008 but will be removed in later versions. I don't know if it suppresses rowcount messages though.
Setting the "Disallow Results from Triggers" option to 1 worked for us (the default is 0).
Note that this option will not be available in a future releases of Microsoft SQL Server, but after it is no longer available it will behave as if it was set to 1. So setting this to 1 now fixes the problem and also give you the same behavior as will be in future releases.