EF Code First - Globally set varchar mapping over nvarchar - c#

I have what should be an easy question but I have been unable to find the answer myself.
I am using EF4 CTP-5 Code First Model with hand generated POCOs. It is processing string comparisons in generated SQL as
WHERE N'Value' = Object.Property
I am aware that I can override this functionality using:
[Column(TypeName = "varchar")]
public string Property {get;set;}
Which fixes the issue for that single occurrence and correctly generates the SQL as:
WHERE 'Value' = Object.Property
However, I am dealing with a VERY large domain model and going through each string field and setting TypeName = "varchar" is going to be very very tedious. I would like to specify that EF should see string as varchar across the board as that is the standard in this database and nvarchar is the exception case.
Reasoning for wanting to correct this is query execution efficiency. Comparison between varchar and nvarchar is very inefficient in SQL Server 2k5, where varchar to varchar comparisons execute almost immediately.

Before EF 4.1, you could use conventions and add the following convention to your ModelBuilder:
using System;
using System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive;
using System.Data.Entity.ModelConfiguration.Conventions.Configuration;
using System.Reflection;
public class MakeAllStringsNonUnicode :
IConfigurationConvention<PropertyInfo, StringPropertyConfiguration>
{
public void Apply(PropertyInfo propertyInfo,
Func<StringPropertyConfiguration> configuration)
{
configuration().IsUnicode = false;
}
}
(Taken from http://blogs.msdn.com/b/adonet/archive/2011/01/10/ef-feature-ctp5-pluggable-conventions.aspx)
UPDATE: Pluggable conventions were dropped for the 4.1 release. Check my blog for an alternative approach)

For anyone looking to do this in EF Core (v3 and above), a quick way to achieve this is through the ModelBuilder.Model property; it provides easy access to all entities and properties within the model.
A "bare-bones" implementation follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Apply configurations via regular modelBuilder code-first calls
// ...
// ...
// Override the configurations to force Unicode to false
var entities = modelBuilder.Model.GetEntityTypes();
foreach (var entity in entities)
{
foreach (var property in entity.GetProperties())
{
property.SetIsUnicode(false);
}
}
}
EF Core happily ignores the SetIsUnicode call on non-string properties, so you don't even have to check the property type (but you easily could if it makes you feel better :)
For those who prefer to be a bit more explicit, tacking on a where clause to the GetProperties() call will do the trick:
...
var stringProperties = entity.GetProperties()
.Where(e=> e.ClrType == typeof(string));
foreach (var property in stringProperties)
{
property.SetIsUnicode(false);
}
...
UPDATE - Entity Framework Core 6
You are now able to do this type of global mapping out-of-the-box using
EF Core 6's pre-convention-model-configuration
A sample of how this would be achieved with this new feature is shown below:
configurationBuilder
.DefaultTypeMapping<string>()
.IsUnicode(false);

