It seems like a lot of the data annotation attributes are no longer available. DefaultValue() is a good example.
Putting all these unsupported attributes for the entire database in OnModelCreating() does not seem like a good approach to me. It could easily grow very long and it's in a separate file from my model definition.
Do others agree with me, or is there some reason I've missed why growing OnModelCreating() is better than just adding an attribute right next to the model property?
Instead of putting all attributes in OnModelCreating(), you could use the EntityTypeConfiguration classes (see https://docs.microsoft.com/en-us/dotnet/api/system.data.entity.modelconfiguration.entitytypeconfiguration-1?view=entity-framework-6.2.0)
There are some great examples here.
@jaoord That certainly seems a better design than just throwing code for every entity in your database into a single method, and one I'll probably use (if I need to).
But I still think data annotation attributes are a better design and much easier to use. It pairs those annotations with the declaration of the property it affects. What could be better than that? And I just can't understand the decision to move away from this.
Personally, I felt annotation on POCO classes was tightly coupling domain objects to the data layer and avoided annotations in favor of Fluent API. I prefer a stricter separation of concerns, and POCO classes for me are domain definition files, and the Fluent API only purpose is to map their properties to the database. I don't know if this is the team's thinking, and I certainly don't mean to imply they should deprecate useful annotations.
As far as throwing it all into a single method call, it's a lifecycle event so that's not too unusual, however our OnModelCreating is refactored to a list of calls like:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new FooEntityConfig());
modelBuilder.Configurations.Add(new BarEntityConfig());
modelBuilder.Configurations.Add(new WidgetEntityConfig());
base.OnModelCreating(modelBuilder);
}
@RyanJMcGowan : I'm not sure I understand. How are your table columns, and your table column properties not the same concern?
I don't particularly care for code first. I find it easier to design my schema in SSMS. But I feel like I've been pushed into code first. And now the classes are, in fact, how I define my schema. To me, they are the same thing, and the same concern.
The table columns are also defined in the Fluent API. I have no annotations on my POCO class at all.
I have also done database-first and used the Fluent API to map to the database. I do like thinking through the data structure in SSMS. You can skip using migrations, or code that as well. Even though it's called code-first, there's no reason you can't just design in SSMS and assign POCOs to tables in code, either by annotation or by Fluent API.
@RyanJMcGowan : If you specified all your class definitions and properties in OnModelCreating(), I would think you were a little strange, but that all your table creation stuff was together. But you have it split between there and where you actually define your entity classes. So for whatever reason, I'm just not getting why you think those should be separate. Are you saying you don't see any reason why someone might want them defined all in one place?
The columns may also be defined in the Fluent API, but if data annotation attributes were fully supported, that would be done behind the scenes. And you wouldn't mess with OnModelCreating() to design your schema.
I have class definitions in class files under a separate core class library that is common to several projects across the solution. My POCO classes are very simple and the classes know nothing about a database. If I wanted to, I could store them as binary files, as JSON, or in a relational database without having to alter the POCO class. It's just a simple class blind to the outside workings of the application.
In OnModelCreating() I only implement the way they classes maps to a relational database with the Fluent API. My OnModelCreating() is just a list of modelBuilders, one for each POCO class I'm storing. There's no class definitions in OnModelCreating of course. That would be very strange indeed.
In my core project is my POCO class, with almost no dependencies, a very simple class sans annotations:
```C#
public class GameData : BaseEntity
{
public string Name { get; set; }
public string Group { get; set; }
public string Category { get; set; }
public string SubCategory { get; set; }
public string Description { get; set; }
public string URL { get; set; }
// other properties, etc.
}
In my data infrastructure project, where Entity Framework is a dependency:
```C#
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// modelBuilder configurations
modelBuilder.Configurations.Add(new GameDataEntityConfig());
// more modelBuilder configurations
base.OnModelCreating(modelBuilder);
}
And the Fluent API modelBuilder:
C#
internal class GameDataEntityConfig : EntityTypeConfiguration<GameData>
{
public GameDataEntityConfig()
{
this.ToTable("WhateverTableNameIWant");
this.HasKey<Guid>(g => g.Id);
this.Property(g => g.Name)
.HasColumnName("WhateverColumnNameIWant")
.HasColumnType("varchar")
.HasMaxLength(50);
// more mappings...
}
}
Hey @Ryan-Efendy, this is a great discussion and glad to see everyone contributing with the various best practices.
To be able to achieve a domain model without having dependencies on a persistence framework has been the core centric concept of Entity Framework and its largest differentiator in the ORM market (across any tech stack I've seen ie: .NET, iOS, Android etc.) since way back in EF 4 when POCO support got introduced.
It's worth having a full read through the reason for its existence/introduction here (https://docs.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-getting-started/continuing-with-ef/what-s-new-in-the-entity-framework-4#poco-support), but the intro sentence pretty much sums it up: "When you use domain-driven design methodology, you design data classes that represent data and behaviour that's relevant to the business domain. These classes should be independent of any specific technology used to store (persist) the data"
We moved away from attributes when EF 4 got introduced, since this was a major 'aha' moment and we were fully onboard with the separation of concerns and never looked back since.
I can't speak to the EF team here, but I imagine there are certain things that are either difficult or impossible to define via attributes, and certainly costly to maintain for the team thus their choice to deprecate / move away from the attributes way of defining things. If this was Apple rather than Microsoft they would have deprecated them in 2011 and moved to the 'new way' in 2012/2013 at the latest. At some point though, you can't be on the 'old track' of thinking so I would say this is a great time to re-architect everything over to the more modern approach.
One other angle to all this is that unfortunately in EF 6 we were limited to how much we could prevent the skew of persistence from impacting our model, mostly due to mapping limitations for things that were stored in one format in the database (say a String) but needed to be accessed in another (ie: say a semantic enum or a more complex structure - ie: an Address, PhoneNumber object/struct etc.). EF Core has since made a big breakthrough on this front with the Value Converters (https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) or Owned Entity types.
If you look at the syntax needed to achieve these, this gets more and more difficult/impossible to do via attributes, and while I'm not familiar specifically I'm pretty sure persisting to other database types such as Cosmos / no SQL is going get stickier and stickier on this front.
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
All of this points the way to persistence independence and what has been Entity Framework's largest innovation against other ORMs. Take advantage of it =)
Most helpful comment
Personally, I felt annotation on POCO classes was tightly coupling domain objects to the data layer and avoided annotations in favor of Fluent API. I prefer a stricter separation of concerns, and POCO classes for me are domain definition files, and the Fluent API only purpose is to map their properties to the database. I don't know if this is the team's thinking, and I certainly don't mean to imply they should deprecate useful annotations.
As far as throwing it all into a single method call, it's a lifecycle event so that's not too unusual, however our OnModelCreating is refactored to a list of calls like: