Zur Zeit wird gefiltert nach: workaround
Filter zurücksetzen

Entity Framework - DbContext und der 2nd-Level Cache mit dem EFCachingProvider

Der DbContext stellt eigentlich ein Wrapper auf den ObjectContext dar. Ich bevorzuge mittlerweile den DbContext, da dieser einige Funktionalitäten bietet, die ich beim ObjectContext vermisse. Da wären bspw. das vereinfachte Change Tracking, der Support der DataAnnotations, der veinfachte Zugriff auf dem Context-Cache über die Local-Eigenschaft und das dieser neben Code First auch mit dem EDM-Designer verwendet werden kann.

Ein Nachteil ist jedoch die fehlende Unterstützung für das Caching. Für den ObjectContext gibt es den EFCachingProvider, der seine Aufgaben relativ gut verrichtet. Über den Konstruktor im ObjectContext wird dieser aktiviert.

Nun ist der DbContext ja nichts anderes als ein Wrapper und diesen kann auch eine ObjectContext-Instanz übergeben werden. Also kam ich auf eine ganz „wilde“ Idee, um den DbContext mit einer Zusammenarbeit mit dem EFCachingProvider zu überreden. Alternativ liesse sich das auch mit dem Proxy-Pattern realisieren, aber ich gebe die Hoffnung nicht auf, dass der EFCachingProvider ein fester Bestandteil vom EF werden wird.

Mit Hilfe der mitgelieferten T4-Vorlage, die den Einsatz des DbContext für DbFirst und ModelFirst ermöglicht, lässt sich der Caching-Support sehr einfach realisieren. Die angepasste T4-Vorlage sieht so aus:


<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#><#@
 output extension=".cs"#><#

var loader = new MetadataLoader(this);
var region = new CodeRegion(this);
var inputFile = @"SimpleModel.edmx";
var ItemCollection = loader.CreateEdmItemCollection(inputFile);

Code = new CodeGenerationTools(this);
EFTools = new MetadataTools(this);
ObjectNamespace = Code.VsNamespaceSuggestion();
ModelNamespace = loader.GetModelNamespace(inputFile);

EntityContainer container = ItemCollection.GetItems<EntityContainer>().FirstOrDefault();
if (container == null)
{
    return string.Empty;
}
#>
//------------------------------------------------------------------------------
// <auto-generated>
// <#=GetResourceString("Template_GeneratedCodeCommentLine1")#>
//
// <#=GetResourceString("Template_GeneratedCodeCommentLine2")#>
// <#=GetResourceString("Template_GeneratedCodeCommentLine3")#>
// </auto-generated>
//------------------------------------------------------------------------------

<#

