In Autofac, how do I propagate Keys through Adapters - autofac

I'm using the adapter support in Autofac to convert multiple types to a desired type. I also want to preserve the keys/names/metadata attached to the adapter input types, so that they exist with the same values on the adapter output types - this is needed for using IIndex<,> to resolve instances by name.
I can't figure out how to propagate the keys/names/metadata through the adapter function, since the adapter function runs during component construction, and the metadata needs to be propagated when the container is built.
Here's an example xunit test, which fails:
/// <summary>
/// Unit test to figure out how to propagate keys through adapters.
/// </summary>
public sealed class AutofacAdapterTest
{
public class A
{
public A(string key)
{
Key = key;
}
public string Key { get; private set; }
}
public class B
{
public B(string name)
{
Name = name;
}
public string Name { get; private set; }
}
public class C : B
{
public C(string name)
: base(name)
{}
}
public class LookerUpper
{
private readonly IIndex<string, B> _bIndex;
public LookerUpper(IIndex<string, B> bIndex)
{
_bIndex = bIndex;
}
public B LookupByName(string name)
{
return _bIndex[name];
}
}
[Fact]
public void TestPropagateKeysThroughAdapters()
{
var builder = new ContainerBuilder();
// Register named types
builder.RegisterType<A>().Named<A>("A").WithParameter("key", "A");
builder.RegisterType<B>().Named<B>("B").WithParameter("name", "B");
builder.RegisterType<C>().Named<C>("C").Named<B>("C").WithParameter("name", "C");
// Adapter to convert an A to a B, since it's not a subclass
builder.RegisterAdapter<A, B>((c, a) => new B(a.Key));
// Register LookerUpper, which is the only top-level type that needs to be autowired
builder.RegisterType<LookerUpper>();
var container = builder.Build();
var lookerUpper = container.Resolve<LookerUpper>();
// Test expected results
Assert.Equal("A", lookerUpper.LookupByName("A").Name);
Assert.IsType<B>(lookerUpper.LookupByName("A")); // A should have been adapted to a B
Assert.Equal("B", lookerUpper.LookupByName("B").Name);
Assert.IsType<B>(lookerUpper.LookupByName("B"));
Assert.Equal("C", lookerUpper.LookupByName("C").Name);
Assert.IsType<C>(lookerUpper.LookupByName("C"));
Assert.Throws<ComponentNotRegisteredException>(() => lookerUpper.LookupByName("D"));
}
}
The statement lookerUpper.LookupByName("A") fails with a ComponentNotRegisteredException, because the name value "A" is not propagated through the adapter function (which adapts A -> B ). If the first two lines of Asserts are commented out, the rest of the test works as expected.

I found a workable solution to this problem by using Autofac Metadata instead of Autofac keys or names. For the call to RegisterAdapter<TFrom, TTo>(Func<TFrom,TTo>), metadata is propagated from the IComponentRegistration for TFrom to the IComponentRegistration for TTo; however the keys/names are not propagated. The omission of keys may be a bug or by design, I'll file a bug with autofac to figure out which is the case and follow up.
The unfortunate part about using metadata is I can't use an IIndex<string, B> constructor parameter, so I had to use an IEnumerable<Meta<Lazy<B>>> parameter and create my own dictionary of string -> Lazy<B> to provide similiar functionality to IIndex. Here's the code that works:
/// <summary>
/// Unit test to figure out how to propagate keys through adapters.
/// </summary>
public sealed class AutofacAdapterTest
{
internal const string LookupKey = "lookup";
public class A
{
public A(string key)
{
Key = key;
}
public string Key { get; private set; }
}
public class B
{
public B(string name)
{
Name = name;
}
public string Name { get; private set; }
}
public class C : B
{
public C(string name)
: base(name)
{}
}
public class LookerUpper
{
private readonly IDictionary<string, Lazy<B>> _bLookup;
public LookerUpper(IEnumerable<Meta<Lazy<B>>> bMetas)
{
_bLookup = bMetas.ToDictionary(meta => meta.Metadata[LookupKey].ToString(), meta => meta.Value);
}
public B LookupByName(string name)
{
return _bLookup[name].Value;
}
}
[Fact]
public void TestPropagateKeysThroughAdapters()
{
var builder = new ContainerBuilder();
// Register types that will be looked up; attach metadata for the lookup key
builder.Register((c) => new A("A")).WithMetadata(LookupKey, "A");
builder.Register((c) => new B("B")).WithMetadata(LookupKey, "B");
builder.Register((c) => new C("C")).AsSelf().As<B>().WithMetadata(LookupKey, "C");
// Adapter to convert an A to a B, since it's not a subclass
builder.RegisterAdapter<A, B>((c, a) => new B(a.Key));
// Register LookerUpper, which is the only top-level type that needs to be autowired
builder.RegisterType<LookerUpper>();
var container = builder.Build();
var lookerUpper = container.Resolve<LookerUpper>();
// Test expected results
Assert.Equal("A", lookerUpper.LookupByName("A").Name);
Assert.IsType<B>(lookerUpper.LookupByName("A")); // A should have been adapted to a B
Assert.Equal("B", lookerUpper.LookupByName("B").Name);
Assert.IsType<B>(lookerUpper.LookupByName("B"));
Assert.Equal("C", lookerUpper.LookupByName("C").Name);
Assert.IsType<C>(lookerUpper.LookupByName("C"));
Assert.Throws<KeyNotFoundException>(() => lookerUpper.LookupByName("D"));
}
}
It should also be possible to create an IRegistrationSource and some extension methods that extend what is done in RegisterAdapter<TFrom, TTo>, such that the keys in TFrom are propagated to TTo - that would be an ideal solution, but potentially more work to maintain, so I'll probably stick with this.

