I am currently trying to make a custom constraint with the C# Entity Framework. In more detail:
I have a number field which only can have certain values (for example 1, 2 and 3). How do I achieve this constraint in a code first environment?
Entity Framework automatically validates any validations you add to your model by ValidationAttributes. RequiredAttribute or RangeAttribute are two examples of built-in subclasses of this attribute.
If you want some custom validation, the most convenient way is to piggyback on this mechanism and create you own ValidationAttribute subclass.
If you want to validate a non-contiguous range of values you can't use RangeAttribute but you could make an own attribute, for instance like this:
public class AllowedValuesAttribute : ValidationAttribute
{
private readonly ICollection<object> _validValues;
private readonly Type _type;
public AllowedValuesAttribute(Type type, object[] validValues)
{
_type = type;
_validValues = validValues.Where(v => v != null && v.GetType() == type).ToList();
}
public override bool IsValid(object value)
{
return value.GetType() == _type && _validValues.Contains(value);
}
public override string FormatErrorMessage(string name)
{
return string.Format("Value for '{0}' is not an allowed value", name);
}
}
Usage:
[AllowedValues(typeof(int), new object[] { 1, 2, 4, 8, 16 })]
public int Base { get; set; }
Note that we have to use fixed values here, because the content of the attribute must be known at compile time. Also, we have to use object because (currently) C# doesn't support generic attributes. Apart from that, there are numerous options. For example, the attribute could also have a method that finds allowed values at runtime, maybe from a named source, so you can supply this name in its constructor.
I don't see any problem in adorning entity classes with validation attributes. The entity model is not a domain model, it's part of a data access layer. It's primary purpose is (and should be) to facilitate an application's data access. If an entity model also happens to support business logic that's a mere bonus.
It's very bad practice to add dataannotations in domain-model, like D.Mac wrote.
So what about doing it the nicer way?
public MyClass
{
private int myNumberField;
public int MyNumberField
{
get { return myNumberField; }
set
{
if (value >= 1 && value <=3)
myNumberField = value;
else
// throw exception?
// set default-value (maybe 1)?
// do nothing?
}
}
}
you could do whatever you want in the setter of your property
and only restricting it in the front-end is not the best solution, since you always can modify javascript/html - but you should show the user, that he only can insert values 1, 2 or 3.
Also restrict it in the viewmodel with data annotations.
OR:
you could also override EntityFrameworks SaveChanges and add your businesslogic:
public override int SaveChanges(SaveOptions options)
{
foreach (ObjectStateEntry entry in
ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified))
{
// Validate the objects in the Added and Modified state
// if the validation fails, e.g. throw an exeption.
}
return base.SaveChanges(options);
}
Related
I have a class that contains a Dictionary<int, object> property that is marked as required. When I deserialize the data coming into the controller onto that class, the Required attribute works to prevent nulls from entering that property, but it does not stop nulls from entering the dictionary as a value given the key value pairs are formatted and passed in correctly.
Is there a way to have the Required attribute also prevent nulls from being values in the dictionary? Or is there another attribute that can be added to that property to accomplish this?
Or would the best way to solve this be to roll my own class that essentially consists of key-value pairs that I can mark both the key property and value property as Required? EX:
public class Example
{
[Required]
public int Key;
[Required]
public object Value;
}
And then just have an IEnumerable<Example> instead of Dictionary<int, object>?
The best I can think of is to have ISet<Example> (using a HashSet<Example>) with Example's GetHashCode and Equals methods overridden. That should satisfy your second desirable.
As for the [Required] attribute, you would have to write the code yourself to check if those properties are not null before adding it into the ISet<Example>. That may require some reflection logic.
This is what I ended up going with, and it works exactly how I wanted Required to work.
[AttributeUsage(AttributeTargets.Property)]
public class DictionaryRequiredAttribute : ValidationAttribute
{
public DictionaryRequiredAttribute() : base(() => "The {0} field is required and cannot contain null values.") { }
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
if (value is IDictionary dictValue)
{
foreach (var key in dictValue.Keys)
{
if (dictValue[key] == null)
{
return false;
}
}
}
return true;
}
}
Based mainly on the implementation of the RequiredAttribute found here.
I am trying to validate a form submit BUT i want to validate a property ONLY if another property is set to true.
my two properties:
[DisplayName(Translations.Education.IsFeaturette)]
public bool IsFeaturette { get; set; }
[DisplayName(Translations.Education.AddFeaturetteFor)]
[CusomValidatorForFeaturettes(IsFeaturette)]
public string[] Featurettes { get; set; }
custom annotation:
public class CusomValidatorForFeaturettes: ValidationAttribute
{
private readonly bool _IsFeatturette;
public CusomValidatorForFeaturettes(bool isFeatturette): base("NOT OK")
{
_IsFeatturette = isFeatturette;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null && _IsFeatturette )
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
return ValidationResult.Success;
}
}
Basiclly if isfeature is true then Featurettes MUST have a value!
Error im getting:
An object reference is required for the non-static field, method, or
property 'EducationViewModel.IsFeaturette'
I can not make this property static cuz that would give me problems since this property is set with enityframework and I don't wish to change any of this. How can I accomplish this without making the property static?
Attributes are added to the metadata of the assembly at compile-time, and therefore its parameters must be known at compile-time. The error is generated because the your passing the value of a property (bool IsFeaturette) to the attribute which is not static (it could be true or false at run-time).
Instead, pass a string indicating the name of the property to compare, and in the method, use reflection to get the value of the property.
public bool IsFeaturette { get; set; }
[CusomValidatorForFeaturettes("IsFeaturette")]
public string[] Featurettes { get; set; }
and modify the validation attribute to
public class CusomValidatorForFeaturettes: ValidationAttribute
{
private readonly string _OtherProperty;
public CusomValidatorForFeaturettes(string otherProperty): base("NOT OK")
{
_OtherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// Get the dependent property
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(_OtherProperty);
// Get the value of the dependent property
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
......
You can then convert otherPropertyValue to bool and do your conditional check.
I also recommend you read The Complete Guide to Validation in ASP.NET-MVC-3 Part-2 to better understand how to implement a validation attribute, including how to implement IClientValidatable so that you get both server and client side validation. I also recommend you rename the method to (say) RequiredIfTrueAttribute to better reflect what it is doing.
Note also that foolproof have a good range of validation attributes for use in MVC.
And as a final note, your current implementation is specific to a dependency on one property (the value of IsFeaturette), which is pointless for a validation attribute - you would have been better off just checking the values in the controller and adding a ModelStateError. The code above means that you can pass in any property name (so long as that property was typeof bool) which is what a validation attribute should allow.
I'd like to use a custom value type as detailed here http://vbcity.com/blogs/jatkinson/archive/2010/01/12/create-custom-types-and-initialize-them-without-the-new-keyword-c-vb-net.aspx]1 in Entity Framework. They underlying type is an enum so I'd like to store the whole thing in the database as an int, the enum's base type. The purpose of the custom value type is for retrieving string values from a dictionary that's mapped to the enum values. When I use the custom value type in EF code first as a property of an entity the database column is not generated. Also, when looking at the model with EF power tools that property does not show up.
Here's my custom value type:
public struct AttachmentType
{
private AttachmentType(AttachmentTypeCode attachmentTypeCode)
{
if (CodesValues.ContainsKey(attachmentTypeCode))
{
_val = attachmentTypeCode;
}
else
{
throw new InvalidEnumArgumentException("This is not a valid attachment type code.");
}
}
public override string ToString()
{
return CodesValues[_val];
}
private AttachmentTypeCode _val;
public static implicit operator AttachmentType(AttachmentTypeCode attachmentTypeCode)
{
return new AttachmentType(attachmentTypeCode);
}
private static readonly Dictionary<AttachmentTypeCode, string> CodesValues = new Dictionary<AttachmentTypeCode, string>()
{
{AttachmentTypeCode.Email, "Electronic Mail Message"},
{AttachmentTypeCode.WordDocument, "Microsoft Word 2007 Document"},
{AttachmentTypeCode.PDF, "Adobe PDF Document"},
};
public enum AttachmentTypeCode
{
Email= 1,
WordDocument= 2,
PDF = 3
}
}
The answer to this problem is to treat the class as a complex type via annotation or Fluent API, add a public property with a getter of the enum type to access the internal private property, and add a custom code-first configuration like the following to map the public property just created (in my case an enum, which EF5 supports) of the complex type to a database field:
modelBuilder.Types<ClassAttachmentTypeIsUsedIn>()
.Configure(ctc => ctc.Property(cust => cust.AttachmentType.EnumProperty)
.HasColumnName("AttachmentType"));
see more here:
http://visualstudiomagazine.com/articles/2014/04/01/making-complex-types-useful.aspx
I want to use the method in this article to implement friendlier ToString() outputs for my enum types. I would like to know how this can be done in Entity Framework's auto generated Enum codes? Would I have to modify the code generation template (if so, can someone kindly give me some guidance since the template is rather complicated), or can someone suggest an alternative method?
Thanks!
You can use your own enum type in your EF model, instead of creating a new enum in the model designer. Here's how:
In the model designer, rght click on surface and select:
Add New -> Enum Type...
In the dialog, just set checkbox:
Reference external type
and enter your type: namespace.MyEnum
Then create columns in your tables to use this type.
Since you're likely going to modify the existing model, make sure there is no confusion between enum type from the model and (external) enum type from your code. Best approach would be to remove the enum type you previously had created in the model and adjust the columns to use the associated enum type from your code.
Now, you can declare your enum type with description attributes as you planned.
You don't need to make workarounds for enums. They're supported in the latest Entity Framework.
To make your enums friendly to your website you can use attributes. Here is sample attribute:
public class EnumDescription : Attribute
{
public string Text { get; private set; }
public EnumDescription(string text)
{
this.Text = text;
}
}
Mark your enums with attribute:
public enum DaylightSavingTime
{
[EnumDescription("Detect automatically")]
Auto = 0,
[EnumDescription("DST always on")]
AlwaysOn = 1,
[EnumDescription("DST always off")]
AlwaysOff = 2
}
Add extensions to enable ToDescription() method:
public static class EnumExtensions
{
public static string ToDescription(this Enum enumeration)
{
Type type = enumeration.GetType();
MemberInfo[] memInfo = type.GetMember(enumeration.ToString());
if (null != memInfo && memInfo.Length > 0)
{
object[] attrs = memInfo[0].GetCustomAttributes(typeof(EnumDescription), false);
if (null != attrs && attrs.Length > 0)
return ((EnumDescription)attrs[0]).Text;
}
return enumeration.ToString();
}
}
Usage:
var blabla = DaylightSavingTime.Auto;
Console.WriteLine(blabla.ToDescription());
Output:
Detect automatically
I'm using Dapper to hit a legacy database and there's whitespace padding on the column.
The solution I've found is to add properties like:
private string _status;
public string status
{
get { return _status; }
set { _status = value.Trim(); }
}
Obviously this is tedious to do on every string member of every model that will interact with this database. According to the MSDN Entry on SET ANSI_PADDING, modifying this to OFF isn't an option either.
As the database is not mine, I'm unable to change it to nvarchar or simiar.
Is there an easy way to force Dapper to trim every string column that it reads from the database?
Take a look at Dapper.Contrib. The Get method can generate a change tracking proxy over each returned instance if it is an interface type.
(From the comments for the Get method)
Returns a single entity by a single id from table "Ts". T must be of interface type.
Id must be marked with [Key] attribute.
Created entity is tracked/intercepted for changes and used by the Update() extension.
You could look to modify the CreateProperty method to implement your Trim logic
One way is to write an extension method and use reflection to trim all string properties on the class. For example:
public static class ObjExt
{
public static void Trim<T>(this T item)
{
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var p in properties)
{
if (p.PropertyType != typeof(string) || !p.CanWrite || !p.CanRead) { continue; }
var value = p.GetValue(item) as string;
p.SetValue(item,value.Trim());
}
}
}