if (!String.IsNullOrEmpty(ObjectNamespace))
{
#>
namespace <#=Code.EscapeNamespace(ObjectNamespace)#>
{
<#
    PushIndent(CodeRegion.GetIndent(1));
}

#>
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.EntityClient;
using System.Data.Objects;
using System.IO;
using EFProviderWrapperToolkit;
using EFTracingProvider;
using EFCachingProvider;
using EFCachingProvider.Caching;

// Workaround, create an objectcontext for 2nd Level Cache with EFProviderToolkit
<#=Accessibility.ForType(container)#> partial class <#=Code.Escape(container)#>Context : ObjectContext
{
	private TextWriter logOutput;
	
    #region Konstruktoren
    public <#=Code.Escape(container)#>Context() 
    : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
                    "name=<#=container.Name#>",
                    /*"EFTracingProvider",*/
                    "EFCachingProvider"
            ), "<#=container.Name#>")

	{
		
    }
	
	public <#=Code.Escape(container)#>Context(string connectionString)
            : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
                    connectionString,
                    /*"EFTracingProvider",*/
                    "EFCachingProvider"
            ))
    {
    }
    #endregion
	
	// ObjectSets are not required, when we use the DbContext.
	
    #region Tracing Extensions

    private EFTracingConnection TracingConnection
    {
        get { return this.UnwrapConnection<EFTracingConnection>(); }
    }

    public event EventHandler<CommandExecutionEventArgs> CommandExecuting
    {
        add { this.TracingConnection.CommandExecuting += value; }
        remove { this.TracingConnection.CommandExecuting -= value; }
    }

    public event EventHandler<CommandExecutionEventArgs> CommandFinished
    {
        add { this.TracingConnection.CommandFinished += value; }
        remove { this.TracingConnection.CommandFinished -= value; }
    }

    public event EventHandler<CommandExecutionEventArgs> CommandFailed
    {
        add { this.TracingConnection.CommandFailed += value; }
        remove { this.TracingConnection.CommandFailed -= value; }
    }

    private void AppendToLog(object sender, CommandExecutionEventArgs e)
    {
        if (this.logOutput != null)
        {
            this.logOutput.WriteLine(e.ToTraceString().TrimEnd());
            this.logOutput.WriteLine();
        }
    }

    public TextWriter Log
    {
        get { return this.logOutput; }
        set
        {
            if ((this.logOutput != null) != (value != null))
            {
                if (value == null)
                {
                    CommandExecuting -= AppendToLog;
                }
                else
                {
                    CommandExecuting += AppendToLog;
                }
            }

            this.logOutput = value;
        }
    }


    #endregion

    #region Caching Extensions

    private EFCachingConnection CachingConnection
    {
        get { return this.UnwrapConnection<EFCachingConnection>(); }
    }

    public ICache Cache
    {
        get { return CachingConnection.Cache; }
        set { CachingConnection.Cache = value; }
    }

    public CachingPolicy CachingPolicy
    {
        get { return CachingConnection.CachingPolicy; }
        set { CachingConnection.CachingPolicy = value; }
    }

    #endregion
}
// Workaround ends here

// DbContext
<#=Accessibility.ForType(container)#> partial class <#=Code.Escape(container)#> : DbContext
{
	// Modification: Constructor with ObjectContext init
    public <#=Code.Escape(container)#>()
        : base(new <#=Code.Escape(container)#>Context(), true)
    {
<#
        WriteLazyLoadingEnabled(container);
#>
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        throw new UnintentionalCodeFirstException();
    }

<#
    foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
    {
#>
    <#=Accessibility.ForReadOnlyProperty(entitySet)#> DbSet<<#=Code.Escape(entitySet.ElementType)#>> <#=Code.Escape(entitySet)#> { get; set; }
<#
    }

    foreach (var edmFunction in container.FunctionImports)
    {
        WriteFunctionImport(edmFunction, false);
    }
#>
}
<#

if (!String.IsNullOrEmpty(ObjectNamespace))
{
    PopIndent();
#>
}
<#
}
#>
<#+
string ModelNamespace { get; set; }
string ObjectNamespace { get; set; }
CodeGenerationTools Code { get; set; }
MetadataTools EFTools { get; set; }

string GetResourceString(string resourceName)
{
	if(_resourceManager == null)
	{
		_resourceManager = new System.Resources.ResourceManager("System.Data.Entity.Design", typeof(System.Data.Entity.Design.MetadataItemCollectionFactory).Assembly);
	}
	
    return _resourceManager.GetString(resourceName, null);
}
System.Resources.ResourceManager _resourceManager;

void WriteLazyLoadingEnabled(EntityContainer container)
{
   string lazyLoadingAttributeValue = null;
   var lazyLoadingAttributeName = MetadataConstants.EDM_ANNOTATION_09_02 + ":LazyLoadingEnabled";
   if(MetadataTools.TryGetStringMetadataPropertySetting(container, lazyLoadingAttributeName, out lazyLoadingAttributeValue))
   {
       bool isLazyLoading;
       if(bool.TryParse(lazyLoadingAttributeValue, out isLazyLoading) && !isLazyLoading)
       {
#>
        this.Configuration.LazyLoadingEnabled = false;
<#+
       }
   }
}

