Fluent API Extension Methods for Common Attributes in .NET


#1

Hi,

Random thought while I was playing around with Entity Framework. Maybe its a bad idea, maybe not. Maybe there’s already something like this out there, but I thought it seems like something that should be part of the framework.

Attributes work well for describing your objects to a particular framework, but only if the object will only be used with that specific framework. Once your objects, or the annotations themselves, become shared, it becomes more problematic to get them to play ball under every context.

I don’t want to sound like I’m against attributes, I love them for quick small scale applications, but in large complex systems our objects do seem to get cluttered with them. The common DataAnnotations were always a problem if you needed to describe different validation rules between your EF back-end, and your MVC front-end.

When Entity Framework added fluent configurations, this helped a lot to deal with ensuring that your object’s database configuration was separated from your objects. And that got me thinking, did the EF fluent API go far enough?

I’ve always been a fan of LINQ, and how it abstracts the query from the underlying data source or technology used to access the data.

What if there was a common set of extension methods for expressing the same configurations as the annotations, but where they are backed by a different context provider?

The three most obvious areas of development that this could target are:

  • Data Validation (e.g. Entity Framework, MVC)
  • Data Modeling (e.g. Entity Framework, model binding)
  • Data Serialization (e.g. XML, JSON, Data Contracts for WCF)

If it were done correctly, you could either define a single configuration of a given object that could be applied across multiple contexts, or have separate configurations for where the object needs different rules for different contexts.

This would result in having only a single API needed to learn to configure these aspects of .NET technologies, regardless of the underlying framework/technology used. It would also mean that our domain classes could remain free of annotations/attributes, allowing a single class to be re-used correctly across multiple contexts, and having the different configured behaviours as appropriate.

Regards,
Rob.

== brain dump mode: off ==


#2

I’m pretty sure that you don’t need attributes, especially when it comes to modeling, Entity Framework supports the notion of convention over configuration without the usage of attributes.

You can check more about it here.

Here are few examples from a project I’ve been working on.

namespace Somia.Data.SqlServer.Configuration
{
    using System.Data.Entity.ModelConfiguration;

    using Somia.Data.Entities;

    public class PostConfig : EntityTypeConfiguration<Post>
    {
        internal PostConfig()
        {
            Property(x => x.Data).IsRequired();
            Property(x => x.UrlName).IsRequired();
        }
    }
}

namespace Somia.Data.SqlServer.Conventions
{
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Infrastructure;
    using System.Data.Entity.ModelConfiguration.Conventions;

    public class NoUnderscoreConvention : IStoreModelConvention<EdmProperty>
    {
        public void Apply(EdmProperty property, DbModel model)
        {
            property.Name = property.Name.Replace("_", string.Empty);
        }
    }
}

And finally the context.

namespace Somia.Data.SqlServer
{
    using System.Data.Entity;
    using System.Data.Entity.ModelConfiguration.Conventions;

    using Somia.Data.Entities;

    public sealed class DataContext : DbContext
    {
        public DataContext()
            : base("Somia")
        {
            Posts = Set<Post>();

            Files = Set<File>();

            Resources = Set<Resource>();

            Tags = Set<Tag>();
        }

        public DbSet<File> Files { get; set; }

        public DbSet<Post> Posts { get; set; }

        public DbSet<Resource> Resources { get; set; }

        public DbSet<Tag> Tags { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.AddFromAssembly(GetType().Assembly);
            modelBuilder.Configurations.AddFromAssembly(GetType().Assembly);

            modelBuilder.Conventions.Add(new PluralizingTableNameConvention());

            base.OnModelCreating(modelBuilder);
        }
    }
}

.NET Foundation Website | Blog | Projects | Code of Conduct