Entity Framework Schema Redirection
If your runtime schema differs from your design-time schema, you need to update your Entity Framework metadata to hit your new schema.
Here's a simple solution for doing it at runtime for Entity Framework 4. I need to update it for EF 5 and 6.
// Copyright (c) Microsoft Corporation. All rights reserved
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using System.Xml.Linq;
using System.Xml;
using System.Data.Metadata.Edm;
using System.Data.SqlClient;
using System.Reflection;
using System.Data.EntityClient;
using System.Data.Mapping;
namespace EfSchemaRedirection
{
class Program
{
static T Connect<T>(string connectionString, string schema) where T : ObjectContext
{
var assembly = typeof(T).Assembly;
var rn = assembly.GetManifestResourceNames();
var ssdl = rn.Single(r => r.EndsWith(".ssdl"));
var csdl = rn.Single(r => r.EndsWith(".csdl"));
var msl = rn.Single(r => r.EndsWith(".msl"));
var doc = XDocument.Load(assembly.GetManifestResourceStream(ssdl));
XNamespace ns = "https://schemas.microsoft.com/ado/2009/02/edm/ssdl";
var entitySets = doc.Root
.Elements(ns + "EntityContainer").ToList()
.Elements(ns + "EntitySet").ToList();
foreach (var es in entitySets)
{
var dq = es.Descendants(ns + "DefiningQuery").FirstOrDefault();
if (dq != null) //warning hacking any defining queries is likely to be fragile and require customization.
{
XNamespace store = "https://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator";
var designTimeSchema = es.Attribute(store + "Schema").Value;
dq.Value = dq.Value.Replace("FROM [" + designTimeSchema + "].", "FROM [" + schema + "].")
.Replace("JOIN [" + designTimeSchema + "].", "JOIN [" + schema + "].");
}
else
{
es.SetAttributeValue("Schema", schema);
}
}
Func<string, XmlReader[]> getFromResource = (name) =>
{
using (var s = assembly.GetManifestResourceStream(name))
{
return new XmlReader[] { XDocument.Load(s).CreateReader() };
}
};
var workspace = new System.Data.Metadata.Edm.MetadataWorkspace();
var storeItems = new StoreItemCollection(new XmlReader[] { doc.CreateReader() });
var edmItems = new EdmItemCollection(getFromResource(csdl));
var mappingItems = new StorageMappingItemCollection(edmItems, storeItems, getFromResource(msl));
workspace.RegisterItemCollection(storeItems);
workspace.RegisterItemCollection(edmItems);
workspace.RegisterItemCollection(mappingItems);
workspace.RegisterItemCollection(new ObjectItemCollection());
workspace.LoadFromAssembly(assembly);
var storeConn = new SqlConnection(connectionString);
ConstructorInfo contextConstructor = typeof(T).GetConstructor(new Type[] { typeof(EntityConnection) });
var entityConn = new EntityConnection(workspace, storeConn);
return (T)contextConstructor.Invoke(new Object[] { entityConn });
}
static void Main(string[] args)
{
using (var db = Connect<SchemaTestEntities>(@"server=.;database=SchemaTest;Integrated Security=true","B"))
{
var t = db.T.First();
var vt = db.vts.First();
Console.WriteLine(t.source_schema);
Console.WriteLine(vt.source_schema);
var sql = string.Format("select * from {0}.[vt]","A");
var vt2 = db.ExecuteStoreQuery<vt>(sql).First();
Console.WriteLine(vt2.source_schema);
}
}
}
}
Comments
Anonymous
December 06, 2013
Thanks Dave!!! This works with EF6: public static T Connect<T>(string connectionString, string schema) where T : DbContext { var assembly = typeof(T).Assembly; var resourceNames = assembly.GetManifestResourceNames(); var ssdlName = resourceNames.Single(r => r.EndsWith(".ssdl")); var csdlName = resourceNames.Single(r => r.EndsWith(".csdl")); var mslName = resourceNames.Single(r => r.EndsWith(".msl")); var ssdlDocument = XDocument.Load(assembly.GetManifestResourceStream(ssdlName)); XNamespace ssdlNamespace = "schemas.microsoft.com/.../ssdl"; var functions = ssdlDocument.Root.Elements(ssdlNamespace + "Function").ToList(); foreach (var f in functions) { f.SetAttributeValue("Schema", schema); } var entitySets = ssdlDocument.Root.Elements(ssdlNamespace + "EntityContainer").ToList().Elements(ssdlNamespace + "EntitySet").ToList(); foreach (var es in entitySets) { es.SetAttributeValue("Schema", schema); } Debug.WriteLine(ssdlDocument.ToString(System.Xml.Linq.SaveOptions.DisableFormatting)); Func<string, XmlReader[]> getFromResource = (resourceName) => { using (var s = assembly.GetManifestResourceStream(resourceName)) { return new XmlReader[] { XDocument.Load(s).CreateReader() }; } }; var edmItems = new EdmItemCollection(getFromResource(csdlName)); var storeItems = new StoreItemCollection(new XmlReader[] { ssdlDocument.CreateReader() }); var storageMappingItems = new StorageMappingItemCollection(edmItems, storeItems, getFromResource(mslName)); var workspace = new MetadataWorkspace( () => { return edmItems; }, () => { return storeItems; }, () => { return storageMappingItems; } ); workspace.LoadFromAssembly(assembly); var storeConn = new SqlConnection(connectionString); ConstructorInfo contextConstructor = typeof(T).GetConstructor(new Type[] { typeof(ObjectContext) }); var entityConn = new EntityConnection(workspace, storeConn); var context = new ObjectContext(entityConn); return (T)contextConstructor.Invoke(new Object[] { context }); }Anonymous
January 17, 2014
Hi Dave, This works fine in my project. But the issue is var dbContext=Model<testContext>(); after returning if i use dbContext its still pointing to old schema but not schema. While the method which you is working perfectly without any error. Can you help me on this. Thanks . KCAnonymous
March 06, 2014
Thanks Dave! I´m using EF5, i did it work using part of your code and part of René code. I did some changes because i´m using Oracle, and add some code to resolve the problem that occurs when you have more than one DbContext in your project. I posted in Pastbin http://pastebin.com/UpqUzezq because of the comment size limitations. Sorry, my poor english!Anonymous
March 06, 2014
Hi, it´s me again. I just wanna leave a comment with my login. Thanks Dave! I´m using EF5, i did it work using part of your code and part of René code. I did some changes because i´m using Oracle, and add some code to resolve the problem that occurs when you have more than one DbContext in your project. I posted in Pastbin http://pastebin.com/UpqUzezq because of the comment size limitations. Sorry, my poor english!Anonymous
November 18, 2014
Thanks for the write-up and help from the comments. This works great with exception. When I don't use the redirection, I see Oracle queries against a 1-row table taking 0msecs. Note that there is an initial hit on the first query, but after that they all clock in at 0msecs. Using the redirection, I see a cost of 46msecs to 100msecs. I also see that the total Oracle query time is consistently +40msecs to +70msecs on top of the redirection cost. This seems significant to me. I'm guessing I have found two negatives:
- Linq-SQL query statements aren't cached (explanation for the 0msecs query response times versus consistent >0msecs)
- Reflection/manipulation takes time Anyone find a trick to cache or speed it up?