Modified Entity Workspace is ignored when generating SQL - entity-framework

I have an Entity Model (EDMX) file and EF 4.3.1. I am trying to make a run-time modification to the EDMX (change the store:Schema of the tables/entitySets used in generating the query). I am using code based on the EF Model Adapter project by B. Haynes.
It appears that I can make the changes to the XML just fine using the schema model adapter, and load it into a metadata workspace and then pass it to the connection. However, when the query is generated by the DbContext/EF framework code, it uses the old value for the schema.
Create a new MyEntities
Load the EDMX medata data manually
Replace the "store:Schema" value with the new desired value
Create the metadata workspace from the modified XML
Return a new EntityConnection using that modified workspace
Query the data (from x in db.Table select x)
This is the basics of what is going on. We create our dbContext by creating a new EntityConnection based on the modified workspace and the connection. There is also some provider wrapping and such going on, for logging, etc. Sorry if that's confusing.
public MyEntities(): base( this.Create("name=MyEntitiesConnStr"), true)
{
}
public static DbConnection Create(string connectionString)
{
var ecsb = ConnectionHelper.ResolveConnectionStringDetails(connectionString);
var workspace = GetModifiedEntityWorkspace(ecsb);
var storeConnection = DbProviderFactories.GetFactory(ecsb.Provider).CreateConnection();
Debug.Assert(storeConnection != null, "storeConnection != null");
storeConnection.ConnectionString = ecsb.ProviderConnectionString;
var wrappedConnection = MyWrappedConnetion.WrapConnection(storeConnection);
_log.Debug("Creating new entity connection");
var newEntityConnection = new EntityConnection(workspace, wrappedConnection);
WireEvents(wrappedConnection);
return newEntityConnection;
}
private static MetadataWorkspace GetModifiedEntityWorkspace(EntityConnectionStringBuilder ecsb)
{
// instantiate manager class
// read all XML items from the embedded resources
// change the store:schema to the real one for this environment
// <EntitySet Name="..." store:Type="Tables" store:Schema="SCM" store:Name="TBLX">
// create new MetadataWorksspace(ssdl,cdl,...)
}
Any idea where/why it is still getting the old Schema value for the query? I think it worked right with EF 4.0,

Turns out the problem was with the <DefiningQuery> element under the entity set.
This element contains a definition of the base query used to define the entity. Perhaps something changed and now they refer to that for speed reasons. It is necessary to modify that query as well, and then the schema change will take effect.
<EntitySet Name="MYTABLE" store:Type="Tables" store:Schema="MYSCHEMA" ...>
<DefiningQuery>
SELECT MYTABLE.COLUMN [...REPEAT..]
FROM MYSCHEMA.MYTABLE AS MYTABLE
</definingQuery>
So, changing "MYSCHEMA" in both those locations fixes it. Just the store:Schema element is not enough.

Related

Entity Framework telling me the model backing the context has changed

I have a weird problem with Entity Framework code first migrations. I've been using EF and code first migrations on a project for months now and things are working fine. I recently created a new migration and when running Update-Database a restored backup of my database I get this error:
The model backing the context has changed since the database was
created. Consider using Code First Migrations to update the database
The migration does something like the following:
public override void Up()
{
using (SomeDbContext ctx = new SomeDbContext())
{
//loop through table and update rows
foreach (SomeTable table in ctx.SomeTables)
table.SomeField = DoSomeCalculation(table.SomeField);
ctx.SaveChanges();
}
}
I'm not using the Sql() function because DoSomeCalculation must be done in C# code.
Usually when I get something like this is means that I have updated my model somehow and forgot to create a migration. However that's not the case this time. The weird thing is that the error isn't even occurring on a migration that I created a few days ago and had been working fine.
I looked a quite a few articles about this and they all seems to say call
Database.SetInitializer<MyContext>(null);
Doing that does seem to work, but my understanding (based on this article) is that doing that will remove EF's ability to determine when the database and model are out of sync. I don't want to do that. I just want to know why it thinks they are out of sync all of a sudden.
I also tried running Add-Migration just to see if what it thought changed about the model but it won't let me do that stating that I have pending migrations to run. Nice catch 22, Microsoft.
Any guesses as to what's going on here?
I'm wondering if maybe the fact that migration listed above is using EntityFramework is the problem. Seems like maybe since it's not the latest migration anymore, when EF gets to it tries to create a SomeDbContext object it checks the database (which is not fully up to date yet since we're in the middle of running migrations) against my current code model and then throws the "context has changed" error.
It's possibly related to your using EF within the migration. I'm not sure how you're actually managing this, unless you've set a null database initialiser.
If you need to update data within a migration, use the Sql function, e.g.
Sql("UPDATE SomeTable SET SomeField = 'Blah'");
You should note that the Up() method is not actually running at the time of doing the migration, it's simply used to set up the migration which is then run later. So although you may think you've done something in the migration above the bit where you're using EF, in reality that won't have actually run yet.
If you cannot refactor your calculation code so it can be written in SQL, then you would need to use some mechanism other than migrations to run this change. One possibility would be to use the Seed method in your configuration, but you would need to be aware that this does not keep track of whether the change has been run or not. For example...
internal sealed class Configuration : DbMigrationsConfiguration<MyContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(MyContext context)
{
// Code here runs any time ANY migration is performed...
}
}
I tried replacing the EntityFramework code with regular ADO.NET code and it seems to work. Here is what it looks like:
public override void Up()
{
Dictionary<long, string> idToNewVal = new Dictionary<long, string>();
using (SqlConnection conn = new SqlConnection("..."))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("SELECT SomeID, SomeField FROM SomeTable", conn))
{
SqlDataReader reader = cmd.ExecuteReader();
//loop through all fields, calculating the new value and storing it with the row ID
while (reader.Read())
{
long id = Convert.ToInt64(reader["SomeID"]);
string initialValue = Convert.ToString(reader["SomeField"]);
idToNewVal[id] = DoSomeCalculation(initialValue);
}
}
}
//update each row with the new value
foreach (long id in idToNewVal.Keys)
{
string newVal = idToNewVal[id];
Sql(string.Format("UPDATE SomeTable SET SomeField = '{0}' WHERE SomeID = {1}", newVal, id));
}
}

