How do I write EF.Functions extension method? - sql-server

I see that EF Core 2 has EF.Functions property EF Core 2.0 Announcement which can be used by EF Core or providers to define methods that map to database functions or operators so that those can be invoked in LINQ queries. It included LIKE method that gets sent to the database.
But I need a different method, SOUNDEX() that is not included. How do I write such a method that passes the function to the database the way DbFunction attribute did in EF6? Or I need to wait for MS to implement it? Essentially, I need to generate something like
SELECT * FROM Customer WHERE SOUNDEX(lastname) = SOUNDEX(#param)

Adding new scalar method to EF.Functions is easy - you simply define extension method on DbFunctions class. However providing SQL translation is hard and requires digging into EFC internals.
However EFC 2.0 also introduces a much simpler approach, explained in Database scalar function mapping section of the New features in EF Core 2.0 documentation topic.
According to that, the easiest would be to add a static method to your DbContext derived class and mark it with DbFunction attribute. E.g.
public class MyDbContext : DbContext
{
// ...
[DbFunction("SOUNDEX")]
public static string Soundex(string s) => throw new Exception();
}
and use something like this:
string param = ...;
MyDbContext db = ...;
var query = db.Customers
.Where(e => MyDbContext.Soundex(e.LastName) == MyDbContext.Soundex(param));
You can declare such static methods in a different class, but then you need to manually register them using HasDbFunction fluent API.

EFC 3.0 has changed this process a little, as per https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#udf-empty-string
Example of adding CHARINDEX in a partial context class:
public partial class MyDbContext
{
[DbFunction("CHARINDEX")]
public static int? CharIndex(string toSearch, string target) => throw new Exception();
partial void OnModelCreatingPartial(
ModelBuilder modelBuilder)
{
modelBuilder
.HasDbFunction(typeof(MyDbContext).GetMethod(nameof(CharIndex)))
.HasTranslation(
args =>
SqlFunctionExpression.Create("CHARINDEX", args, typeof(int?), null));
}
}

Related

Changes not saved when using inherited DbContext

I'm using a loosely coupled model between my Silverlight client and my DomainService.
I'm using POCO with EF 4.1
I'm not using any of the scaffolding the tooling offers.
The DomainService class is declared as:
public partial class MyDomainService : DbDomainService<MyContext>
{
...
}
in the update method I have the following:
public UpdatePerson(PersonInfo source)
{
var person = DbContext.People.Find(source.Id);
person.Name = source.Name;
DbContext.SaveChanges();
}
But when I manually check the database the change is not saved. However if I modify the code to look like this --- all is fine:
public UpdatePerson(PersonInfo source)
{
using(var context = GetDbContext())
{
var person = context.People.Find(source.Id);
person.Name = source.Name;
context.SaveChanges();
}
}
I suppose I don't mind creating my own local context variable, but I'm curious what's going on under the covers that the first approach doesn't work.
Since you are not using the DomainService the way that it is designed to work you may find it doing several weird things. DbContext.SaveChanges is never supposed to be called by your code, it will get called by the DomainService in the PersistChangeset method after all of the CUD methods in the changeset have been processed.
The DbContext held by the DomainService has several properties changed. ProxyCreationEnabled, ValidateOnSaveEnabled, AutoDetectChangesEnabled, and LazyLoadingEnabled are all set to false. In your case, since AutoDetectChangesEnabled is set to false just changing the person.Name will not trigger the DbContext to know that there are any changes to person.Name.

Parameter must be an entity type exposed by the DomainService?

Trying to implement a domain service in a SL app and getting the following error:
Parameter 'spFolderCreate' of domain method 'CreateSharePointFolder' must be an entity type exposed by the DomainService.
[EnableClientAccess()]
public class FileUploadService : DomainService
{
public void CreateSharePointFolder(SharePointFolderCreate spFolderCreate)
{
SharePointFolder spf = new SharePointFolder();
spf.CreateFolder_ClientOM(spFolderCreate.listName, spFolderCreate.fileName);
}
[OperationContract]
void CreateSharePointFolder(SharePointFolderCreate spFolderCreate);
[DataContract]
public class SharePointFolderCreate
{
private string m_listName;
private string m_fileName;
[DataMember]
public string listName
{
get { return m_listName; }
set { m_listName = value; }
}
[DataMember]
public string fileName
{
get { return m_fileName; }
set { m_fileName = value; }
}
}
So am I missing something simple here to make this all work?
It may be that the framework is inferring the intended operation because you have the word "Create" prefixing the function name (CreateSharePointFolder). Details of this behaviour can be found here
Although that is all fine for DomainServices and EntityFramework, following the information in that article, it can be inferred that methods beginning "Delete" will be performing a delete of an entity, so must accept an entity as a parameter. The same is true for "Create" or "Insert" prefixed methods. Only "Get" or "Select" methods can take non-entity parameters, making it possible to pass a numeric id (for example) to a "Get" method.
Try changing your method name temporarily to "BlahSharePointFolder" to see if it is this convention of inferrance that's causing your problem.
Also, as there is no metadata defined for your SharePointFolderCreate DC, you might need to decorate the class (in addition to the [DataContract] attribute) with the [MetadataType] attribute. You will see how to implement this if you used the DomainServiceClass wizard and point to an EF model. There is a checkbox at the bottom for generating metadata. Somewhere in your solution.Web project you should find a domainservice.metadata.cs file. In this file, you will find examples of how to use the [MetadataType] attribute.
For the RIA WCF service to work correctly with your own methods, you need to ensure that all entities existing on the parameter list have at least one member with a [Key] attribute defined in their metadata class, and that the entity is returned somewhere on your DomainService in a "Get" method.
HTH
Lee

Entity Framework Code-first default data in database

How do I handle situations in which I need pre-existing data before the app is started or right after the database is generated. For example, I have a list of countries in which I'd like to load into the database after code-first generates it. How do I do this?
App is structured as follows:
Repository > Service > WebMVC
The xml is in the WebMVC project.
You create custom initializer, which inherits from DropCreateDatabaseIfModelChanges or DropCreateDatabaseAlways interface. Like:
public class EntitiesContextInitializer : DropCreateDatabaseIfModelChanges<-YourDbContext->
And then you overwrite Seed method like:
protected override void Seed(YourDbContext context)
Whole example might look like:
public class EntitiesContextInitializer : DropCreateDatabaseIfModelChanges<EntitiesContext>
{
protected override void Seed(EntitiesContext context)
{
List<Role> roles = new List<Role>
{
new Role {Id=1, Title="Admin"},
new Role {Id=2, Title="ProjectManager"},
new Role {Id=3, Title="Developer"}
};
// add data into context and save to db
foreach (Role r in roles)
{
context.Roles.Add(r);
}
context.SaveChanges();
}
}
Edit: After setting this up, you have to set up Initializer too, as Ladislav Mrnka mentioned.
Database.SetInitializer(new EntitiesContextInitializer());
ie.: in Global.asax:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
Database.SetInitializer(new EntitiesContextInitializer());
}
Don't forget to add using System.Data.Entity;
.....
You must create custom database initializer derived for example from DropCreateDatabaseIfModelChanges and fill data in overriden Seed method. Then you must use Database.SetInitializer to set your new initializer when application starts. Here is example (from CTP5) used to create custom index in the database.
For an example see the new MVC / Entity Framework tutorial series at
http://www.asp.net/entity-framework/tutorials#Using%20MVC
Both #1 and #4 show initializer classes.

How can I get started with PHPUnit, where my class construct requires a preconfigured db connection?

I have a class that uses a lot of database internally, so I built the constructor with a $db handle that I am supposed to pass to it.
I am just getting started with PHPUnit, and I am not sure how I should go ahead and pass the database handle through setup.
// Test code
public function setUp(/*do I pass a database handle through here, using a reference? aka &$db*/){
$this->_acl = new acl;
}
// Construct from acl class
public function __construct(Zend_Db_Adapter_Abstract $db, $config = array()){
You would do it like this:
public class TestMyACL extends PHPUnit_Framework_TestCase {
protected $adapter;
protected $config;
protected $myACL;
protected function setUp() {
$this->adapter = // however you create a new ZendDbADapter
$this->config = // however you create a new config array
$this->myACL = new ACL($this->adapter, $this->config); // This is the System Under Test (SUT)
}
}
IMHO, you need to work on your naming conventions. See Zend Framework Naming Conventions, for a start. An example would be the underscore, look up variables in the link. Also class naming.
You can do normally without reference same as constructor because this method is simplest.

TooManyRowsAffectedException with encrypted triggers

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.

Resources