I've been programming asp.net for, oh, a couple of days now. Here's a question I can't even begin to figure out for myself.
I hope it's obvious from the code what I want to accomplish, and I have, but it's not pretty. Furthermore I'd like to use it on whatever table, whatever field, i.e. check the uniqueness of a value against a table and field I specify, passing it all into
the attribute constructor.
publicclassUniqueEmailAttribute:ValidationAttribute{publicUniqueEmailAttribute(){}publicoverrideBooleanIsValid(Object value){//not pretty. todo: do away with this.var db =newCoinDataContext();int c = db.Emails.Count(e => e.Email1== value.ToString());return(Boolean)(c ==0);}}
DataAnnotation attributes will work here, if that's what you want to do. You're on the right track. Now you just need to apply the attribute to the properties you want to test.
I can confirm this - because it works. But it's not pretty. I'd like to call the attribute UniqueAttribute, not UniqueEmailAttribute, passing in the datacontext and table I want to the constructor. Any thought on that?
You cannot pass a DataContext into an attribute constructor, because of the way CLR "serializes" attribute construction into your assembly. It can only be constant values like ints and strings.
You could pass in the type of a DataContext (e.g., typeof(MyDataContext)) along with a table name and have the attribute create the data context as it needs it.
If you're using LINQ to SQL, then you need to pass the table type rather than the table name, and call GetTable(Type) to get the ITable back that represents the table.
Now's the challenging part: you need to construct a query which matches the field. The problem, of course, is that this isn't trivial. You can't just write it like you can above, because you don't know what the table type or field name is going to be ahead
of time. Now you get to learn the underlying way LINQ works.
This is a fair bit of code, and it still needs error handling (bad DataTypeContextType, bad EntityType, bad PropertyName):
using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public class UniqueAttribute : ValidationAttribute {
public UniqueAttribute(Type dataContextType, Type entityType, string propertyName) {
DataContextType = dataContextType;
EntityType = entityType;
PropertyName = propertyName;
}
public Type DataContextType { get; private set; }
public Type EntityType { get; private set; }
public string PropertyName { get; private set; }
public override bool IsValid(object value) {
// Construct the data context
ConstructorInfo constructor = DataContextType.GetConstructor(new Type[0]);
DataContext dataContext = (DataContext)constructor.Invoke(new object[0]);
// Get the table
ITable table = dataContext.GetTable(EntityType);
// Get the property
PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName);
// Our ultimate goal is an expression of:
// "entity => entity.PropertyName == value"
// Expression: "value"
object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType);
ConstantExpression rhs = Expression.Constant(convertedValue);
// Expression: "entity"
ParameterExpression parameter = Expression.Parameter(EntityType, "entity");
// Expression: "entity.PropertyName"
MemberExpression property = Expression.MakeMemberAccess(parameter, propertyInfo);
// Expression: "entity.PropertyName == value"
BinaryExpression equal = Expression.Equal(property, rhs);
// Expression: "entity => entity.PropertyName == value"
LambdaExpression lambda = Expression.Lambda(equal, parameter);
// Instantiate the count method with the right TSource (our entity type)
MethodInfo countMethod = QueryableCountMethod.MakeGenericMethod(EntityType);
// Execute Count() and say "you're valid if you have none matching"
int count = (int)countMethod.Invoke(null, new object[] { table, lambda });
return count == 0;
}
// Gets Queryable.Count<TSource>(IQueryable<TSource>, Expression<Func<TSource, bool>>)
private static MethodInfo QueryableCountMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Count" && m.GetParameters().Length == 2);
}
I hope you have a new appreciation for how much is happening behind the scenes when you type an expression. :)
Very nice! Exactly what I'm looking for, but I had no idea it would require this bulk of code. Appreciation: yes! Thank you. Now, I you don't mind if I post this over at StackOverflow cause I'm running the same question in parallell, and the people there
will probably want to see the solution.
camitz
Member
3 Points
12 Posts
A general validation attribute for checking uniqueness in a linq to sql data context
Jan 09, 2010 10:20 AM|LINK
I've been programming asp.net for, oh, a couple of days now. Here's a question I can't even begin to figure out for myself.
I hope it's obvious from the code what I want to accomplish, and I have, but it's not pretty. Furthermore I'd like to use it on whatever table, whatever field, i.e. check the uniqueness of a value against a table and field I specify, passing it all into the attribute constructor.
</div>data annotation
ali62b
Contributor
4750 Points
690 Posts
Re: A general validation attribute for checking uniqueness in a linq to sql data context
Jan 09, 2010 02:26 PM|LINK
AFAIK you can't use DataAnnotations attributes for this kinds of validations(which need to access database)@BlueCoder
Regards
bradwils
Contributor
5779 Points
691 Posts
Microsoft
Re: A general validation attribute for checking uniqueness in a linq to sql data context
Jan 09, 2010 04:18 PM|LINK
DataAnnotation attributes will work here, if that's what you want to do. You're on the right track. Now you just need to apply the attribute to the properties you want to test.
ali62b
Contributor
4750 Points
690 Posts
Re: A general validation attribute for checking uniqueness in a linq to sql data context
Jan 09, 2010 05:01 PM|LINK
Thanks Brad for correction. I didn't knew we can use DataAnnotations for these kinds of scenarios.
@BlueCoder
Regards
camitz
Member
3 Points
12 Posts
Re: A general validation attribute for checking uniqueness in a linq to sql data context
Jan 09, 2010 05:06 PM|LINK
I can confirm this - because it works. But it's not pretty. I'd like to call the attribute UniqueAttribute, not UniqueEmailAttribute, passing in the datacontext and table I want to the constructor. Any thought on that?
Martin
bradwils
Contributor
5779 Points
691 Posts
Microsoft
Re: A general validation attribute for checking uniqueness in a linq to sql data context
Jan 09, 2010 10:27 PM|LINK
You cannot pass a DataContext into an attribute constructor, because of the way CLR "serializes" attribute construction into your assembly. It can only be constant values like ints and strings.
You could pass in the type of a DataContext (e.g., typeof(MyDataContext)) along with a table name and have the attribute create the data context as it needs it.
camitz
Member
3 Points
12 Posts
Re: A general validation attribute for checking uniqueness in a linq to sql data context
Jan 09, 2010 10:37 PM|LINK
Allright! Almost there:
public class UniqueAttribute : ValidationAttribute
{
Type dataContextType;
string tableName;
public UniqueAttribute(Type dataContextType, string tableName)
{
this.dataContextType = dataContextType;
this.tableName = tableName;
}
public override Boolean IsValid(Object value)
{
var db = ....reflection yadda yadda
int c = db.Emails.Count(e => e.Email1 == value.ToString()); <== WHAT GOES HERE?
return c == 0;
}
}
bradwils
Contributor
5779 Points
691 Posts
Microsoft
Re: A general validation attribute for checking uniqueness in a linq to sql data context
Jan 09, 2010 11:49 PM|LINK
If you're using LINQ to SQL, then you need to pass the table type rather than the table name, and call GetTable(Type) to get the ITable back that represents the table.
Now's the challenging part: you need to construct a query which matches the field. The problem, of course, is that this isn't trivial. You can't just write it like you can above, because you don't know what the table type or field name is going to be ahead of time. Now you get to learn the underlying way LINQ works.
This is a fair bit of code, and it still needs error handling (bad DataTypeContextType, bad EntityType, bad PropertyName):
using System; using System.ComponentModel.DataAnnotations; using System.Data.Linq; using System.Linq; using System.Linq.Expressions; using System.Reflection; public class UniqueAttribute : ValidationAttribute { public UniqueAttribute(Type dataContextType, Type entityType, string propertyName) { DataContextType = dataContextType; EntityType = entityType; PropertyName = propertyName; } public Type DataContextType { get; private set; } public Type EntityType { get; private set; } public string PropertyName { get; private set; } public override bool IsValid(object value) { // Construct the data context ConstructorInfo constructor = DataContextType.GetConstructor(new Type[0]); DataContext dataContext = (DataContext)constructor.Invoke(new object[0]); // Get the table ITable table = dataContext.GetTable(EntityType); // Get the property PropertyInfo propertyInfo = EntityType.GetProperty(PropertyName); // Our ultimate goal is an expression of: // "entity => entity.PropertyName == value" // Expression: "value" object convertedValue = Convert.ChangeType(value, propertyInfo.PropertyType); ConstantExpression rhs = Expression.Constant(convertedValue); // Expression: "entity" ParameterExpression parameter = Expression.Parameter(EntityType, "entity"); // Expression: "entity.PropertyName" MemberExpression property = Expression.MakeMemberAccess(parameter, propertyInfo); // Expression: "entity.PropertyName == value" BinaryExpression equal = Expression.Equal(property, rhs); // Expression: "entity => entity.PropertyName == value" LambdaExpression lambda = Expression.Lambda(equal, parameter); // Instantiate the count method with the right TSource (our entity type) MethodInfo countMethod = QueryableCountMethod.MakeGenericMethod(EntityType); // Execute Count() and say "you're valid if you have none matching" int count = (int)countMethod.Invoke(null, new object[] { table, lambda }); return count == 0; } // Gets Queryable.Count<TSource>(IQueryable<TSource>, Expression<Func<TSource, bool>>) private static MethodInfo QueryableCountMethod = typeof(Queryable).GetMethods().First(m => m.Name == "Count" && m.GetParameters().Length == 2); }I hope you have a new appreciation for how much is happening behind the scenes when you type an expression. :)
camitz
Member
3 Points
12 Posts
Re: A general validation attribute for checking uniqueness in a linq to sql data context
Jan 10, 2010 12:01 AM|LINK
Very nice! Exactly what I'm looking for, but I had no idea it would require this bulk of code. Appreciation: yes! Thank you. Now, I you don't mind if I post this over at StackOverflow cause I'm running the same question in parallell, and the people there will probably want to see the solution.