void WriteFunctionImport(EdmFunction edmFunction, bool includeMergeOption)
{
    var parameters = FunctionImportParameter.Create(edmFunction.Parameters, Code, EFTools);
    var paramList = String.Join(", ", parameters.Select(p => p.FunctionParameterType + " " + p.FunctionParameterName).ToArray());
    var returnType = edmFunction.ReturnParameter == null ? null : EFTools.GetElementType(edmFunction.ReturnParameter.TypeUsage);
    var processedReturn = returnType == null ? "int" : "ObjectResult<" + MultiSchemaEscape(returnType) + ">";

    if (includeMergeOption)
    {
        paramList = Code.StringAfter(paramList, ", ") + "MergeOption mergeOption";
    }
#>

    <#=AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction))#> <#=processedReturn#> <#=Code.Escape(edmFunction)#>(<#=paramList#>)
    {
<#+
        if(returnType != null && (returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType ||
                                  returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType))
        {
#>
        ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace.LoadFromAssembly(typeof(<#=MultiSchemaEscape(returnType)#>).Assembly);

<#+
        }

        foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable))
        {
            var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null";
            var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")";
            var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + parameter.RawClrTypeName + "))";
#>
        var <#=parameter.LocalVariableName#> = <#=isNotNull#> ?
            <#=notNullInit#> :
            <#=nullInit#>;

<#+
        }

        var genericArg = returnType == null ? "" : "<" + MultiSchemaEscape(returnType) + ">";
        var callParams = Code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()));

        if (includeMergeOption)
        {
            callParams = ", mergeOption" + callParams;
        }
#>
        return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<#=genericArg#>("<#=edmFunction.Name#>"<#=callParams#>);
    }
<#+
    if(!includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
    {
        WriteFunctionImport(edmFunction, true);
    }
}

string AccessibilityAndVirtual(string accessibility)
{
    return accessibility + (accessibility != "private" ? " virtual" : "");
}

string MultiSchemaEscape(TypeUsage usage)
{
    var type = usage.EdmType as StructuralType;
    return type != null && type.NamespaceName != ModelNamespace ?
        Code.CreateFullName(Code.EscapeNamespace(type.NamespaceName), Code.Escape(type)) :
        Code.Escape(usage);
}

#>

Für den CodeFirst-Ansatz wird das Ganze schon ein wenig wilder, da das Model zur Laufzeit erstellt wird und der Metadaten-ConnectionString in dieser Form auch nicht existiert. Aber auch hier habe ich mit ein wenig „Basteln“ eine lauffähige Variante hinbekommen. Da dieser Workaround ziemlich wild ist, habe ich diesen als T4-Vorlage realisiert. Folgende Vorteile ergeben sich daraus: Der Workaround ist dokumentiert und lässt sich leichter entfernen, wenn in einer zukünftigen Version der 2nd-Level Cache endlich mal realisiert wird.

Die T4-Vorlage für den wildesten aller Workarounds ist hier:


<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ assembly name="Microsoft.CSharp" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="$(ProjectDir)$(OutDir)EntityFramework.dll" #>
<#@ assembly name="$(TargetPath)" #> // in this case dll with codefirst context
<#@ import namespace="System.Data.EntityClient" #>
<#@ import namespace="System.Data.Entity.Infrastructure" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml" #>
<#@ include file="TemplateFileManager.CS.ttinclude" #>
<#@ include file="VsAutomationHelper.CS.ttinclude" #>
<#@ include file="ConfigurationAccessor.CS.ttinclude" #>

<# 
// ToDo: Initalize an instance of the DbContext (T4 uses connection by convention)
var contextInstance = new DbContextCodeFirst2ndLevelCache.TestContext();