I extended Marc Cals' answer (and Diego's blog post) to globally set all strings on all entities as non-unicode as per the question, rather than having to call it manually per-class. See below.
/// <summary>
/// Change the "default" of all string properties for a given entity to varchar instead of nvarchar.
/// </summary>
/// <param name="modelBuilder"></param>
/// <param name="entityType"></param>
protected void SetAllStringPropertiesAsNonUnicode(
DbModelBuilder modelBuilder,
Type entityType)
{
var stringProperties = entityType.GetProperties().Where(
c => c.PropertyType == typeof(string)
&& c.PropertyType.IsPublic
&& c.CanWrite
&& !Attribute.IsDefined(c, typeof(NotMappedAttribute)));
foreach (PropertyInfo propertyInfo in stringProperties)
{
dynamic propertyExpression = GetPropertyExpression(propertyInfo);
MethodInfo entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
MethodInfo genericEntityMethod = entityMethod.MakeGenericMethod(entityType);
object entityTypeConfiguration = genericEntityMethod.Invoke(modelBuilder, null);
MethodInfo propertyMethod = entityTypeConfiguration.GetType().GetMethod(
"Property", new Type[] { propertyExpression.GetType() });
StringPropertyConfiguration property = (StringPropertyConfiguration)propertyMethod.Invoke(
entityTypeConfiguration, new object[] { propertyExpression });
property.IsUnicode(false);
}
}
private static LambdaExpression GetPropertyExpression(PropertyInfo propertyInfo)
{
var parameter = Expression.Parameter(propertyInfo.ReflectedType);
return Expression.Lambda(Expression.Property(parameter, propertyInfo), parameter);
}
/// <summary>
/// Return an enumerable of all DbSet entity types in "this" context.
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
private IEnumerable<Type> GetEntityTypes()
{
return this
.GetType().GetProperties()
.Where(a => a.CanWrite && a.PropertyType.IsGenericType && a.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
.Select(a => a.PropertyType.GetGenericArguments().Single());
}
Finally, call it from your OnModelCreating(DbModelBuilder modelBuilder):
foreach (var entityType in GetEntityTypes())
SetAllStringPropertiesAsNonUnicode(modelBuilder, entityType);

Here is a project from Sergey Barskiy that extends EF to allow custom conventions, which as a result, you can make custom attributes instead of the fluent API.
Here is a code snippet from here that demonstrates the utility in action.
What you don't see here is the decimal precision attribute and others. So per your question, once you set Unicode to false, it should be varchar as opposed to nvarchar.
public class Product
{
public int ProductId { get; set; }
[Indexed("Main", 0)]
public string ProductNumber { get; set; }
[Indexed("Main", 1)]
[Indexed("Second", direction: IndexDirection.Ascending)]
[Indexed("Third", direction: IndexDirection.Ascending)]
public string ProductName { get; set; }
[String(4, 12, false)] //minLength, maxLength, isUnicode
public string Instructions { get; set; }
[Indexed("Third", 1, direction: IndexDirection.Descending)]
public bool IsActive { get; set; }
[Default("0")]
public decimal? Price { get; set; }
[Default("GetDate()")]
public DateTime? DateAdded { get; set; }
[Default("20")]
public int Count { get; set; }
}
Read this and this for detail.

With Diego's blog help, to make the public properties of a POCO varchar without using anotations is :
private void SetStringPropertiesAsNonUnicode<e>(DbModelBuilder _modelBuilder) where e:class
{
//Indiquem a totes les propietats string que no són unicode per a que es crein com a varchar
List<PropertyInfo> stringProperties = typeof(e).GetProperties().Where(c => c.PropertyType == typeof(string) && c.PropertyType.IsPublic).ToList();
foreach (PropertyInfo propertyInfo in stringProperties)
{
dynamic propertyExpression = GetPropertyExpression(propertyInfo);
_modelBuilder.Entity<e>().Property(propertyExpression).IsUnicode(false);
}
}
// Edit: Also stole this from referenced blog post (Scott)
static LambdaExpression GetPropertyExpression(PropertyInfo propertyInfo)
{
var parameter = Expression.Parameter(propertyInfo.ReflectedType);
return Expression.Lambda(Expression.Property(parameter, propertyInfo), parameter);
}

Related

Linq to Entities could not be translated

I have this as my model (modified for obvious reasons)
public class Model
{
public int Id {get; set;}
public string Data {get; set;}
public TypeDomainValue ModelType {get; set;}
}
ModelType is known in the database as a string value only (no tables connected).
However if I want to filter on the value of TypeDomainValue in a linq statement
models.Where(c => c.ModelType.Value.Contains(searchString));
I get the error that the Linq expression cannot be translated.
I already tried using EF.Functions.Like which creates a similar error.
How can I get this to be translated properly as I do not want to load the entire table into memory.
Edit:
I use the following ValueConverter
public class ModelTypeDomainValueConverter : ValueConverter<ModelTypeDomainValue, string>
{
public ModelTypeDomainValueConverter([CanBeNull] ConverterMappingHints mappingHints = null) : base(ConvertToString(), ConvertToDomainValue(), mappingHints)
{
}
private static Expression<Func<ModelTypeDomainValue, string>> ConvertToString()
{
return x => x.Value;
}
private static Expression<Func<string, ModelTypeDomainValue>> ConvertToDomainValue()
{
return x => ModelTypeDomainValue.CreateByValue(x);
}
}
Which gets added with the following extension:
public static ModelBuilder UseValueConverter(this ModelBuilder modelBuilder, ValueConverter converter)
{
var type = converter.ModelClrType;
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type);
foreach (var property in properties)
{
modelBuilder.Entity(entityType.Name).Property(property.Name).HasConversion(converter);
}
}
return modelBuilder;
}
Make ur life easy and Use owned properties in Entity framework core.
bcs TypeDomainValue smells like that one.
it has not its own table.
it don't have any primary key.
it resides in same table as Mode
so its own property.
I m not saying own properties can not have those but typically they are like them
and we use them as Value objects

Entity Framework CodeFirst dynamic mapping