It was fixed in Autofac version 3.5.1.
Link to the bug
Link to the fix

Related

Is it possible to access a shared TPH column in EF Core without using intermediate classes?

When using shared columns in an EF Core TPH setup, is it possible to access the shared column during projection?
class Program
{
public static readonly ILoggerFactory MyLoggerFactory
= LoggerFactory.Create(builder => {
builder.AddConsole();
});
static async Task Main(string[] args)
{
using (var context = new ClientContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var actions = await context.Actions
.Select(a => new
{
Id = a.Id,
// this works - but really messy and complex in real world code
Message = (a as ActionA).Message ?? (a as ActionB).Message,
// this throws "Either the query source is not an entity type, or the specified property does not exist on the entity type."
// is there any other way to access the shared column Message?
// Message = EF.Property<string>(a, "Message"),
})
.ToListAsync();
actions.ForEach(a => Console.WriteLine(a.Id + a.Message));
}
}
public class ActionBase
{
public int Id { get; set; }
// ... other shared properties
}
public class ActionA : ActionBase
{
// shared with B
[Required]
[Column("Message")]
public string Message { get; set; }
// ... other specific properties
}
public class ActionB : ActionBase
{
// shared with A
[Required]
[Column("Message")]
public string Message { get; set; }
// ... other specific properties
}
public class ActionC : ActionBase
{
public string SomethingElse { get; set; }
// ... other specific properties
}
class ClientContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// TO USE SQL
//optionsBuilder
// .UseLoggerFactory(MyLoggerFactory)
// .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=TPHSharedColumn;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30")
// .EnableSensitiveDataLogging(false);
// TO USE INMEMORY
optionsBuilder
.UseLoggerFactory(MyLoggerFactory)
.UseInMemoryDatabase(Guid.NewGuid().ToString());
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ActionA>().HasData(new ActionA()
{
Id = 1,
Message = "A"
});
builder.Entity<ActionB>().HasData(new ActionB()
{
Id = 2,
Message = "B"
});
builder.Entity<ActionC>().HasData(new ActionC()
{
Id = 3,
SomethingElse = "C"
});
}
public DbSet<ActionBase> Actions { get; set; }
}
}
In this simple example, it would of course be possible to move Message to the base class - but that would make it possible to accidentally add an ActionC with a Message since I would need to remove the Required attribute.
I also know I could add a ActionWithRequiredMessage intermediate class to inherit ActionA and ActionB with, but again - in the much more complex real world example this is not feasible since there are also other shared columns and C# does not allow inheriting from multiple classes - and EF Core does not seem to like to use interfaces for this.
I simply would like to find a way to directly access the shared column - and use it in a projection.
Anyone know if this is possible?
I can't find it documented, but in EF Core 5.x you can access the shared column using any of the derived entities having a property mapped to it, e.g. all these work
Message = (a as ActionA).Message,
Message = (a as ActionB).Message,
Message = ((ActionA)a).Message,
Message = ((ActionB)a).Message,