// Change database connection 
// Workaround for accessing the app.config from t4 [random appdomain :-(]
ConfigurationAccessor config = new ConfigurationAccessor((IServiceProvider)this.Host);
// set the connectionstring
string cn = config.ConnectionStrings.Cast<ConnectionStringSettings>()
	.Where(c=>c.Name == contextInstance.GetType().Name).Single().ConnectionString;

contextInstance.Database.Connection.ConnectionString = cn;


string contextName = contextInstance.GetType().Name;
string contextNamespace = contextInstance.GetType().Namespace;
string contextSuffix = "OC";
var fileManager = TemplateFileManager.Create(this);

// File properties for C# output files
var compileProp = new FileProperties();
compileProp.BuildAction = BuildAction.Compile;

// File properties for EDMX output
var edmxProp = new FileProperties();
edmxProp.BuildAction = BuildAction.EntityDeploy;
edmxProp.CustomTool = "EntityModelCodeGenerator";

// Create Edmx file
fileManager.StartNewFile(String.Format("{0}.edmx", contextName)
						, fileProperties:edmxProp);

this.Write(this.GetEdmxFileFromDbContext(contextInstance));

// Create the ObjectContext with ef provider wrapper
fileManager.StartNewFile(String.Format("{0}{1}.cs", contextName, contextSuffix)
						, fileProperties:compileProp);

CreateObjectContextFromDbContext(contextName, contextNamespace, contextSuffix);


fileManager.Process(true);

#>

<#+ 

void CreateObjectContextFromDbContext(string contextName, string contextNamespace, string contextSuffix)
{
#>
namespace <#= contextNamespace #>
{
	using System;
	using System.Collections.Generic;
	using System.Configuration;
	using System.Linq;
	using System.Text;
	using System.Data;
	using System.Data.Entity;
	using System.Data.EntityClient;
	using System.Data.Objects;
	using EFProviderWrapperToolkit;
    using EFTracingProvider;
    using EFCachingProvider;
    using EFCachingProvider.Caching;
	
	public class <#= contextName #><#= contextSuffix #> : ObjectContext
	{
	
	    public <#= contextName #><#= contextSuffix #>()
        	: base (EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
          		TransformToMetaDataConnectionString("<#= contextName #>"),
          			/*"EFTracingProvider",*/
          			"EFCachingProvider"
          		), "<#= contextName #>")
	    {
	    }
		
		public static string TransformToMetaDataConnectionString(string contextName)
		{
		  string folderres = "Workaround."; //Subfolder of workaround with edmx
          var con = System.Configuration.ConfigurationManager.ConnectionStrings
            .Cast<ConnectionStringSettings>()
            .Where(c => c.Name == contextName)
            .Single();

          if (con == null)
          {
            throw new ArgumentException("No connection with name '{0}' found.", contextName);
          }

          var conn = new EntityConnectionStringBuilder();
          conn.ProviderConnectionString = con.ConnectionString;
          conn.Provider = con.ProviderName;
          conn.Metadata = String.Format("res://*/{1}{0}.csdl|res://*/{1}{0}.ssdl|res://*/{1}{0}.msl", contextName, folderres);
          return conn.ConnectionString;
		}
	}
}
<#+ 
}

/// <summary>
/// The code first context must already exists and compiled. Be sure that 
/// the connection string exists in the app.config and the database is created. 
/// Required for edmx file creation
/// </summary
string GetEdmxFileFromDbContext(System.Data.Entity.DbContext ctx)
{	
	string xml = String.Empty;
	
    var sw = new UTF8StringWriter();
    using (var writer = new XmlTextWriter(sw))
    {
      EdmxWriter.WriteEdmx(ctx, writer);
    }
	
    xml = sw.ToString();
    

	return xml;
}

public class UTF8StringWriter : StringWriter
{
  public override Encoding Encoding
  {
	get { return Encoding.UTF8; }
  }
}

#>