So I'm using the 'CodeFirst' methodology of Entity Framework and I have mapping files to map the table information and add in things such as validation so for instance:
this.Property(t => t.AccountName)
.IsRequired()
.HasMaxLength(25);
This is using the Fluent API and I'm wondering how to get the property name by string instead of t.AccountName. I'm wanting to dynamically set these properties and I just don't know how to do that programmatically.
Without commenting on whether this is advisable or not(!), you can achieve what you need because the Property() method takes an expression tree as its parameter. Consider the following:
public class MyEntity
{
[Key]
public int MyEntityId { get; set; }
public string MyProperty { get; set; }
}
public class MyContext : DbContext
{
public DbSet<MyEntity> MyEntities
{
get { return this.Set<MyEntity>(); }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var param = Expression.Parameter(typeof(MyEntity));
var propertyExpression = Expression.Lambda(Expression.Property(param, "MyProperty"), param);
modelBuilder.Entity<MyEntity>()
.Property((Expression<Func<MyEntity, string>>)propertyExpression)
.HasColumnName("Fish");
}
}
Here I build configuration for the MyProperty column, which I refer to by name in a lambda expression.
The above code works for string properties, but would require some modification to work for any property type. The cast to Expression<Func<MyEntity, string>> hard-codes the property type, but we can eliminate the cast using the dynamic language feature.
var param = Expression.Parameter(typeof(MyEntity));
dynamic propertyExpression = Expression.Lambda(Expression.Property(param, "MyProperty"), param);
modelBuilder.Entity<MyEntity>()
.Property(propertyExpression)
.HasColumnName("FishFace");

Setting MaxLength for all strings in Entity Framework code first

I'm developing a code-first database, using Entity Framework 6.
I know I can set [MaxLength(myLen)] on the property of a model.
What I wondered, is if this is possible to do in a filter or a custom attribute, so that all strings take on a default, of say 250, unless specified directly on the property.
Failing this, is there a way to change the default of nvarchar(max)?
Entity Framework introduced Custom Code First Conventions for this in 6.1
modelBuilder.Properties<string>()
.Configure(c => c.HasMaxLength(250));
Conventions operate in a last wins manner and the Fluent API and Data Annotations can be used to override a convention in specific cases
You can do this, which ensures all strings are the maximum length supported by the database provider:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<string>().Configure(p => p.IsMaxLength());
}
Add this method (or modify the existing one) in your DbContext class.
In EF6 you can use a custom code first convention, but you will also need to have a way to specify nvarchar(max) data type to a string property. So, I came up with the following solution.
Also see:
https://msdn.microsoft.com/en-us/data/jj819164#order
/// <summary>
/// Set this attribute to string property to have nvarchar(max) type for db table column.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class TextAttribute : Attribute
{
}
/// <summary>
/// Changes all string properties without System.ComponentModel.DataAnnotations.StringLength or
/// Text attributes to use string length 16 (i.e nvarchar(16) instead of nvarchar(max) by default).
/// Use TextAttribute to a property to have nvarchar(max) data type.
/// </summary>
public class StringLength16Convention : Convention
{
public StringLength16Convention()
{
Properties<string>()
.Where(p => !p.GetCustomAttributes(false).OfType<DatabaseGeneratedAttribute>().Any())
.Configure(p => p.HasMaxLength(16));
Properties()
.Where(p => p.GetCustomAttributes(false).OfType<TextAttribute>().Any())
.Configure(p => p.IsMaxLength());
}
}
public class CoreContext : DbContext, ICoreContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Change string length default behavior.
modelBuilder.Conventions.Add(new StringLength16Convention());
}
}
public class LogMessage
{
[Key]
public Guid Id { get; set; }
[StringLength(25)] // Explicit data length. Result data type is nvarchar(25)
public string Computer { get; set; }
//[StringLength(25)] // Implicit data length. Result data type is nvarchar(16)
public string AgencyName { get; set; }
[Text] // Explicit max data length. Result data type is nvarchar(max)
public string Message { get; set; }
}
In this code ModelBuilder class defines the shape of your entities, the relationships between them, and how they map to the database.
public class WebsiteDBContext : DbContext
{
public WebsiteDBContext(DbContextOptions<WebsiteDBContext> options) : base(options)
{
}
public DbSet<Global> Globals { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
// it should be placed here, otherwise it will rewrite the following settings!
base.OnModelCreating(builder);
builder.Entity<Global>();
builder.Entity<Global>(entity =>
{
entity.Property(global => global.MainTopic).HasMaxLength(150).IsRequired();
entity.Property(global => global.SubTopic).HasMaxLength(300).IsRequired(false);
entity.Property(global => global.Subject).IsRequired(false);
entity.Property(global => global.URL).HasMaxLength(150).IsRequired(false);
});
}
}

define database schema name universally in entity framework 4.1