Using custom collection types as backing fields

Is it possible to have a custom collection type as a backing field in Entity Framework Core 3?
I have an entity A that has a OwnsMany relationship with an entity B. Since there are many business rules that needed to be checked before adding B to A, I have to rely on methods like AddB, GetB and RemoveB in order to enforce this rules.
public class Id { ... }
public class B{
private A _a;
internal B(A a){
_a = a;
}
private B() {
_a = default!;
}
}
public class A {
private List<B> _bs;
public IReadOnlyCollection<B> Bs => _bs.AsReadOnly();
public B AddB() {
// validate stuff
var b = new B(this);
_bs.Add(b);
return b;
}
public B GetB(Id id) {
//some linq to find B or throw if id not found
}
public B? FindB(Id id) {
return null if id not found
}
public void RemoveB(B b)
=> _bs.Remove(b);
}
Here the thing is that, as the business rules for managing B, A class is becoming more populated with methods like DoSomethingForB, most of them being linq queries or methods that perform calculations over the collection. And if I add another collection of class C then the complexity of A increases. So I would like to delegate linq queries and Get and Find methods to collection classes instead of relying on the generic List<T> method.
public class Id { ... }
public class B{
private A _a;
internal B(A a){
_a = a;
}
private B() {
_a = default!;
}
}
public class BCollection : IEnumerable<B> {
private List<B> _items;
public B GetB(Id id) {
//some linq to find B or throw if id not found
}
public B? FindB(Id id) {
return null if id not found
}
internal void Add(B b) => _items.Add(b);
// add some other methods like Remove etc.
}
public class A {
private BCollection _bs;
public BCollection Bs => _bs;
public A() {
_bs = new BCollection();
}
public B AddB() {
// validate stuff
var b = new B(this);
_bs.Add(b);
return b;
}
}
However, I am unsure whether this is a supported scenario on EF Core 3, specifically if EF Core 3 will know how to reconstruct my BCollection type, or if I have to follow some specific rules (perhaps implement an interface or do some special configuration in the DbContext configuration).
Thanks in advance.

Decoupling Entity Framework from my POCO classes

