In my DbContext, I am setting all strings to varchar(250), but I have some strings marked as varchar(max)". After creating my migrations, I see the tables marked as max are still being created with a max length of 250.
How can I have my data annotations override the config builder command?
Config builder:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<string>()
.HaveColumnType("varchar(250)")
.AreUnicode(false);
}
Entity:
[Column(TypeName = "varchar(max)")]
public string? AffectedColumns { get; set; }
What is generated in the migration:
AffectedColumns = table.Column<string>(type: "varchar(250)", unicode: false, nullable: true)
You're using Pre-convention configuration here. The current documentation doesn't explicitly state how this type of configuration collaborates with model configuration, i.e. fluent API (configuration classes and OnModelCreating overrides) and data annotations, but it does say
This configuration is performed before a model is created.
Which indicates that model configuration overrules it. And yes, it does.
However, in my tests, only the fluent API does so consistently. Data annotations are messy (not only in Entity Framework). For example, [StringLength(20)] overrides the length, [Unicode(true)] does not override the varchar datatype and [Column(TypeName = "varchar(max)")] (or [DataType("nvarchar(max)")]) does not override the length nor the data type.
Which means: always use fluent API to model exceptions to the conventions:
modelBuilder.Entity<MyClass>()
.Property(e => e.AffectedColumns)
.HasColumnType("nvarchar(max)");
EFCore 7 introduces finalizing conventions. It looks like the part you're looking for is here:
Default length for all string properties
You need to put your convention in the ProcessModelFinalizing method. Explicit configurations are not overridden by the finalizing convention so your properties configured for varchar(max) should be fine.
public class DiscriminatorLengthConvention2 : IModelFinalizingConvention
{
public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()
.Where(entityType => entityType.BaseType == null))
{
var discriminatorProperty = entityType.FindDiscriminatorProperty();
if (discriminatorProperty != null
&& discriminatorProperty.ClrType == typeof(string))
{
discriminatorProperty.Builder.HasMaxLength(250);
}
}
}
}
Related
Question: Is there a way in Entity Framework Core 3.0 to apply value conversion on backing fields?
Context:
Let's say I have a Blog entity that contains a list of string values representing post ids. I want to avoid the need to have a join entity/table (as described here) so I do a conversion to store the list as a string in the DB. I also want to protect my model from being modified directly through the PostIds property, so I want to use a backing field for that (as described here).
Something like this:
public class Blog
{
public int BlogId { get; set; }
private readonly List<string> _postIds;
public IReadOnlyCollection<string> PostIds => _postIds;
public Blog()
{
_posts = new List<Post>();
}
}
And configuration of the context would look something like that:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configuring the backing field
modelBuilder.Entity<Blog>()
.Metadata
.FindNavigation(nameof(Blog.PostIds))
.SetPropertyAccessMode(PropertyAccessMode.Field);
// Trying to configure the value conversion, but that doesn't work...
modelBuilder.Entity<Blog>()
.Property(e => e.PostIds)
.HasConversion(v => string.Join(',', v),
v => v.Split(','));
}
Any ideas how this configuration could be achieved with the current version of Entity Framework (3.0)?
In an existing SQL table a nvarchar column holds enumerative informations.
I now try to use a enum class to map this column using ADO.Net Entity Framework. But somehow this does not seems possible since it requires an integer column.
Does ADO EF not support enums mapped to char columns, or how to realize it allowing linq or lambda syntax on querying?
Error: Given element assignment is invalid.
I am more used to hibernate orm where this is easily possible.
You can use private properties in your model to map your data to whatever property type you want.
// Model
public class Piece
{
// Subclass Piece to add mappings for private properties
public class PieceConfig : EntityTypeConfiguration<Piece>
{
public PieceConfig()
{
Property(b => b.dbtype); // needed for EF to see the private property
}
}
[Column("type", TypeName = "VARCHAR")]
private string dbtype { get; set; }
[NotMapped]
public PIECE type
{
get { return (PIECE)Enum.Parse(typeof(PIECE), dbtype); }
set { dbtype= value.ToString(); }
}
}
Then you just need to add the configuration to your OnModelCreating method
modelBuilder.Configurations.Add(new Piece.PieceConfig());
What is the feasibility of modifying the mapping code to convert a short of value zero or non-zero to false or true, if the boolean destination property is marked with an attribute in the POCO model?
I mean, this is supposed to be one of the advantages of EF being open sourced, and would be for in house use only.
Any tips on where in the code I would look would be appreciated, but this question is really more general and I'd like to hear anything anyone has to say on this.
With regard to the General comments please.
I dont know to make the EF change, but dealing with similar issues is not an uncommon issue in EF.
Not all standard types are supported by EF.
You can have a helper field in your POCO class.
So one field is the actual DB field, but no used outside of POCO.
The help field is NOTMAPPED or ignored in fluent API.
You access the DB via you helper and execute any required casting.
A simple example. Or the reverse if I got helper and DB field types back to front.
[NotMapped]
public virtual bool IsVisible { set; get; } // Helper Field NOT on DB
public int Test { get { return IsVisible ? 1 : 0; } // on DB, but set and get via helper only.
set { IsVisible = (value != 0); } }
Edit: Power Fluent API
Here is a snippet that outlines how you have code that runs for every mapped poco in a consistent way.
public class MyDbContext : DbContext
// model building, set breakpoint so you know when this is triggered
// it is important this ISNT called everytime, only on model cache.
// in my case that is app pool recycle.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
// use the CONFIG add feature to better organize and allow use of inheritance when mapping
// I will use snippets and statics to keep it simple.
modelBuilder.Configurations.Add(XYZMap.Map()); // POCO map
modelBuilder.Configurations.Add(ABCMAP.Map()); // poco map
modelBuilder.Configurations.Add(XXXMap.MAP()); // poco map
// etc for your POCO set
// Note, no need to declare DBset<xyz> XYZs {get;set;} !!!!
public static class XYZMap {
public static BaseEntityIntConfiguration<PocoXYZ> Map() {
//see return object !
var entity = new BaseEntityLongConfiguration<PocoXYZ>();
//entity.Property()... // map away as usual POCO specifc
///entity.HasRequired()...// property and relationships as required
// do nothing for default
return entity;
}
}
}
// all tables with int key use this base config. do it once never again
public class BaseEntityIntConfiguration<T> : BaseEntityConfiguration<T> where T : BaseObjectInt {
public BaseEntityIntConfiguration(DatabaseGeneratedOption DGO = DatabaseGeneratedOption.Identity) {
// Primary Key
this.HasKey(t => t.Id);
// Properties
//Id is an int allocated by DB
this.Property(t => t.Id).HasDatabaseGeneratedOption(DGO); // default to db generated
// optimistic lock is also added here, Specific to out poco design
this.Property(t => t.RowVersion)
.IsRequired()
.IsFixedLength()
.HasMaxLength(8)
.IsRowVersion();
// any other common mappings/ rules ??
}
}
public class BaseEntityConfiguration<T> : EntityTypeConfiguration<T> where T : BaseObject {
public BaseEntityConfiguration() {
this.ApplyAttributeRules(); // <<<<< Here is where I apply SYSTEM WIDE rules
}
}
public static void ApplyAttributeRules<T>(this EntityTypeConfiguration<T> entity) where T : BaseObject {
// so this will be called for each mapped type
foreach (var propertyInfo in typeof (T).GetProperties()) {
// I use reflection to look for properties that meet certain criteria.
// eg string. I want as NVARCHAR 4000 not NVCAHR max so i can index it.
if (propertyInfo.UnderLyingType().FullName == "System.String") {
SetStringLength(BosTypeTool.StringLengthIndexable, propertyInfo.Name, entity);
continue;
}
SetStringLength(4000, propertyInfo.Name, entity);
}
}
private static void SetStringLength<TModelPoco>(int length, string propertyName,
EntityTypeConfiguration<TModelPoco> entity) where TModelPoco : BaseObject {
var propLambda = DynamicExpression.ParseLambda<TModelPoco, String>(propertyName);
entity.Property(propLambda).HasMaxLength(length);
// dynamic library from Microsoft.... http://msdn.microsoft.com/en-US/vstudio/bb894665.aspx
}
// get underlying type incase it is nullable
public static Type UnderLyingType(this PropertyInfo propertyInfo) {
return Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
}
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; }
}
My requirement is configuing the string length mapping globally, but also can use MaxLengthAttribute to configue a property specially. Here's my code:
public class StringLengthConvention
: IConfigurationConvention<PropertyInfo, StringPropertyConfiguration>
{
public void Apply(
PropertyInfo propertyInfo,
Func<StringPropertyConfiguration> configuration)
{
StringAttribute[] stringAttributes = (StringAttribute[])propertyInfo.GetCustomAttributes(typeof(StringAttribute),true);
if (stringAttributes.Length > 0)
{
configuration().MaxLength = stringAttributes [0].MaxLength;
}
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add<StringLengthConvention>();
}
public class ContentInfo
{
// ...
[MaxLength(200)]
[String]
public string TitleIntact { get; set; }
// ...
}
My problem is the "MaxLength" can't work anymore. Do i need to check if the property has a MaxLengthAttribute before applying the global configuration in the StringLengthConvention.Apply()?
What would work in this situation is to create a lightweight convention specifying the MaxLength property for strings. In this situation the convention would set the max length of the property for all strings except where it was already configured by an annotation, with the fluent API, or by another convention.
In your OnModelCreate method add the following code to set your default MaxLength:
modelBuilder.Properties<string>()
.Configure(c => c.HasMaxLength(DefaultStringLength));
there is a walkthough of conventions here: http://msdn.microsoft.com/en-us/data/jj819164.aspx
make sure to take a look at the "Further Examples" at the bottom of the page