i know you can define the entity's schema name per class by using ToTable("TableName", "SchemaName") but is there a way to set it up so you can set the schema name for all tables in the configuration as i am getting some weird results when i am using some types of relationship mapping and split entity mapping where it is reverting back to the default dbo.TableName in the internal sql queries
see this earlier post for sql output example
Im having Oracle database-first with EF 4.1, all mappings done with Data Annotations. Different Schema names in Test and Production environments. My solution is to map the Schema dynamically during OnModelCreating with some help of fluent API, reflection and late binding. Iterate through all Context class properties of generic type and do the dirty work. Works for me so far.
public class Context : DbContext
{
public Context()
: base(new OracleConnection(ConfigurationManager.ConnectionStrings["OraAspNetConString"].ConnectionString), true)
{
}
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
foreach (var p in typeof(Context).GetProperties().Where(foo=>foo.PropertyType.IsGenericType))
{
// this is what we are trying to accomplish here --
//modelBuilder.Entity<User>().ToTable("TBL_USERS", "TestSchema");
Type tParam = p.PropertyType.GetGenericArguments()[0]; // typeof User
MethodInfo generic = typeof(DbModelBuilder).GetMethod("Entity").MakeGenericMethod(new[] { tParam });
object entityTypeConfig = generic.Invoke(modelBuilder, null);
MethodInfo methodToTable = typeof(EntityTypeConfiguration<>).MakeGenericType(new[] { tParam }).GetMethod("ToTable", new Type[] { typeof(string), typeof(string) });
methodToTable.Invoke(entityTypeConfig, new[] { GetMappedTableName(tParam), currentOraSchemaName });
}
base.OnModelCreating(modelBuilder);
}
private string currentOraSchemaName = ConfigurationManager.AppSettings.Get("OraSchemaName");
private string GetMappedTableName(Type tParam)
{
TableAttribute tableAttribute = (TableAttribute)tParam.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault();
return tableAttribute.Name;
}
}
The user class here, with no hard-coded schema mapping --
[Table("TBL_USERS")]
public class User
{
[Column("USER_ID")]
public string UserId { get; set; }
[Column("USER_NAME")]
public string Name { get; set; }}
Since final EFv4.1 version doesn't have public API for custom conventions you cannot change the schema globally from the API.

EF4 CTP5 How To? Map an inherited Property to a field in a related table

i defined an entity called Variable and derived classes by using Table Per Hierarchy (TPH). The Base class "Variable" contains a collection of PropertyValues:
private ICollection<PropertyValue> propertyValues;
public const string DiscriminatorColumn = "Discriminator";
public const string Table = "Variables";
public VariableType VariableType { get; set; }
public string Name { get; set; }
[NotMapped]
public string Discriminator { get; set; }
public virtual ICollection<PropertyValue> PropertyValues
{
get { return this.propertyValues ?? (this.propertyValues = new ObservableCollection<PropertyValue>()); }
set { SetProperty(ref this.propertyValues, value, () => PropertyValues); }
}
Now, i want to derive a SpecialVariable class (or more than one), which define some SpecialProperties (e.g. HighLimit) which should be mapped to an entry in the PropertyValues (table).
public class MySpecialVariabe : Variable
{
public double HighLimit { get; set; }
}
My OnModelCreating function looks currently like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Variable>().HasKey(x => new { x.Id });
modelBuilder.Entity<Variable>()
.Map<MySpecialVariabe>(m => m.Requires(Variable.DiscriminatorColumn).HasValue(typeof(MySpecialVariabe).Name))
.Map<MySpecialVariabe2>(m => m.Requires(Variable.DiscriminatorColumn).HasValue(typeof(MySpecialVariabe2).Name)).ToTable(Variable.Table);
}
Can someone give me some tips how to realize this, without writing tons of bad looking code in the derived class. (Performance is not that important.)
best regards,
Chris
You can't map properties to records. That is how I understand your question. You have some PropertyValues table which is most probably some Key/Value pair and you want to map entity properties as records (data) to this table. This is not something which EF do for you. You must provide not mapped properties which will work with correct record in propertyValues collection.
Something like:
[NotMapped]
public double HighLimit
{
get
{
var current = propertyValues.SingleOrDefault(p => p.Key == "HighLimit");
return current != null ? current.Value : 0.0;
}
set
{
var current = propertyValues.SingleOrDefault(p => p.Key == "HighLimit");
if (current != null)
{
current.Value = value;
}
else
{
propertyValues.Add(new PropertyValue { Key = "HighLimit", Value = value });
}
}
}
The problem with this approach is that you can't use HighLimit in Linq-to-entities queries - you must always use PropertyValues.
Moreover TPH in EF requires that properties of derived entity (MySpecialVariable) are mapped to the same table as parent entity (Variable). You can't map properties of derived entity into data stored in other table (PropertyValues).

Categories

Resources