Die Beispiel-Solution kann hier heruntergeladen werden. Für die DbFirst bzw. ModelFirst-Variante muss zuerst eine Datenbank mit den Namen DbContextTest angelegt werden, dass SQL-Skript befindet sich im Verzeichnis SQL-Skripts. Der EFCachingProvider in der Solution ist modifiziert, die Anpassungen sind auch in diesem Blog beschrieben.

Zurück

24.07.2011
15:03

T4 und der Zugriff auf die App.config

Zur Zeit sitze ich mal wieder tief in der T4-Materie. Dabei war mal wieder die zufällige Erstellung von AppDomains eine kleine Bremse. Das ist eine ganz spezielle Eigenart, die zum Beispiel auch dafür sorgt, dass auf die App.config der Solution nicht direkt zugegriffen werden kann.

Im Blog von Sky Sander fand ich einen schönen Workaround, damit der Zugriff doch wieder ermöglicht werden kann.

Bei Gelegenheit muss ich mal überprüfen, ob sich die zufällige Erstellung der T4-AppDomains irgendwie in den Griff bekommen lässt.

Nachfolgend der Workaround, der bei mir in Form einer Include-Datei mit Namen ConfigurationAccessor.CS.ttinclude verwendet wird:


<#@ assembly name="System.Configuration" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Configuration" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#+
/// <summary>
/// Provides strongly typed access to the hosting EnvDTE.Project and app.config/web.config 
/// configuration file, if present.
/// 
/// Typical usage from T4 template:
/// <code>ConfigurationAccessor config = new ConfigurationAccessor((IServiceProvider)this.Host);</code>
/// </summary>
/// <author>Sky Sanders [sky.sanders@gmail.com, http://skysanders.net/subtext]</author>
/// <date>01-23-10</date>
/// <copyright>The contents of this file are a Public Domain Dedication.</copyright>
public class ConfigurationAccessor
{
   /// <summary>
   /// Typical usage from T4 template:
   /// <code>ConfigurationAccessor config = new ConfigurationAccessor((IServiceProvider)this.Host);</code>
   /// </summary>
   public ConfigurationAccessor(IServiceProvider host)
   {
      // Get the instance of Visual Studio that is hosting the calling file
      EnvDTE.DTE env = (EnvDTE.DTE)host.GetService(typeof(EnvDTE.DTE));
      // Gets an array of currently selected projects. Since you are either in this file saving it or
      // right-clicking the item in solution explorer to invoke the context menu it stands to reason
      // that there is 1 ActiveSolutionProject and that it is the parent of this file....
      _project = (EnvDTE.Project)((Array)env.ActiveSolutionProjects).GetValue(0);
      string configurationFilename=null;  
      // examine each project item's filename looking for app.config or web.config
      foreach (EnvDTE.ProjectItem item in _project.ProjectItems)
      {
            if (Regex.IsMatch(item.Name,"(app|web).config",RegexOptions.IgnoreCase))
            {
                // TODO: try this with linked files. is the filename pointing to the source?
                configurationFilename=item.get_FileNames(0);
                break;
            }
      }

      if(!string.IsNullOrEmpty(configurationFilename))
      {
           // found it, map it and expose salient members as properties
           ExeConfigurationFileMap configFile = null;
           configFile = new ExeConfigurationFileMap();
           configFile.ExeConfigFilename=configurationFilename;

           _configuration = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(configFile, ConfigurationUserLevel.None);
      }
  }

  private EnvDTE.Project _project;
  private System.Configuration.Configuration _configuration;

  /// <summary>
  /// Provides access to the host project.
  /// </summary>
  /// <remarks>see http://msdn.microsoft.com/en-us/library/envdte.project.aspx</remarks>
  public EnvDTE.Project Project
  {
      get { return _project; }
  }

  /// <summary>
  /// Convenience getter for Project.Properties.
  /// Examples:
  /// <code>string thisAssemblyName = config.Properties.Item("AssemblyName").Value.ToString();</code>

