Creating Strong-typed Metadata Classes

This post is about an aspect of the CodeFirstMetadata library. You can find out more about this library and where to get it here and here.

You can find out more about strong-typed metadata classes in this post.

You can find out about code-first (generalized, not Entity Framework) here.

This post talks about the two existing examples to explain how strong typing works in real code and to show how instances of these examples are created.

At present, in order to create a set of strong typed classes to solve a new problem you need to create a fairly messy set of classes. Feel free to ping me if you think you have a good problem or you want to extend the existing problems and I’ll help guide you. In the long run I want to automate that process, so I probably won’t document it until then.

Because part will be automated/generated, it comes in two parts. I’m currently combining them with inheritance, rather than partial classes, to make this code approachable for non-.NET programmers, and because virtual/override are simpler concepts.

These classes all derive from a common base class – CodeFirstMetadata<T> – to provide common features like naming. Below this are code element specific classes like CodeFirstMetadataClass<T> that help with the conversion. I may later replace this with a shallow hierarchy and interfaces, so don’t get dependent on this implementation.

For a semantic log, the class, the predictable part I’ll later generate looks like:

using System.Collections.Generic;
using CodeFirst.Common;

namespace CodeFirstMetadataTest.SemanticLog
{
// TODO: Generate this base class based on expected attributes
public abstract class CodeFirstSemanticLogBase : CodeFirstMetadataClass<CodeFirstSemanticLog>
{
public CodeFirstSemanticLogBase()
{
this.Events = new List<CodeFirstLogEvent>();
}

public virtual string UniqueName { get; set; }
public virtual string LocalizationResources { get; set; }

public IEnumerable<CodeFirstLogEvent> Events { get; private set; }

}

}



 



The manual changes I’ve made, which are by far the most complex I’ve needed so far are:



using System.Linq;
// TODO: Attempt to remove this line after generating base class

namespace CodeFirstMetadataTest.SemanticLog
{

public class CodeFirstSemanticLog : CodeFirstSemanticLogBase
{

private string _uniqueName;
public override string UniqueName
{
get
{
if (string.IsNullOrWhiteSpace(_uniqueName))
{ return Namespace.Replace(".", "-") + "-" + ClassName; }
return _uniqueName;
}
set
{ _uniqueName = value; }
}


public bool IncludesInterface
{ get { return this.ImplementedInterfaces.Count() > 0; } }

public bool IsLocalized
{ get { return !string.IsNullOrWhiteSpace(this.LocalizationResources); } }

public override bool ValidateAndUpdateCore()
{
var isOk = base.ValidateAndUpdateCore();
if (isOk)
{ return CheckAndUpdateEventIds(); }
return false;
}

/// <summary>
/// This is a weird algorithm because it numbers implicit events from
/// the top, regardless of whether other events have event IDs. But
/// while I wouldn't have chosen this, I think it's important to match
/// EventSource implicit behavior exactly.
/// </summary>
private bool CheckAndUpdateEventIds()
{
var i = 0;
foreach (var evt in this.Events)
{
i++;
if (evt.EventId == 0) evt.EventId = i;
}
// PERF: The following is an O<n2> algorithm, probably a better way
var dupes = this.Events
.Where(x => this.Events
.Any(y => (y != x) && x.EventId == y.EventId));
return (dupes.Count() == 0);
}
}
}



 



EventSource, and presumably any other log system, requires a unique name, and I want to help you create that. Also, whether there is an interface and whether the class is localized have a significant impact on the template, so I simplify access to this information.



Loading strong-typed metadata is an opportunity for validation of the model. I use this to provide unique numeric ids to each of the log events, which are needed by EventSource and potentially other log mechanisms.



Mapping Between Code-first and Strong-typed Metadata



A bunch of ugly Roslyn and reflection code maps between code-first and strong typed metadata. This is the code that drove creation of the RoslynDom library – directly hitting the .NET Compiler Platform/Roslyn API within this code was monstrous.



var root = RDomFactory.GetRootFromFile(fileName);
var cfNamespace = root.Namespaces.First();
var returnType = typeof(T);
var mapping = TargetMapping.DeriveMapping("root", "root", returnType.GetTypeInfo()) as TargetNamespaceMapping;
var mapper = new CodeFirstMapper();
var newObj = mapper.Map(mapping, cfNamespace);



  • cfNamespace is the RolsynDom root
  • T is the type to return – the strong-typed metadata
  • mapping derived data about the mapping of the target– just create it as shown
  • mapper is the class that does the hard work
  • newObj is the new strong-typed metadata object


In the end, you have an object that is the strong-typed metadata for the initial code.



OK, but how does that work?



For metaprogramming:



  • I create a minimal description is in a file with a .cfcs extension
  • I lie to Visual Studio and tell it that this is a C# file (Tools/Options/Text Editor/File Extensions) I get nice IntelliSense for most features (more work to be done later).
  • MSBuild doesn’t see it as a C# file, so the .cfcs files are ignored as source in compilation
  • Generation creates .g.cs files that are included in compilation


The intent is to have this automated as part of your normal development pipeline, through one or more mechanism – build, custom tools, VS extension/PowerShell. The pipeline part is not done yet, but you can grab the necessary pieces from the console application in the example.



Getting CodeFirstMetadata



You can get this project on GitHub. I’ll add this to NuGet when the samples are in a more accessible from your Visual Studio project.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>