Переопределение конструктора DbContext, созданного кодом
Я уверен, что я сделал это раньше, но я не могу понять, как сейчас! Мой сценарий:
// This is generated from EDMX
public partial class HOLDbEntities : DbContext
{
public HOLDbEntities()
: base("name=HOLDbEntities")
{
}
}
Теперь я хочу, чтобы эта строка соединения была легко изменена (я хочу реализовать из HOLDbEntities), поэтому мне нужно переопределить этот конструктор.
Я пробовал:
public partial class HOLDbEntities
{
private const string _contextName = "HOLDbEntities";
public static string ContextName { get { return _contextName; } }
public HOLDbEntities()
: base(ContextName)
{
}
}
Но это вызовет ошибку:
HOLDbEntities уже определяет член с именем "HOLDbEntities" с теми же параметрами.
Я могу понять, почему эти ошибки, но как я могу остановить создание автогенератора в первую очередь, чтобы сделать то, что я пытаюсь достичь?
Ответы
Ответ 1
Лучшее, что я могу предложить, это метод factory:
private HOLDbEntities(string contextName) : base(contextName) { }
public static HOLDbEntities Create() {
return new HOLDbEntities(ContextName);
}
и используйте HOLDbEntities.Create()
, а не new HOLDbEntities()
.
Ответ 2
Я проголосовал за предыдущий принятый ответ, потому что это довольно элегантный способ сделать это. Однако другим подходом было бы изменить шаблон T4, который генерирует класс dbContext.
При использовании EF DB сначала у вас есть .edmx файл и под ним у вас есть файл [Entity].Context.tt. Перейдите в этот файл и удалите (или измените) следующий код:
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
{
// Note: the DbSet members are defined below such that the getter and
// setter always have the same accessibility as the DbSet definition
if (Accessibility.ForReadOnlyProperty(entitySet) != "public")
{
#>
<#=codeStringGenerator.DbSetInitializer(entitySet)#>
<#
}
}
#>
теперь ваш контекстный класс будет генерировать без конструктора, поэтому вы должны иметь возможность идти и создавать его в расширенном классе.
Ответ 3
i изменил context.tt следующим образом:
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
{
// Note: the DbSet members are defined below such that the getter and
// setter always have the same accessibility as the DbSet definition
if (Accessibility.ForReadOnlyProperty(entitySet) != "public")
{
#>
<#=codeStringGenerator.DbSetInitializer(entitySet)#>
<#
}
}
#>
var Method = (typeof(Entities)).GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).FirstOrDefault(x => x.Name == "OnModelConstructed");
if (Method!=null) Method.Invoke(this,null);
}
поэтому я могу объявить метод OnModelConstructed в частичном классе контекста.
Ответ 4
Вот мое решение проблемы. Я редактирую файл TT, как предложил Дилан Хейс, и заменил его конструктором. В моем случае мне нужно было изменить имена схем только определенных схем. Я установил переменную в файле конфигурации, чтобы сообщить мне, в какой среде я находился и использовал правильную схему.
using System.Configuration;
using System.Data.Entity;
using System.Data.Entity.Core.Mapping;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Reflection;
using System.Xml;
namespace WS.Framework.WSJDEData
{
public partial class WSJDE : DbContext
{
public WSJDE()
: base("name=WSJDE")
{
ObjectContext context = (this as IObjectContextAdapter).ObjectContext;
string environment = ConfigurationManager.AppSettings.Get("Environment");
const string devCTL = "TESTCTL";
const string devDTA = "TESTDTA";
const string qaCTL = "CRPCTL";
const string qaDTA = "CRPDTA";
const string prodCTL = "PRODCTL";
const string prodDTA = "PRODDTA";
var x = Assembly.GetExecutingAssembly().GetManifestResourceStream("WSJDEData.WSJDE.ssdl");
XmlReader[] sReaders = new XmlReader[]
{
XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("WSJDEData.WSJDE.ssdl"))
};
XmlReader[] mReaders = new XmlReader[]
{XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("WSJDEData.WSJDE.msl"))};
StoreItemCollection sCollection = new StoreItemCollection(sReaders);
ObjectContext objContext = ((IObjectContextAdapter) context).ObjectContext;
MetadataWorkspace workspace = objContext.MetadataWorkspace;
EdmItemCollection cCollection = workspace.GetItemCollection(DataSpace.CSpace) as EdmItemCollection;
StorageMappingItemCollection csCollection = new StorageMappingItemCollection(cCollection, sCollection,
mReaders);
workspace.RegisterItemCollection(sCollection);
workspace.RegisterItemCollection(csCollection);
EntityContainer container = workspace.GetItem<EntityContainer>("WSJDEModelStoreContainer", DataSpace.SSpace);
foreach (EntitySetBase entitySetBase in container.BaseEntitySets)
{
string schema = entitySetBase.Schema;
if (schema != null)
{
string name = schema.Substring(schema.Length - 3);
if (name == "CTL")
{
switch (environment)
{
case "Dev":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, devCTL);
break;
case "QA":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, qaCTL);
break;
case "Prod":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, prodCTL);
break;
}
}
if (name == "DTA")
{
switch (environment)
{
case "Dev":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, devDTA);
break;
case "QA":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, qaDTA);
break;
case "Prod":
typeof (EntitySetBase).GetField("_schema",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(entitySetBase, prodDTA);
break;
}
}
}
}
}
}
}
Ответ 5
Я использовал функцию интерполяции строк в С# 6 в .tt
генерации кода .tt
.
Сгенерированный код становится
public MyEntities()
: base($"name={MyConfigurationManager.ConnectionStringKey("MyEntities")}")
{
}
когда вы используете
public <#=code.Escape(container)#>()
: base($"name={MyConfigurationManager.ConnectionStringKey("<#=container.Name#>")}")
в файле .tt
.
public static string ConnectionStringKey(string key)
в статическом классе MyConfigurationManager
в моем случае добавляет инициалы логина к ключу и проверяет, есть ли у какой-либо строки подключения в ConfigurationManager.ConnectionStrings
этот ключ; в этом случае этот ключ возвращается и в противном случае просто возвращает значение по умолчанию ключ.
Так что теперь строка подключения может отличаться для каждого пользователя.
Например
<add name="MyEntities" connectionString="metadata=res://*/Base.csdl|res://*/Base.ssdl|res://*/Base.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=local.base;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
<add name="MyEntitiesFB" connectionString="metadata=res://*/Base.csdl|res://*/Base.ssdl|res://*/Base.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=local.fb.base;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
означает, что пользователь FB использует последний ключ, а все остальные - первый ключ.