  /// <code>string thisAssemblyName = config.Properties.Item("AssemblyName").Value.ToString();</code>
  /// </summary>
  /// <remarks>see http://msdn.microsoft.com/en-us/library/envdte.project_properties.aspx</remarks>
  public EnvDTE.Properties Properties 
  {
       get { return _project.Properties;}
  }

  /// <summary>
  /// Provides access to the application/web configuration file.
  /// </summary>
  /// <remarks>see http://msdn.microsoft.com/en-us/library/system.configuration.configuration.aspx</remarks>
  public System.Configuration.Configuration Configuration
  {
       get { return _configuration; }
  }   

  /// <summary>
  /// Provides access to the appSettings section of the configuration file.
  /// Behavior differs from typical AppSettings usage in that the indexed
  /// item's .Value must be explicitly addressed.
  /// <code>string setting = config.AppSettings["MyAppSetting"].Value;</code>

  /// </summary>
  /// <remarks>see http://msdn.microsoft.com/en-us/library/system.configuration.configuration.appsettings.aspx</remarks>
  public  KeyValueConfigurationCollection AppSettings
  {
       get { return _configuration.AppSettings.Settings;}
  }

  /// <summary>
  /// Provides access to the connectionStrings section of the configuration file.
  /// Behavior is as expected; items are accessed by string key or integer index.
  /// <code>string northwindProvider = config.ConnectionStrings["northwind"].ProviderName;</code>
  /// </summary>
  /// <remarks>see http://msdn.microsoft.com/en-us/library/system.configuration.configuration.connectionstrings.aspx</remarks>
  public  ConnectionStringSettingsCollection ConnectionStrings
  {
        get { return _configuration.ConnectionStrings.ConnectionStrings;}
  }
}#>

Zurück

Entity Framework 4 - EfCachingProvider und die System.NotSupportedException: Command tree type System.Data.Common.CommandTrees. DbFunctionCommandTree is not supported bei Ausführung von gespeicherten Prozeduren

Ich habe die neue Version des EFProviderToolkits heruntergeladen. Bei der Ausführung von Prozeduren hatte auch die frühere Version Probleme. Wie ich es dennoch in den Griff bekommen habe, ist hier beschrieben.

Nun stand ich wieder an einer ähnlichen Stelle an. Der Code wurde überarbeitet, theoretisch lassen sich auch die gespeicherten Prozeduren ausführen. Theoretisch, denn der Caching Provider quittiert mir die Ausführung mit der Fehlermeldung:

System.NotSupportedException: Command tree type System.Data.Common.CommandTrees.DbFunctionCommandTree is not supported.

Bei der neuen Version tritt der Fehler jedoch in der Datei EFCommandDefinition.cs auf, also machte ich mich dort auf die Suche. Fündig wurde ich in der Methode GetAffectedEntitySets.

Dort werden die Tree-Kommandos geparst und der Visitor angeheftet. Die Methode DbFunctionCommandTree fehlt jedoch in dieser Methode und dadurch wird eine Ausnahme ausgelöst, die in Zeile 170 steht.

Also habe ich in der Methode diesen Part hinzugefügt und konnte so das Problem in den Griff bekommen. Da ich in diesem Projekt nur gespeicherte Prozeduren ohne Rückgabewert aufrufe, bleibt die Basisfunktionalität des Caching Providers erhalten.

