How can I change the default max length of string properties in Entity Framework 6? - ef-code-first

By default convention, strings properties in an entity model that are not explicitly given a max length are set to nvarchar(max) in the database. We wish to override this convention and give strings a max length of nvarchar(100) if they are not already explicitly set otherwise.
I discovered the PropertyMaxLengthConvention built-in convention, which by its description and documentation would seem to be what I am looking for. However, it either doesn't work or I'm using it wrong or it just doesn't do what I think it does.
I've tried simply adding the convention:
modelBuilder.Conventions.Add(new PropertyMaxLengthConvention(100));
Then I thought maybe the default one is already being used, so I tried removing it first:
modelBuilder.Conventions.Remove<PropertyMaxLengthConvention>();
modelBuilder.Conventions.Add(new PropertyMaxLengthConvention(100));
I even tried explictly adding the convention before and after the default one:
modelBuilder.Conventions.AddBefore<PropertyMaxLengthConvention>(new PropertyMaxLengthConvention(100));
modelBuilder.Conventions.AddAfter<PropertyMaxLengthConvention>(new PropertyMaxLengthConvention(100));
No joy. When I add migrations, the columns are still created as nvarchar(max).
Is there a way to use that convention to do what I want? If not, can I write a custom convention that will default string properties to nvarchar(100) but will still allow me to explicitly set them to a different value including maxlength?

After tracking down the source code for the aforementioned convention, I discovered that it only sets the default max length for properties that are specified to have fixed length. (Bizarre!)
So I took the source code and modified it to create my own convention. Now string properties with unspecified max length will have a default max length instead of being nvarchar(max). The only downside is there doesn't appear to be a way to detect when the IsMaxLength() configuration is explicitly applied. So if I have a column that I do want to have created as nvarchar(max) I can't use IsMaxLength() to do it.
To address this, I created an extension method for StringPropertyConfiguration called ForceMaxLength() that configures the property with HasMaxLength(int.MaxValue) - ordinarily an invalid value, but one for which I can easily test in my custom convention. When I detect it, I simply set the MaxLength back to null and set the IsMaxLength to true and let the property configuration continue as normal.
Here's the custom convention:
using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace MyProject.CustomConventions
{
public class CustomPropertyMaxLengthConvention : IConceptualModelConvention<EntityType>, IConceptualModelConvention<ComplexType>
{
private const int DefaultLength = 128;
private readonly int length;
public CustomPropertyMaxLengthConvention()
: this(DefaultLength)
{
}
public CustomPropertyMaxLengthConvention(int length)
{
if (length <= 0)
{
throw new ArgumentOutOfRangeException("length", "Invalid Max Length Size");
}
this.length = length;
}
public virtual void Apply(EntityType item, DbModel model)
{
SetLength(item.DeclaredProperties);
}
public virtual void Apply(ComplexType item, DbModel model)
{
SetLength(item.Properties);
}
private void SetLength(IEnumerable<EdmProperty> properties)
{
foreach (EdmProperty current in properties)
{
if (current.IsPrimitiveType)
{
if (current.PrimitiveType == PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String))
{
SetStringDefaults(current);
}
if (current.PrimitiveType == PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Binary))
{
SetBinaryDefaults(current);
}
}
}
}
private void SetStringDefaults(EdmProperty property)
{
if (property.IsUnicode == null)
{
property.IsUnicode = true;
}
SetBinaryDefaults(property);
}
private void SetBinaryDefaults(EdmProperty property)
{
if (property.MaxLength == int.MaxValue)
{
property.MaxLength = null;
property.IsMaxLength = true;
}
else if (property.MaxLength == null || !property.IsMaxLength)
{
property.MaxLength = length;
}
}
}
}
Here's the extension method:
using System.Data.Entity.ModelConfiguration.Configuration;
namespace MyProject.Model.Mapping
{
public static class MappingExtensions
{
public static void ForceMaxLength(this StringPropertyConfiguration obj)
{
obj.HasMaxLength(int.MaxValue);
}
}
}
Here's how it's used:
using System.Data.Entity.ModelConfiguration;
namespace MyProject.Model.Mapping
{
public class MyEntityMap : EntityTypeConfiguration<MyEntity>
{
public MyEntityMap()
{
Property(v => v.StringValue).ForceMaxLength();
}
}
}

Or just
public class StringConventions : Convention
{
public StringConventions()
{
this.Properties<string>().Configure(x => x.HasMaxLength(100));
}
}

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.
/// <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; }
}

Related

How to make ASP.NET to stop interpret null as string