I'm dynamically creating my DbContext by iterating over any entities that inherit from EntityBase and adding them to my Context:
private void AddEntities(DbModelBuilder modelBuilder)
{
var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var entityTypes = assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(EntityBase)) && !x.IsAbstract);
foreach (var type in entityTypes)
{
dynamic entityConfiguration = entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
EntityBase entity = (EntityBase)Activator.CreateInstance(type);
//Add any specific mappings that this class has defined
entity.OnModelCreating(entityConfiguration);
}
}
}
That way, I can have many namespaces but just one generic repository in my base namespace that's used everywhere. Also, in apps that make use of multiple namespaces, the base repository will already be setup to use all the entities in all the loaded namespaces. My problem is, I don't want to make EntityFramework.dll a dependency of every namespace in the company. So I'm calling OnModelCreating and passing the EntityTypeConfiguration to the class so it can add any mappings. This works fine and here's how I can add a mapping to tell the model that my "Description" property comes from a column called "Descriptor":
class Widget... {
public override void OnModelCreating(dynamic entity)
{
System.Linq.Expressions.Expression<Func<Widget, string>> tmp =
x => x.Description;
entity.Property(tmp).HasColumnName("Descriptor");
}
The good thing is, my entity class has no reference to EF, this method is only called once, when the context is created and if we scrap EF and go to something else in the future, my classes won't have all sorts of attributes specific to EF in them.
The problem is, it's super ugly. How can I let the model know about column mappings and keys in a simpler way than creating these Expressions to get properties to map without hard coding references to EF all over my poco classes?
You could define your own Attributes and use these to control the configuration within OnModelCreating(). You should be able to gain (using reflection) all the details you need for column mapping in one linq query a second query for the creation of the key.
public class DatabaseNameAttribute : Attribute
{
private readonly string _name;
public DatabaseNameAttribute(string name)
{
_name = name;
}
public string Name
{
get
{
return _name;
}
}
}
public class KeySequenceAttribute : Attribute
{
private readonly int _sequence;
public KeySequenceAttribute(int sequence)
{
_sequence = sequence;
}
public int Sequence
{
get
{
return _sequence;
}
}
}
[DatabaseName("BlogEntry")]
public class Post
{
[DatabaseName("BlogId")]
[KeySequence(1)]
public int id { get; set; }
[DatabaseName("Description")]
public string text { get; set; }
}

Dynamic way to Generate EntityTypeConfiguration : The type 'TResult' must be a non-nullable value type

I was thinking to generate EntityTypeConfiguration dynamically from run time and i don't want any EF dependency in Models[That is why i avoid Data Annotation].
So I declare a custom attribute(or can be any configuration file later on)
[AttributeUsage(AttributeTargets.Property, AllowMultiple=true )]
public class PersistableMemberAttribute : Attribute
{
public bool Iskey;
public bool IsRequired;
public bool IsIgnored;
public bool IsMany;
public string HasForeignKey;
public bool PropertyIsRequired;
public bool PropertyIsOptional;
}
And here is one of my Models is look like:
public class Blog
{
[PersistableMember(Iskey=true)]
public Guid BlogId { get; set; }
[PersistableMember(PropertyIsRequired = true)]
public string Name { get; set; }
public string Url { get; set; }
[PersistableMember(IsIgnored=true)]
public int Rating { get; set; }
[PersistableMember(IsMany =true)]
public ICollection<Post> Posts { get; set; }
}
Now I am going to write a generic EntityTypeConfiguration , which will create the configuration dynamically on run time based on the attribute values :
public class GenericEntityConfiguration<T> : EntityTypeConfiguration<T> where T : class
{
public GenericEntityConfiguration()
{
var members = typeof(T).GetProperties();
if (null != members)
{
foreach (var property in members)
{
var attrb= property.GetCustomAttributes(typeof( PersistableMemberAttribute ),false).OfType<PersistableMemberAttribute>();
if (attrb != null && attrb.Count() > 0)
{
foreach (var memberAttributute in attrb)
{
if (memberAttributute.Iskey || memberAttributute.IsIgnored)
{
var entityMethod = this.GetType().GetMethod("Setkey");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
if (memberAttributute.IsRequired)
{
var entityMethod = this.GetType().GetMethod("SetRequired");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
if (memberAttributute.PropertyIsRequired || memberAttributute.PropertyIsOptional)
{
var entityMethod = this.GetType().GetMethod("SetPropertyConfiguration");
entityMethod.MakeGenericMethod(property.PropertyType)
.Invoke(this, new object[] { property, memberAttributute });
}
}
}
}
}
}
public void SetPropertyConfiguration<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute)
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.PropertyIsRequired)
{
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsRequired();
}
if (attribute.PropertyIsOptional)
{
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsOptional();
}
}
public void Setkey<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute)
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.Iskey)
{
this.HasKey<TResult>((Expression<Func<T,TResult>>)lambda);
}
if (attribute.IsIgnored)
{
this.Ignore<TResult>((Expression<Func<T, TResult>>)lambda);
}
}
public void SetRequired<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute) where TResult : class
{
var functorParam = Expression.Parameter(typeof(T));
var lambda = Expression.Lambda(
Expression.Property(functorParam, propertyInfo)
, functorParam);
if (attribute.IsRequired)
{
this.HasRequired<TResult>((Expression<Func<T, TResult>>)lambda);
}
}
}
But i got the compilation error of
Error 1 The type 'TResult' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Data.Entity.ModelConfiguration.Configuration.StructuralTypeConfiguration.Property(System.Linq.Expressions.Expression>)' D:\R&D\UpdateStorePOC\UpdateStorePOC\Data\GenericEntityConfiguration.cs 63 17 UpdateStorePOC
which for these two statements:
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsRequired();
this.Property<TResult>((Expression<Func<T, TResult>>)lambda).IsOptional();
that means that I need to put a constraint on my method to restrict it to a value type. In C#, this is done with the ‘struct’ keyword.
public void SetPropertyConfiguration<TResult>(PropertyInfo propertyInfo, PersistableMemberAttribute attribute) Where TResult : struct
But Its not the solution since my property type can be a class e.g string or int, bool double, etc . So it is not at all clear that I can send them into this method. Please help me to solve this issue whether there is any other way to do it.
I don't want any EF dependency in models.
With fluent mapping you're almost there and you won't come any closer. Your attributes, even though intended to be moved to a configuration file, don't make your model any more free of any EF footprint.1 Worse, they only add a second mapping layer (if you like) between your model and EF's mapping. I only see drawbacks:
You still have to maintain meta data for your model, probably not any less than regular fluent mapping and (probably) in awkward manually edited XML without compile-time checking.
You will keep expanding your code to cover cases that EF's mapping covers but yours doesn't yet.2 So it's a waste of energy: in the end you'll basically have rewritten EF's mapping methods.
You'll have to keep your fingers crossed when you want to upgrade EF.
With bugs/problems you're on your own: hard to get support from the community.
So my answer to your question help me to solve this issue would be: use fluent mapping out of the box. Keep it simple.
1 For example, you would still have to use the virtual modifier to enable proxies for lazy loading.
2 Like support for inheritance, unmapped foreign keys, max length, db data type, ... this could go on for a while.