Die angepasste Methode im Projekt EFCachingProivder sieht wie folgt aus:


        private void GetAffectedEntitySets(DbCommandTree commandTree)
        {
            FindAffectedEntitySetsVisitor visitor = new FindAffectedEntitySetsVisitor(this.affectedEntitySets, this.functionsUsed);
            DbQueryCommandTree queryTree = commandTree as DbQueryCommandTree;
            if (queryTree != null)
            {
                queryTree.Query.Accept(visitor);
                return;
            }

            DbUpdateCommandTree updateTree = commandTree as DbUpdateCommandTree;
            if (updateTree != null)
            {
                this.IsModification = true;
                updateTree.Target.Expression.Accept(visitor);
                updateTree.Predicate.Accept(visitor);
                if (updateTree.Returning != null)
                {
                    updateTree.Returning.Accept(visitor);
                }

                return;
            }

            DbInsertCommandTree insertTree = commandTree as DbInsertCommandTree;
            if (insertTree != null)
            {
                this.IsModification = true;
                insertTree.Target.Expression.Accept(visitor);
                if (insertTree.Returning != null)
                {
                    insertTree.Returning.Accept(visitor);
                }

                return;
            }

            DbDeleteCommandTree deleteTree = commandTree as DbDeleteCommandTree;
            if (deleteTree != null)
            {
                this.IsModification = true;
                deleteTree.Target.Expression.Accept(visitor);
                if (deleteTree.Predicate != null)
                {
                    deleteTree.Predicate.Accept(visitor);
                }

                return;
            }

            // Modification for store procedures support without caching
            DbFunctionCommandTree funcTree = commandTree as DbFunctionCommandTree;
            if (funcTree != null)
            {
              return;
            }

            throw new NotSupportedException("Command tree type " + commandTree.GetType() + " is not supported.");
        }

Second Level Cache ist in manchen Situationen schon sehr hilfreich und die Ecken und Kanten in diesem Toolkit lassen sich immer noch anpassen. ;-)

Zurück

Entity Framework 4 - Sichten ohne Primarykey im Entity Designer verwenden

Sitze gerade an einem kleinen Projekt, dass eine bestehende Datenbank verwendet. Bei diesen soll eine View eingebunden werden, bei der die Primarykey-Spalte nicht erkannt wird.

Legacy-Datenbanken sind ja bekanntlich ein heisses Eisen und Änderungen können sich da manchmal grausam rächen. Einen Workaround mit IsNull in der Sicht sorgt jedoch dafür, dass Spalten als Primarykey im Entity Designer erkannt und dargestellt werden.

Sieht in etwa so aus:


CREATE VIEW [dbo].[vwName]
  AS SELECT 
	 ISNULL(IdColumn, '-1') AS MyPrimaryKey
	,Column2
	,Column3
	,...
    FROM TABLE

Eine gute Beschreibung gibts auf StackOverflow, wo sonst ;-)

Zurück

27.01.2011
22:11

Lesson learnt: Oracle mag ich nicht

Wer kennt das Sprichwort nicht: Lächle und sei froh, es könnte schlimmer kommen, ich lächelte und war froh und es kam schlimmer.

Vor allem ist es immer wieder interessant, dass man Dinge erleben kann, die sich wie eine unglaubliche Geschichte anhören. In einem der letzten Projekte ging es mir so.

Das Hauptproblem in einem Projekt war, dass ein DWH, dass auf einer eingerichteten Oracle-Schreibmaschine, die nicht den Anspruch einer soliden Grundlage erfüllte, problemlos lief. Auf dem Zielsystem unterlag die Performance jedoch starken Schwankungen. Einen Hinweis zur Definition Schreibmaschine: So nenne ich Rechner, die eigentlich nur für Office vorgesehen sind und unter dem Windows-Leistungsindex von 4 liegen.

Nach dem ersten Review kam u. a. heraus, dass die Umgebung virtualisiert war und es mehrere Zonen gab. Was für mich die Performanceschwankungen erklärte, stellte aber für den Performance-Spezialist (Guru) nicht das Problem dar und er präsentierte auch ein paar angepasste SQL-Skripts. Um dies auch rechtfertigen zu können, wurden kurzerhand die spezifizierten Hardwareanforderungen auf die tatsächliche Umgebung angepasst. Das ist mal agiles Projektmanagement aus einem anderen Blickwinkel. Für mich kam dadurch eine neue Herausforderung hinzu: Wie mache ich aus einer Schreibmaschine einen Taschenrechner? Der RAM ist ja schnell begrenzt, aber die CPU-Leistung? So bin ich auch mit cpulimit in Berührung gekommen.