I have a Web API method:
public List<Task> GetTasks([FromUri] TaskFilter filter)
{
}
The method has parameter with list of nullable identifiers:
public class TaskFilter
{
public IList<int?> Assignees { get; set; }
}
When I call it:
GET /tasks?assignees=null
Server returns an error:
{
"message":"The request is invalid.",
"modelState": {
"assignees": [ "The value 'null' is not valid for Nullable`1." ]
}
}
It works only if I pass empty string:
GET /tasks?assignees=
But standard query string converters (from JQuery, Angular, etc) do not work with nulls in such way.
How to make ASP.NET to interpret 'null' as null?
Upd: The query string can contain several identifiers, e.g.:
GET /tasks?assignees=1&assignees=2&assignees=null
Upd2: JQuery converts nulls in array to empty strings, and ASP.NET interprets them as null. So the question is about calling WebAPI from Angular 1.6 ($HttpParamSerializerProvider)
Upd3: I know about workarounds, but I do not ask for them. I want a solution for specific problem:
It is a GET method
Method accepts a list from Uri
A list can contain null values
It should be List<int?> because API docs are generated automatically, and I do not want to see text array as parameter type
By default ASP.NET expects empty strings for null values (JQuery.param works in that way)
But some client libraries (e.g. Angular) does not convert null array items to empty strings
You can create a custom model bind for this specific type, inherithing from DefaultModelBinder, for sample:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
public class TaskFilterBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var assignees = request.QueryString["assignees"];
if (assignees == "null") // check if assignees is null (string) then return NULL
return null;
return assignees;
}
}
Finally we need to inform the controller as to the binding we want it to use. This we can specify using attributes
[ModelBinder(typeof(TaskFilterBinder))]
as below:
public List<Task> GetTasks([FromUri(ModelBinder=typeof(TaskFilterBinder))] TaskFilter filter)
{
// Do your stuff.
}
For more reference check this link on Custom Model Binders.
Hope, this solves your problem . Thanks
Finally, I found a solution using custom value provider:
using System;
using System.Collections.Generic;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ValueProviders;
using System.Web.Http.ValueProviders.Providers;
using System.Globalization;
using System.Net.Http;
using System.Web.Http.ModelBinding;
public sealed class NullableValueProviderAttribute : ModelBinderAttribute
{
private readonly string[] _nullableColumns;
public NullableValueProviderAttribute(params string[] nullableColumns)
{
_nullableColumns = nullableColumns;
}
public override IEnumerable<ValueProviderFactory> GetValueProviderFactories(HttpConfiguration configuration)
{
return new ValueProviderFactory[] { new NullableValueProviderFactory(_nullableColumns) };
}
}
public class NullableValueProviderFactory : ValueProviderFactory, IUriValueProviderFactory
{
private readonly string[] _nullableColumns;
public NullableValueProviderFactory(string[] nullableColumns)
{
_nullableColumns = nullableColumns;
}
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
return new NullableQueryStringValueProvider(actionContext, CultureInfo.InvariantCulture, _nullableColumns);
}
}
public class NullableQueryStringValueProvider : NameValuePairsValueProvider
{
private static readonly string[] _nullValues = new string[] { "null", "undefined" };
private static IEnumerable<KeyValuePair<string, string>> GetQueryNameValuePairs(HttpRequestMessage request, string[] nullableColumns)
{
foreach (var pair in request.GetQueryNameValuePairs())
{
var isNull = Array.IndexOf(nullableColumns, pair.Key) >= 0 && Array.IndexOf(_nullValues, pair.Value) >= 0;
yield return isNull ? new KeyValuePair<string, string>(pair.Key, "") : pair;
};
}
public NullableQueryStringValueProvider(HttpActionContext actionContext, CultureInfo culture, string[] nullableColumns) :
base(GetQueryNameValuePairs(actionContext.ControllerContext.Request, nullableColumns), culture)
{ }
}
And specify it in Web API action:
public List<Task> GetTasks([NullableValueProvider("assignees")] TaskFilter filter)
{
}

What is the use of return this["key"]?

This is an simple Scenario , i totally understand the first code of set & get.
private string exampleValue;
public string Example
{
get { return this.exampleValue ; }
set { this.exampleValue = value ; }
}
How is it differ from this code :
public string Example
{
get
{
return this["Example"].ToString();
}
}
It is an Indexer property.
You can define your own indexers in a class. For example, here is a string indexer.
class myClass
{
...
public object this[string name]
{
get
{
... implement code here to retrieve the object that correspond to your string index
}
set
{
... implement code here to store the object that correspond to your string index
}
}
}
Indexers are not limited to string or integer. For example, the Dictionary object uses a generic Indexer:
public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, ...
...
public TValue this[TKey key] { get; set; }
Also, the Session and Application objects in ASP.NET uses a string indexer.

Unboxing object

I have a web method that accepts object
[WebMethod]
public static void GetObject(object data)
{
}
Also, I have 2 classes:
class ConnectionString
{
public string ConnectionString { get; set; }
public DatabaseType DatabaseType { get; set; }
}
class Path
{
public string Path { get; set; }
public bool IsNetwork { get; set; }
}
On client side, using javascript, i defined 2 similar classes as well:
function ConnectionString() {
this.ConnectionString = '';
this.DatabaseType = 0;
};
function Path() {
this.Path = '';
this.IsNetwork = false;
};
Now, according to user decision, he can ether choose to create log in database or file system. When I send data to the method, my object resulted as null. If I create method
for each object, it works. Is there a way to unbox or desirialize from OBJECT type to ?
You need to create two method overloads that each take in one of the possible classes. In the current implementation the engine does not know what classes should be put in the WSDL...
If you are using WCF you could use [KnownType] attribute to specify which classes your method supports.