Does the defaultConnectionFactory influence the key and index generation with EF Code First?

Initially our solution had System.Data.Entity.Infrastructure.LocalDbConnectionFactory set as the defaultConnectionFactory type in our web.config.
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
I'm not sure we really need it like that since we use local SQL Server for dev and SQL Azure for real deployments. We use EF Code First and our tables get created with keys named like PK_dbo.Customers and FK_dbo.Customers_dbo.Employers_EmployerID and an index is created for each foreign key like IX_EmployerID.
We have switched to a custom connectionfactory based on the ideas in this post for a ReliableDbProvider created by Robert Moore because we want to have built in retry logic for transient failures with SQL Azure. It seems to work fine but it also seems like it causes the keys to be named differently (PK__Customer__A4AE64B8BB3388DF, Customer_Employer) and indexes to not be generated.
I didn't expect the factory to influence the generation. Any idea how it contributes?
After reflecting some code, seems like it has to do with the way the DbMigrationsConfiguration class which is used inside the DropCreateDatabaseIfModelChanges initializer works so we'll have to see if that can be overridden somehow.
public DbMigrationsConfiguration()
{
this.SetSqlGenerator("System.Data.SqlClient", new SqlServerMigrationSqlGenerator());
this.SetSqlGenerator("System.Data.SqlServerCe.4.0", new SqlCeMigrationSqlGenerator());
this.CodeGenerator = new CSharpMigrationCodeGenerator();
}
Still open to ideas!
Based on some reflected code, it looks like the issue is that there is hardcoded logic for non-System.Data.SqlClient or sqlce providers in DatabaseCreator class that forces the generation down a different path.
public void CreateDatabase(InternalContext internalContext, Func<DbMigrationsConfiguration, DbContext, DbMigrator> createMigrator, ObjectContext objectContext)
{
if (internalContext.CodeFirstModel == null || !(internalContext.ProviderName == "System.Data.SqlClient") && !(internalContext.ProviderName == "System.Data.SqlServerCe.4.0"))
{
internalContext.DatabaseOperations.Create(objectContext);
internalContext.SaveMetadataToDatabase();
}
else
{
Type type = internalContext.Owner.GetType();
DbMigrationsConfiguration dbMigrationsConfiguration = new DbMigrationsConfiguration();
dbMigrationsConfiguration.ContextType = type;
dbMigrationsConfiguration.AutomaticMigrationsEnabled = true;
dbMigrationsConfiguration.MigrationsAssembly = type.Assembly;
dbMigrationsConfiguration.MigrationsNamespace = type.Namespace;
dbMigrationsConfiguration.TargetDatabase = new DbConnectionInfo(internalContext.OriginalConnectionString, internalContext.ProviderName);
createMigrator(dbMigrationsConfiguration, internalContext.Owner).Update();
}
internalContext.MarkDatabaseInitialized();
}
In the end, we updated our datacontext constructor so that the DefaultConnectionFactory is set in code and not in config. In development (debug mode) only, if db doesn't exist, we set first to the SqlConnectionFactory since it generates the db with indexes and better naming that we want. After that or in release mode we want to use the custom provider which has retry logic we want.