MEF Metadata from the exported parts

I'm looking to use MEF for a plugin system for an application I'm building. Each component I want to have an identifier on (a GUID) which I want to be able to look up against. But this ID is also something that is useful when working with the exported part.
Is there a way that I can have a Metadata attribute which contains the ID as well as a property (or method) on the exported part, short of having developers fill it out twice or use reflection to find it from the attribute?
It's likely to be a mixture of a MEF metadata attribute, and an abstract base class. I would define my plugin contract as something like:
public interface IPluginMetadata
{
Guid PluginId { get; }
}
public interface IPlugin : IPluginMetadata
{
void Initialise();
}
I've enforced that the IPlugin interface also inherits our metadata contract IPluginMetadata. Next, we can create a custom export attribute:
[AttributeUsage(AttributeTargets.Class, Inherit = true), MetadataAttribute]
public class ExportPluginAttribute : ExportAttribute, IPluginMetadata
{
public ExportPluginAttribute(string pluginId) : base(typeof(IPlugin))
{
if (string.IsNullOrEmpty(pluginId))
throw new ArgumentException("'pluginId' is required.", "pluginId");
PluginId = new Guid(pluginId);
}
public Guid PluginId { get; private set; }
}
You don't need to decorate the export attribute with the metadata contract IPluginMetadata, as MEF will project the properties anyway, but I prefer to do so, so if I do introduce changes to my metadata contract, then my export attribute should be updated too. No harm, no foul.
Once we've done this, we can define an abstract base class from which to implement our plugin contract:
public abstract class PluginBase : IPlugin
{
protected PluginBase()
{
var attr = GetType()
.GetCustomAttributes(typeof(ExportPluginAttribute), true)
.Cast<ExportPluginAttribute>()
.SingleOrDefault();
PluginId = (attr == null) ? Guid.Empty : attr.PluginId;
}
public virtual Guid PluginId { get; private set; }
public abstract void Initialise();
}
We can then grab the custom attribute through the abstract class's constructor, and apply the property accordingly. That we can do:
public IPlugin GetPlugin(Guid id)
{
var plugin = container
.GetExports<IPlugin, IPluginMetadata>()
.Where(p => p.Metadata.PluginId == id)
.Select(p => p.Value)
.FirstOrDefault();
return plugin;
}
And also:
[ExportPlugin("BE112EA1-1AA1-4B92-934A-9EA8B90D622C")]
public class MyPlugin : PluginBase
{
public override Initialise()
{
Console.WriteLine(PluginId);
}
}
We can see that out PluginId is exposed both through exported metadata, as well as a property of our plugin.
That code is all untested, but I hope it pushes you in the right direction.
Put the GUID in a constant, and use it for both a property and the metadata:
[Export(typeof(IFoo))]
[ExportMetadata("GUID", _guid)]
public class Foo : IFoo
{
private const string _guid = "abc";
public string Guid { get { return _guid; } }
}
Note that you can't use the Guid type instead of string, as that is not permitted by the const keyword.