asp.net mvc custom attributes

I am trying to create a custom attribute in mvc to use it's parameters in a view as breadCrumb.
well, this is the code of the attribute
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class BreadCrumbAttribute : Attribute {
public BreadCrumbAttribute(string title, string parent, string url) {
this._title = title;
this._parent = parent;
this._url = url;
}
#region named parameters properties
private string _title;
public string Title {
get { return _title; }
}
private string _url;
public string Url {
get { return _url; }
}
private string _parent;
public string Parent {
get { return _parent; }
}
#endregion
#region positional parameters properties
public string Comments { get; set; }
#endregion
}
this is the call of the attribute
[BreadCrumbAttribute("tile", "parent name", "url")]
public ActionResult Index() {
//code goes here
}
this is a way of how I'd like to get the values. (this is a partial view)
System.Reflection.MemberInfo inf = typeof(ProductsController);
object[] attributes;
attributes = inf.GetCustomAttributes(typeof(BreadCrumbAttribute), false);
foreach (Object attribute in attributes) {
var bca = (BreadCrumbAttribute)attribute;
Response.Write(string.Format("{0}><a href={1}>{2}</a>", bca.Parent, bca.Url, bca.Title));
}
Unfortunately, the attribute didn't get call with the way I implement it. Although, If I add the attribute in Class instead of an Action method it worked.
How could I make it work?
Thanks
The problem is that you are using reflection to get the attributes for the class, so naturally it does not include attributes defined on the action method.
To get those, you should define an ActionFilterAttribute, and in the OnActionExecuting or OnActionExecuted method, you can use filterContext.ActionDescriptor.GetCustomAttributes() method (MSDN description here).
Note that with this solution, you will likely have two different types of attributes: The first one is the one you wrote, to define the breadcrumbs. The second is the one that looks at the attributes on the executing action and builds up the breadcrumb (and presumably adds it to the ViewModel or sticks it in HttpContext.Items or something).

Grails bind request parameters to enum

My Grails application has a large number of enums that look like this:
public enum Rating {
BEST("be"), GOOD("go"), AVERAGE("av"), BAD("ba"), WORST("wo")
final String id
private RateType(String id) {
this.id = id
}
static public RateType getEnumFromId(String value) {
values().find {it.id == value }
}
}
If I have a command object such as this:
class MyCommand {
Rating rating
}
I would like to (for example) automatically convert a request parameter with value "wo" to Rating.WORST.
The procedure for defining custom converters is described here (in the context of converting Strings to Dates). Although this procedure works fine, I don't want to have to create a class implementing PropertyEditorSupport for each of my enums. Is there a better alternative?
I found a solution I'm pretty happy with.
Step 1: Create an implementation of PropertyEditorSupport to convert text to/from the relevant Enum
public class EnumEditor extends PropertyEditorSupport {
private Class<? extends Enum<?>> clazz
public EnumEditor(Class<? extends Enum<?>> clazz) {
this.clazz = clazz
}
public String getAsText() {
return value?.id
}
public void setAsText(String text) {
value = clazz.getEnumFromId(text)
}
}
Step 2: Define a class that registers EnumEditor as a converter for the various enum classes. To change the list of enum classes that are bindable by id, just modify BINDABLE_ENUMS
public class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
private static final String REQUIRED_METHOD_NAME = 'getEnumFromId'
// Add any enums that you want to bind to by ID into this list
private static final BINDABLE_ENUMS = [Rating, SomeOtherEnum, SomeOtherEnum2]
public void registerCustomEditors(PropertyEditorRegistry registry) {
BINDABLE_ENUMS.each {enumClass ->
registerEnum(registry, enumClass)
}
}
/**
* Register an enum to be bound by ID from a request parameter
* #param registry Registry of types eligible for data binding
* #param enumClass Class of the enum
*/
private registerEnum(PropertyEditorRegistry registry, Class<? extends Enum<?>> enumClass) {
boolean hasRequiredMethod = enumClass.metaClass.methods.any {MetaMethod method ->
method.isStatic() && method.name == REQUIRED_METHOD_NAME && method.parameterTypes.size() == 1
}
if (!hasRequiredMethod) {
throw new MissingMethodException(REQUIRED_METHOD_NAME, enumClass, [String].toArray())
}
registry.registerCustomEditor(enumClass, new EnumEditor(enumClass))
}
}
Step 3: Make Spring aware of the registry above by defining the following Spring bean in grails-app/conf/spring/resources.grooovy
customPropertyEditorRegistrar(CustomPropertyEditorRegistrar)
So the default Databinding binds on the Enum name and not a separately defined property of the Enum. You can either create your own PropertyEditor as you have mentioned or do a work-around similar to this:
class MyCommand {
String ratingId
Rating getRating() {
return Rating.getEnumFromId(this.ratingId)
}
static constraints = {
ratingId(validator:{val, obj -> Rating.getEnumFromId(val) != null })
}
}

Resources