MVC 4 project using a class library EF model

Using an EF model in the Models folder in my MVC 4 project, I succeeded to display data in a razor view using a coded class named Prod and a controller method as next:
public ActionResult Index()
{
IEnumerable<Prod> Pr = from p in db.Products
select new Prod
{
ProductId = p.ProductID,
ProductName = p.ProductName
};
return View(Pr);
}
Now I am trying to do the same thing using a model in a class library instead of the current one, so I added to my solution a new class library, added then a model using the same connection string, and mapping the same entities, then added to my MVC project a reference to the new class library, and put at the top of both MyController and Prod class the next:
using MyClassLibrary;
Then I deleted the old model, now when I try to display the view, I get the following error:
Unable to load the specified metadata resource.
Any help please ?
When you move or rename the project the data context (.edmx) is in the metadata part of the Entity Framework connection string has to change
you can try have
connectionString="metadata=res://*/MyModel.csdl|res://*/MyModel.s‌​sdl|res://*/MyModel.msl;
instead of
connectionString="metadata=res://*/Models.MyModel.csdl|res://*/Models.MyModel.s‌​sdl|res://*/Models.MyModel.msl;
or try deleting your context and recreating it then check the connection string it adds automatically.
You need to put your connectionstring in web.config in Mc4 web project
You need to Mention the datasource in the connection string.
If you have not used any other web.config file for views. Use you generic web.config file and upload a connection string with New datasource name , user and password.

Entity SQL doesn't work if edmx file in another project

I'm trying to use Enitity SQL to query data, but if the edmx file in another project, there will be an exception thrown. Below is my test steps.
Create a Class Library project and add an edmx file to it, create from database.
Create a Console Application, add the Class Library project to reference and copy the app.config file to this project.
Write the code as below
using (NorthwindEntities context = new NorthwindEntities())
{
string queryString = #"SELECT VALUE cus
FROM NorthwindEntities.Customers AS cus
WHERE cus.ID > 10";
ObjectQuery<Customers> cusQuery =
context.CreateQuery<Customers>(queryString);
List<Customers> cusList = cusQuery.ToList();
}
When I run the Console Application project, an exception is thrown: "'ID' is not a member of type 'NorthwindModel.Customers' in the currently loaded schemas."
It seems the schema doesn't loaded into the project, anyone has ideas?
Addional question: in this query, I select all the properties of this type, if I only select some of the properties, how to return an anonymous type of ObjectQuery?
Any suggestions are appreciate.
This is not an EF problem.
You have written the SQL statement by hand, you are not using the table definition that is in the EDMX file.
If you try to execute the SQL statement in the Query prompt of SQL Server, you will see that it fails there as well.
Try something like this:
var cus = from customers in context.Customers select customers;
var cusList = cus.ToList();

Create and User User Define Function in edmx

i want to create one function in edmx which return scaler value,
how to create it in SSDL and how to access it in code?
One problem you have is your SSDL is automatically generated by the 'EntityModelGenerator', so editing it will be wiped out by a rebuild. Your edits need to be done in th EDMX file.
So firstly, you have to decide is (1) your return value a calculation of sorts (i.e. adding values together in the application, rather than at database level), or (2) is it a direct call to a database stored procedure?
(1) First step is to add the function XML definition into your EDMX file:
<Function Name="LineTotal" ReturnType="decimal">
<Parameter Name="lineTotal" Type="MyDbModel.OrderDetail">
<DefiningExpression>
od.Price * od.Quantity
</DefiningExpression>
</Parameter>
</Function>
Now, although your EDMX knows about this function, your IntelliSense won't. So you have to add some code to make this work. It is a best practice to place these functions in a seperate class.
public class ModelDefinedFunctions
{
[EdmFunction("MyDbModel" , "LineTotal")] //Model Name and Function Name
public static decimal LineTotal(OrderDetail od)
{
throw new NotSupportedException("LineTotal cannot be directly used.");
}
}
Entity Framework will know to redirect this function call to the EDMX instead. Any direct call to this method, where the model does not exist will throw an exception.
You can then call it in your LINQ queries like
var productValues = from line in model.OrderDetails
select new
{
od.ProductID,
od.Price,
od.Quantity,
LineTotal = ModeDefinedFunctions.LineTotal(line)
};
(2) If you are adding a stored procedure directly, it is easier to drag and drop it onto the EDMX designer. There is a [FunctionImport()] attribute, but I haven't used it. You can drag and drop and see what code it generates in the EDMX file?
Alternatively, you can call the model.ExecuteCommand(<spname> , params object[] values
) stored procedure execution method.