Für mich war das eine sehr wichtige Erfahrung, denn so konnte ich auch lernen, dass Gutes SQL in Oracle-Umgebungen gleichzusetzen ist mit produktspezifisch und Tricky. Ein weiterer Vorteil, die Empfehlungen aus den Review habe ich in einer Testumgebung auf einem SQL-Server testen können.

Nach diesen ersten Tests habe ich doch mal im Duden zur Bedeutung von Oracle nachschlagen müssen und da steht Folgendes:

Ora|kel das; -s, - <lat.; »Sprechstätte«>:

a) Stätte (bes. im Griechenland der Antike), wo Priester[innen], Seher[innen] o. Ä. Weissagungen verkündeten oder [rätselhafte, mehrdeutige] Aussagen in Bezug auf gebotene Handlungen, rechtliche Entscheidungen o. Ä. machten;

b) durch das Orakel (a) erhaltene Weissagung, [rätselhafte, mehrdeutige] Aussage Duden - Das Fremdwörterbuch, 9. Aufl. Mannheim 2007 [CD-ROM]

Rätselhafte, mehrdeutige Aussage da habe ich doch ein Déjà-vu. Denn im ersten Review wurde bspw. ein Punkt kritisiert, welcher im zweiten wieder empfohlen wurde. Auch gab es Kritikpunkte an ein paar Einstellungen, die wir auf Empfehlung des Oracle-DBA implementierten. Die Literatur, die ich mir zum Thema organisierte, war für den Guru natürlich nicht die beste und er musste den Autor Donald K. Burleson eines Werkes auch kritisieren. Das hat System, denn in Oracle-Umgebungen macht man gerne den Developer zum Sündenbock.

Ganz ehrlich besser hätte Oracle seinen Namen gar nicht treffen können, bildlich stelle ich mir das so vor:.

Abbildung 1 Oracle-Consulting, demnächst bei Mike Shiva?
Abbildung 1

Auch wenn Oracle einen grösseren Funktionsumfang bietet, so sind damit Sachen möglich, die man besser nicht machen sollte, da sie zu Performanceeinbussen führen.

Was sich aus den Tests auf dem SQL Server ableiten lässt, ist Folgendes:

  • Ein Oracle-Developer der SQL für den SQL-Server schreiben soll, wird keine Probleme bekommen, da dieser sehr tolerant und robust ist.
  • Ein SQL-Server-Developer sollte vorher die Tipps und Tricks-Kurse der Gurus besuchen. Da diese auch Geld verdienen wollen, werden sicherlich nicht alle Tricks im ersten Kurs verraten. Mit dieser Salami-Taktik lässt sich gut verdienen und mit ein bisschen Geschick nimmt der Guru den eigentlichen Kunden auch gleich mit. Um dies zu umgehen, sollten die Schulungen nicht bei „Wir machen alles Unternehmen“ gebucht werden, die einen noch die Unterstützung anbieten. ;-)
  • Während man in der Oracle-Umgebung mit dem Feintuning beschäftigt ist und womöglich noch auf das schlechte SQL der Developer schimpft, weil mal wieder der Append-Hint bei grossen Datenmengen fehlt, lassen sich mit dem SQL-Server schon die ersten Kundenanforderungen realisieren. ;-)
  • Der mit Oracle mitgelieferte SQL Developer ist ein schlechter Aprilscherz.
  • Dem Oracle-Developer und Guru muss man vorher vielleicht noch das Lizenzmodell erklären, sonst könnte es passieren, dass er viele Funktionalitäten der Tools nicht nutzt, in der Annahme diese seien nicht lizenziert.

So etwas passiert eben, wenn die Erwartungen zu hoch sind und deswegen habe ich jetzt um so mehr Freude daran, einen Oracle-Server in die ewigen Jagdgründe migrieren zu dürfen, so als Wiedergutmachung.

Zurück

Translate this page

Kategorien