Below is the code for a simple JsonModelBinder I created for an ASP.NET Core Mvc app. Is there a simple way to recursively validate the model, its properties and the properties of its properties and so on?
JsonModelBinder
public class JsonModelBinder : IModelBinder
{
static readonly JsonSerializerSettings settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
};
public Task BindModelAsync(ModelBindingContext bindingContext)
{
try
{
var json = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).Values;
if (json.Count > 0)
{
var model = JsonConvert.DeserializeObject(json, bindingContext.ModelType, settings);
// TODO: Validate complex model
bindingContext.Result = ModelBindingResult.Success(model);
}
else
{
bindingContext.Result = ModelBindingResult.Success(null);
}
}
catch (JsonException ex)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, ex.Message);
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
Example model
public class Foo {
[Required]
public string Name { get; set; }
public List<Bar> Bars { get; set; }
public Baz Baz { get; set; }
}
public class Bar {
[Required]
public string Name { get; set; }
}
public class Baz {
[Required]
public string Name { get; set; }
}
Controller action
public async Task<IActionResult> Edit(Guid id, [Required, ModelBinder(typeof(JsonModelBinder))] Foo foo) {
if (ModelState.IsValid) {
// Do stuff
}
else {
return View(foo);
}
}
I created the following recursive data annotations validator, and used it like below. Based on some feedback I received, it appears that there is nothing like this built into .NET Core at this time.
Recursive data annotations validator:
public static class RecursiveValidator
{
/// <summary>
/// Recursively validates <paramref name="instance"/>.
/// </summary>
/// <param name="instance"></param>
/// <param name="prefix"></param>
/// <param name="validationContext"></param>
/// <param name="results"></param>
/// <param name="validateAllProperties"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"><paramref name="instance"/> is null</exception>
public static bool TryValidateObject(object instance, IServiceProvider serviceProvider, IDictionary<object, object> items, ICollection<ValidationResult> results, bool validateAllProperties, string prefix)
{
if (instance is null)
{
throw new ArgumentNullException(nameof(instance));
}
var tempResults = new List<ValidationResult>();
ValidationContext validationContext = new ValidationContext(instance, serviceProvider, items);
var isValid = Validator.TryValidateObject(instance, validationContext, tempResults, validateAllProperties: validateAllProperties);
foreach (var item in tempResults)
{
IEnumerable<string> memberNames = item.MemberNames.Select(name => (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + name);
results.Add(new ValidationResult(item.ErrorMessage, memberNames));
}
foreach (var prop in instance.GetType().GetProperties())
{
if (prop.GetSetMethod() == null)
{
continue;
}
else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))
{
var value = prop.GetValue(instance);
if (value == null)
{
continue;
}
else if (value is IEnumerable<object> list)
{
var memberPrefix = (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + prop.Name;
int i = 0;
foreach (var item in list)
{
if (!TryValidateObject(item, serviceProvider, items, results, validateAllProperties: validateAllProperties, prefix: $"{memberPrefix}[{i}]"))
{
isValid = false;
}
i++;
}
}
else
{
var memberPrefix = (!string.IsNullOrEmpty(prefix) ? prefix + "." : "") + prop.Name;
if (!TryValidateObject(value, serviceProvider, items, results, validateAllProperties: validateAllProperties, prefix: memberPrefix))
{
isValid = false;
}
}
}
}
return isValid;
}
}
Example of using it to add model state errors to the bindingContext:
var validationResults = new List<ValidationResult>();
if (!RecursiveValidator.TryValidateObject(model, bindingContext.HttpContext.RequestServices, null, validationResults, validateAllProperties: true, prefix: bindingContext.ModelName))
{
foreach (var result in validationResults)
{
foreach (var member in result.MemberNames)
{
bindingContext.ModelState.AddModelError(member, result.ErrorMessage);
}
}
}
Related
I have this code:
wordGrid.BindingContext = AS.phrase;
AS.phrase = new PSCViewModel() { English = "abcd" };
AS.phrase.English = "JJJJ";
With the setting of BindingContext on the first line I don't see anything in my view. With it after it works and I see "JJJJ".
Here is my viewModel:
public class PSCViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int id;
string english;
public PSCViewModel()
{
}
public int Id
{
get { return id; }
set
{
if (value != id)
{
id = value;
onPropertyChanged("ID");
}
}
}
public string English
{
get { return english; }
set
{
if (value != english)
{
english = value;
onPropertyChanged("English");
}
}
}
private void onPropertyChanged(string v)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(v));
}
}
}
Can anyone see why the change to the English field would not cause the new value of JJJJ to be displayed?
this is how I implemented INotifyPropertyChanged.
public class Bindable : INotifyPropertyChanged
{
private Dictionary<string, object> _properties = new Dictionary<string, object>();
/// <summary>
/// Gets the value of a property
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
protected T Get<T>([CallerMemberName] string name = null)
{
object value = null;
if (_properties.TryGetValue(name, out value))
return value == null ? default(T) : (T)value;
return default(T);
}
/// <summary>
/// Sets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="name"></param>
protected void Set<T>(T value, [CallerMemberName] string name = null)
{
if (Equals(value, Get<T>(name)))
return;
_properties[name] = value;
OnPropertyChanged(name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is a sample class describing how to use
public class Transaction : Bindable
{
public Transaction()
{
this.TransactionDate = DateTimeOffset.Now;
this.TransactionType = TransactionType.Add; //enum
this.Quantity = 0;
this.IsDeleted = false;
this.Item = null; //object defined elsewhere
}
public Guid Id { get { return Get<Guid>(); } private set { Set<Guid>(value); } }
public DateTimeOffset? TransactionDate { get { return Get<DateTimeOffset?>(); } set { Set<DateTimeOffset?>(value); } }
public TransactionType TransactionType { get { return Get<TransactionType>(); } set { Set<TransactionType>(value); } }
public double? Quantity { get { return Get<double?>(); } set { Set<double?>(value); } }
public bool? IsDeleted { get { return Get<bool?>(); } set { Set<bool?>(value); } }
public byte[] RowVersion { get { return Get<byte[]>(); } private set { Set<byte[]>(value); } }
public virtual Guid? ItemId { get { return Get<Guid?>(); } set { Set<Guid?>(value); } }
public virtual Item Item { get { return Get<Item>(); } set { Set<Item>(value); } }
}
You probably already found the definition in System.ComponentModel
It's all part of MVVM. Your ViewModel must implement INotifyPropertyChanged. There is only one event in it: PropertyChangedEventHandler PropertyChanged
I usually define a raise method in the ViewModel like this:
protected void RaisePropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then all the properties in the ViewModel must have they getter/setter like this:
public string AProperty
{
get { return aProperty;}
set
{
if(value != aProperty)
{
aProperty = value;
RaisePropertyChanged("AProperty");
}
}
}
Now, when you bind your View with the ViewModel, it will subscribe to PropertyChanged event an propagate the changes. That's it !
A good start would be to read up on the MVVM pattern and how to implement it in Xamarin Forms. Xamarin has their own tutorials on the topic such as this one:
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/
Basically what you do is create a ViewModel which acts as the BindingContext for the entire page. Within that ViewModel you define properties that are bound to your controls such as Labels, ListViews and TextBoxes. In your case the ViewModel would contain a string property called Phrase that is bound to the control called wordGrid.
public class PhraseViewModel
{
public string Phrase {get; set;}
}
Which can be bound in XAML to e.g. a Label like:
<Label Text="{Binding Phrase}" />
I'm running ASP WebAPI 2 and successfully installed Swashbuckle. I am trying to figure out how one defines what the default schema values are?
For example, on the Swagger live demo site they changed the default value of pet to "doggie". They also defined the allowable values for status. (Live Demo)
I managed to get this working by following what's on this link:
https://github.com/domaindrivendev/Swashbuckle/issues/69#issuecomment-53953785
In short this is what needs to be done:
Create the classes SwaggerDefaultValue and AddDefaultValues as described in the link. Some changes that I did:
public class SwaggerDefaultValue : Attribute
{
public string Name { get; set; }
public string Value { get; set; }
public SwaggerDefaultValue(string value)
{
this.Value = value;
}
public SwaggerDefaultValue(string name, string value) : this(value)
{
this.Name = name;
}
}
public class AddDefaultValues : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
private IDictionary<string, object> GetParameterValuePairs(HttpActionDescriptor actionDescriptor)
{
IDictionary<string, object> parameterValuePairs = new Dictionary<string, object>();
foreach (SwaggerDefaultValue defaultValue in actionDescriptor.GetCustomAttributes<SwaggerDefaultValue>())
{
parameterValuePairs.Add(defaultValue.Name, defaultValue.Value);
}
foreach (var parameter in actionDescriptor.GetParameters())
{
if (!parameter.ParameterType.IsPrimitive)
{
foreach (PropertyInfo property in parameter.ParameterType.GetProperties())
{
var defaultValue = GetDefaultValue(property);
if (defaultValue != null)
{
parameterValuePairs.Add(property.Name, defaultValue);
}
}
}
}
return parameterValuePairs;
}
private static object GetDefaultValue(PropertyInfo property)
{
var customAttribute = property.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
if (customAttribute != null)
{
return customAttribute.Value;
}
return null;
}
}
Edit your SwaggerConfig and add the AddDefaultValues class to the OperationFilters:
GlobalConfiguration.Configuration
.EnableSwagger(c => {
...
c.OperationFilter<AddDefaultValues>()
...
});
Now for the parameters I want default values I just add the following:
public IHttpActionResult Put([FromBody]Pet pet)
{
...
return Ok();
}
public class Pet {
[SwaggerDefaultValue("doggie")]
public string Name { get; set; }
[SwaggerDefaultValue("available")]
public string Status;
...
}
Well the code of vgaspar.trivix did not work completly for me, the default values did not get set for the schema. Also i got an NullPointerException. I managed to get it working as intended by editing the Apply method and manipulated the schemaRegistry like this:
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters == null)
return;
IDictionary<string, object> parameterValuePairs =
GetParameterValuePairs(apiDescription.ActionDescriptor);
foreach (var param in operation.parameters)
{
if (param.schema != null && param.schema.#ref != null)
{
string schemaName = param.schema.#ref.Split('/').LastOrDefault();
if (schemaRegistry.Definitions.ContainsKey(schemaName))
foreach (var props in schemaRegistry.Definitions[schemaName].properties)
{
if (parameterValuePairs.ContainsKey(props.Key))
props.Value.#default = parameterValuePairs[props.Key];
}
}
var parameterValuePair = parameterValuePairs.FirstOrDefault(p => p.Key.IndexOf(param.name, StringComparison.InvariantCultureIgnoreCase) >= 0);
param.#default = parameterValuePair.Value;
}
}
An example Model Schema can be defined by implementing ISchemaFilter and registering it using the following:
httpConfig
.EnableSwagger(c =>
{
c.SchemaFilter<AddSchemaExamples>()
});
An example implementation is provided here:
public class AddSchemaExamples : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (type == typeof(Product))
{
schema.example = new Product
{
Id = 123,
Type = ProductType.Book,
Description = "Treasure Island",
UnitPrice = 10.0M
};
}
}
}
Source: https://github.com/domaindrivendev/Swashbuckle/issues/162
I know this thread is quite old, but I wanted to share my solution which creates a custom constructor just for the Swagger example schema.
In my model:
/// <summary>
/// Supply a custom constructor for Swagger where you can apply defaults to control the example schema.
/// The constructor must have one parameter of type System.Reflection.ParameterInfo[].
/// Note: Setting a property to null will prevent it from showing in the Swagger example.
/// </summary>System.Reflection.ParameterInfo[].
/// </summary>
public class SwaggerConstructor : Attribute { }
In SwaggerConfig.cs:
c.SchemaFilter<ApplySchemaVendorExtensions>();
The schema extension:
public class ApplySchemaVendorExtensions : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
ConstructorInfo constructor = type.GetConstructors().FirstOrDefault(c => c.GetCustomAttribute<SwaggerConstructor>() != null);
if (constructor != null)
{
schema.example = constructor.Invoke(new object[] { constructor.GetParameters() });
}
}
}
Usage:
[SwaggerConstructor]
public MyClass(System.Reflection.ParameterInfo[] decoy) : base()
{
MyProperty = false;
}
Stumbled across this just now, you can also set the tag in the XML documentation, in one of my models, I have this defined
/// <summary>
/// Note content
/// </summary>
/// <example>Any text for a note.</example>
public string Note { get; set; }
which ends up looking like this in the swagger documentation when selecting "Try It Now"
Hope that helps someone!
Using .NET 5 with Swashbuckle.AspNetCore 5.6.3, the only way I could get this to work efficiently is this:
public class ExampleDocFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
string ToCamelCase(string name) => char.ToLowerInvariant(name[0]) + name.Substring(1);
if (schema.Properties == null) return;
var setProperties = context.Type.GetProperties().ToList().Where(f => f.GetCustomAttribute<DefaultValueAttribute>() != null).Where(f => schema.Properties.Any(n => n.Key.Equals(ToCamelCase(f.Name)))).ToDictionary(f => f, f => f.GetCustomAttribute<DefaultValueAttribute>());
foreach (var prop in setProperties) schema.Properties[ToCamelCase(prop.Key.Name)].Example = OpenApiAnyFactory.CreateFor(schema.Properties[ToCamelCase(prop.Key.Name)], prop.Value.Value);
}
}
To use this - in your startup.cs:
services.AddSwaggerGen(swagger => {
...
swagger.SchemaFilter<ExampleDocFilter>();
});
hi i'm googling around about this problem but i didn't find any usfull about this.
I want to deni the set of an property if the Validation per DataAnnotations fails
Could you please tell me what i miss in my code?
Model Codesnip
private string _firstname;
public string Firstname
{
get { return _firstname; }
set
{
_firstname = value;
RaisePropertyChanged(() => Reg(() => Firstname));
}
}
ViewModel Codesnip
[Required]
[RegularExpression(#"^[a-zA-ZäöüßÄÖÜß''-'\s]{2,40}$")]
public string Name
{
get { return currentperson.Name; }
set
{
currentperson.Name = value;
RaisePropertyChanged(() => Reg(() => Name));
}
}
View Codesnip
<TextBox HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" Text="{Binding Firstname,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
any help would be greatly appreciated
EDIT: adding Validation Class
public class VMValidation : VMBase, IDataErrorInfo
{
private Dictionary<string, string> ErrorList = new Dictionary<string, string>();
/// <summary>
/// Gets a value indicating whether or not this domain object is valid.
/// </summary>
public bool IsVailid
{
get { return (ErrorList.Count == 0) ? true : false; }
}
#region IDataErrorInfo
/// <summary>
/// Gets an error message indicating what is wrong with this domain object.
/// The default is an empty string ("").
/// </summary>
public string Error
{
get { return getErrors(); } // noch keine richtige methode für gefunden müsste so aber auch funktionieren
}
/// <summary>
/// dies ist eine methode die durch einen Event (noch unbekannt welcher)
/// ausgelöst wird und den Propertynamen schon mit übergeben bekommt
/// </summary>
/// <param name="propertyName">Name der Property z.B FirstName</param>
/// <returns>The error message for the property.
/// The default is an empty string ("").</returns>
public string this[string propertyName]
{
get { return OnValidate(propertyName); }
}
private string getErrors()
{
string Error = "";
foreach (KeyValuePair<string, string> error in ErrorList)
{
Error += error.Value;
Error += Environment.NewLine;
}
return Error;
}
/// <summary>
/// Validates current instance properties using Data Annotations.
/// </summary>
/// <param name="propertyName">Name der Property</param>
/// <returns>ErrorMsg</returns>
protected virtual string OnValidate(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
throw new ArgumentException("Invalid property name", propertyName);
string error = string.Empty;
var value = this.GetType().GetProperty(propertyName).GetValue(this, null);
var results = new List<ValidationResult>(1);
var context = new ValidationContext(this, null, null) { MemberName = propertyName };
var result = Validator.TryValidateProperty(value, context, results);
if (!result)
{
var validationResult = results.First();
error = validationResult.ErrorMessage;
}
if (error.Length > 0)
{
if (!ErrorList.ContainsKey(propertyName))
ErrorList.Add(propertyName, error);
}
else
if (!ErrorList.ContainsKey(propertyName))
ErrorList.Remove(propertyName);
return error;
}
public bool VailidValue(string propertyName, object value)
{
var results = new List<ValidationResult>(1);
var context = new ValidationContext(this, null, null) { MemberName = propertyName };
var result = Validator.TryValidateProperty(value, context, results);
return result;
}
#endregion //IDataErrorInfo
}
Try creating a validation method, and only setting the value if the result is true.
public string Name
{
get { return currentperson.Name; }
set
{
if (ValidateProperty("Name", value))
{
currentperson.Name = value;
RaisePropertyChanged(() => Reg(() => Name));
}
}
}
protected bool ValidateProperty(string propertyName, object value)
{
var results = new List<ValidationResult>();
bool isValid = Validator.TryValidateProperty(
value,
new ValidationContext(this, null)
{
MemberName = propertyName
},
results);
// Do what you want with the validation results
return isValid;
}
i changed my VMValidation
public class VMValidation : VMBase, IDataErrorInfo
{
private Dictionary<string, string> ErrorList = new Dictionary<string, string>();
public bool IsVailid
{
get { return (ErrorList.Count == 0) ? true : false; }
}
#region IDataErrorInfo
public string Error
{
get { return getErrors(); } // noch keine richtige methode für gefunden müsste so aber auch funktionieren
}
public string this[string propertyName]
{
get { return OnValidate(propertyName); }
}
private string getErrors()
{
string Error = "";
foreach (KeyValuePair<string, string> error in ErrorList)
{
Error += error.Value;
Error += Environment.NewLine;
}
return Error;
}
protected virtual string OnValidate(string propertyName)
{
string error =String.Empty;
if (ErrorList.ContainsKey(propertyName))
error = ErrorList[propertyName];
return error;
}
public bool VailidateValue(string propertyName, object value)
{
if (string.IsNullOrEmpty(propertyName))
{
Logger.ERRROR(propertyName, "Invalid property name");
throw new ArgumentException("Invalid property name", propertyName);
}
var results = new List<ValidationResult>(1);
var context = new ValidationContext(this, null, null) { MemberName = propertyName };
var result = Validator.TryValidateProperty(value, context, results);
if (!result)
{
var validationResult = results.First();
string error = validationResult.ErrorMessage;
if (error.Length > 0)
{
if (!ErrorList.ContainsKey(propertyName))
ErrorList.Add(propertyName, error);
}
}
else
if (ErrorList.ContainsKey(propertyName))
ErrorList.Remove(propertyName);
return result;
}
#endregion //IDataErrorInfo
}
and do what Alyce suggested
[Required]
[RegularExpression(#"^[a-zA-ZäöüßÄÖÜß''-'\s]{2,40}$")]
public string Name
{
get
{
if (currentperson == null)
return "";
return currentperson.Name;
}
set
{
if (VailidateValue("Name", value))
{
currentperson.Name = value;
}
RaisePropertyChanged(() => Reg(() => Name)); //you have to Raise because otherwise you won't get the Error :)
}
}
I have windows phone 7 application and classes for database mapping.
A Poll class looks like that:
[Table]
public class Poll // :BaseModel
{
//private int _pid;
//private string _pdesc;
//private bool _pisopen;
//private string _pname;
//private bool _prandom;
//private string _qlastupdticks;
//private string _ticks;
[Column(DbType = "INT NOT NULL IDENTITY(1,1)", IsPrimaryKey = true, IsDbGenerated = true)]
public int id { get; set; }
[Column(DbType = "INT")]
public int pid { get; set; }
//{
// get { return _pid; }
// set { SetValue(ref _pid, value, GetPropertyName(MethodBase.GetCurrentMethod())); }
//}
[Column]
public string pdesc { get; set; }
//{
// get { return _pdesc; }
// set { SetValue(ref _pdesc, value, GetPropertyName(MethodBase.GetCurrentMethod())); }
//}
[Column]
public bool pisopen { get; set; }
//{
// get { return _pisopen; }
// set { SetValue(ref _pisopen, value, GetPropertyName(MethodBase.GetCurrentMethod())); }
//}
[Column]
public string pname { get; set; }
//{
// get { return _pname; }
// set { SetValue(ref _pname, value, GetPropertyName(MethodBase.GetCurrentMethod())); }
//}
[Column]
public bool prandom { get; set; }
//{
// get { return _prandom; }
// set { SetValue(ref _prandom, value, GetPropertyName(MethodBase.GetCurrentMethod())); }
//}
[Column(DbType = "NVARCHAR(255)")]
public string qlastupdticks { get; set; }
//{
// get { return _qlastupdticks; }
// set { SetValue(ref _qlastupdticks, value, GetPropertyName(MethodBase.GetCurrentMethod())); }
//}
[Column(DbType = "NVARCHAR(255)")]
public string ticks { get; set; }
//{
// get { return _ticks; }
// set { SetValue(ref _ticks, value, GetPropertyName(MethodBase.GetCurrentMethod())); }
//}
public override bool Equals(object obj)
{
var item = obj as Poll;
if (item != null)
{
Equals(item);
}
return false;
}
public bool Equals(Poll other)
{
if (ReferenceEquals(null, other)) return false;
return (other.pid == pid);
}
public override string ToString()
{
return string.Format("{0}_{1}", GetType().Name, pid);
}
public override int GetHashCode()
{
return ToString().ToUpper().GetHashCode();
}
}
Save method looks like that:
public bool RowsSave<T>(IEnumerable<T> entities, out string error)
{
error = string.Empty;
bool res;
var type = typeof (T);
using (var ctx = new PpaDataContext(_connectionString))
{
try
{
var entitesInDb = ctx.GetTable(type).Cast<T>().ToList();
var entitesForSave = new List<T>();
foreach (var entity in entities)
{
if (!entitesInDb.Contains(entity))
{
entitesForSave.Add(entity);
}
else
{
var index = entitesInDb.IndexOf(entity);
foreach (var prop in PropertiesGet(entity))
{
prop.SetValue(entitesInDb[index], prop.GetValue(entity, null), null);
}
}
}
if(entitesForSave.Count > 0)
{
ctx.GetTable(type).InsertAllOnSubmit(entitesForSave);
}
ctx.SubmitChanges();
res = true;
}
catch (Exception ex)
{
error = string.Format("{0}", ex.Message);
res = false;
}
}
return res;
}
When I try insert, each object inserts twice. What incorrect in this code?
The RowsSave method calling one time only.
This is a bit after the fact, but I've run into the exact same problem. This has to do with an error in overridden equality operators. In this specific case, the problem appears because the code does not return the value of the Equals function in:
public override bool Equals(object obj)
{
var item = obj as Poll;
if (item != null)
{
Equals(item);
}
return false;
}
That code will always return false. The corrected code should be:
public override bool Equals(object obj)
{
var item = obj as Poll;
if (item != null)
{
return Equals(item);
}
return false;
}
LINQ-to-SQL uses the object equals comparison after insertions. I'm not sure in what context and for what reason this happens, but if the comparison returns false, it runs the insertion again. If you're experiencing the same behavior, check any overridden equality methods for your entity for correctness.
I don't think you need the line:
ctx.SubmitChanges();
as an insert is being done on the line:
ctx.GetTable(type).InsertAllOnSubmit(entitesForSave);
inspired by this answer I'm trying to map a property on a model class to an expression based on the actual entity.
These are the two classes involved:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Id { get; set; }
public DateTime? BirthDate { get; set; }
public int CustomerTypeId { get; set; }
}
public class CustomerModel
{
...
public bool HasEvenId { get; set; }
}
An example of a possible expression I'd like to convert is:
Expression<Func<CustomerModel, bool>> from = model => model.HasEvenId;
Expression<Func<Customer, bool>> to = entity => ((entity.Id % 2) == 0);
The problem is that I have to expose an OData endpoint via ASP.NET WebAPI but I need to make some operations on the entities before I can them, hence the need of a model class and the need to translate the expression based on the model that I could receive as an OData query in an expression based on the entity, that I would use to query EF4.
This is where I got so far:
private static readonly Dictionary<Expression, Expression> Mappings = GetMappings();
private static Dictionary<Expression, Expression> GetMappings()
{
var mappings = new Dictionary<Expression, Expression>();
var mapping = GetMappingFor((CustomerModel model) => model.HasEvenId, (Customer customer) => (customer.Id%2) == 0);
mappings.Add(mapping.Item1, mapping.Item2);
return mappings;
}
private static Tuple<Expression, Expression> GetMappingFor<TFrom, TTo, TValue>(Expression<Func<TFrom, TValue>> fromExpression, Expression<Func<TTo, TValue>> toExpression)
{
MemberExpression fromMemberExpression = (MemberExpression) fromExpression.Body;
return Tuple.Create<Expression, Expression>(fromMemberExpression, toExpression);
}
public static Expression<Func<TTo, bool>> Translate<TFrom, TTo>(Expression<Func<TFrom, bool>> expression, Dictionary<Expression, Expression> mappings = null)
{
if (expression == null)
return null;
string parameterName = expression.Parameters[0].Name;
parameterName = string.IsNullOrWhiteSpace(parameterName) ? "p" : parameterName;
var param = Expression.Parameter(typeof(TTo), parameterName);
var subst = new Dictionary<Expression, Expression> { { expression.Parameters[0], param } };
ParameterChangeVisitor parameterChange = new ParameterChangeVisitor(parameterName);
if (mappings != null)
foreach (var mapp in mappings)
subst.Add(mapp.Key, parameterChange.Visit(mapp.Value));
var visitor = new TypeChangeVisitor(typeof(TFrom), typeof(TTo), subst);
return Expression.Lambda<Func<TTo, bool>>(visitor.Visit(expression.Body), param);
}
public IQueryable<CustomerModel> Get()
{
var filterExtractor = new ODataFilterExtractor<CustomerModel>();
Expression<Func<CustomerModel, bool>> expression = filterExtractor.Extract(Request);
Expression<Func<Customer, bool>> translatedExpression = Translate<CustomerModel, Customer>(expression, Mappings);
IQueryable<Customer> query = _context.Customers;
if (translatedExpression != null)
query = query.Where(translatedExpression);
var finalQuery = from item in query.AsEnumerable()
select new CustomerModel()
{
FirstName = item.FirstName,
LastName = item.LastName,
Id = item.Id,
BirthDate = item.BirthDate,
CustomerTypeId = item.CustomerTypeId,
HasEvenId = (item.Id % 2 ) == 0
};
return finalQuery.AsQueryable();
}
where:
ODataFilterExtractor is a class that extract the $filter expression from the RequestMessage we receive;
ParameterChangeVisitor just changes all the ParameterExpression to a new one having the provided string as parameter name;
In addition, I changed the VisitMember method of the answer linked above in this way:
protected override Expression VisitMember(MemberExpression node)
{
// if we see x.Name on the old type, substitute for new type
if (node.Member.DeclaringType == _from)
{
MemberInfo toMember = _to.GetMember(node.Member.Name, node.Member.MemberType, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SingleOrDefault();
if (toMember != null)
{
return Expression.MakeMemberAccess(Visit(node.Expression), toMember);
}
else
{
if (_substitutions.Select(kvp => kvp.Key).OfType<MemberExpression>().Any(me => me.Member.Equals(node.Member)))
{
MemberExpression key = _substitutions.Select(kvp => kvp.Key).OfType<MemberExpression>().Single(me => me.Member.Equals(node.Member));
Expression value = _substitutions[key];
// What to return here?
return Expression.Invoke(value);
}
}
}
return base.VisitMember(node);
}
Thanks for you help.
I took the liberty of modifying your code just a hair but this does the trick,
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Id { get; set; }
public DateTime? BirthDate { get; set; }
public int CustomerTypeId { get; set; }
}
public class CustomerModel
{
public string FullName { get; set; }
public bool HasEvenId { get; set; }
}
sealed class AToBConverter<TA, TB> : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> _parameters = new Dictionary<ParameterExpression, ParameterExpression>();
private readonly Dictionary<MemberInfo, LambdaExpression> _mappings;
protected override Expression VisitParameter(ParameterExpression node)
{
if (node.Type == typeof(TA))
{
ParameterExpression parameter;
if (!this._parameters.TryGetValue(node, out parameter))
{
this._parameters.Add(node, parameter = Expression.Parameter(typeof(TB), node.Name));
}
return parameter;
}
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression == null || node.Expression.Type != typeof(TA))
{
return base.VisitMember(node);
}
Expression expression = this.Visit(node.Expression);
if (expression.Type != typeof(TB))
{
throw new Exception("Whoops");
}
LambdaExpression lambdaExpression;
if (this._mappings.TryGetValue(node.Member, out lambdaExpression))
{
return new SimpleExpressionReplacer(lambdaExpression.Parameters.Single(), expression).Visit(lambdaExpression.Body);
}
return Expression.Property(
expression,
node.Member.Name
);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(
this.Visit(node.Body),
node.Parameters.Select(this.Visit).Cast<ParameterExpression>()
);
}
public AToBConverter(Dictionary<MemberInfo, LambdaExpression> mappings)
{
this._mappings = mappings;
}
}
sealed class SimpleExpressionReplacer : ExpressionVisitor
{
private readonly Expression _replacement;
private readonly Expression _toFind;
public override Expression Visit(Expression node)
{
return node == this._toFind ? this._replacement : base.Visit(node);
}
public SimpleExpressionReplacer(Expression toFind, Expression replacement)
{
this._toFind = toFind;
this._replacement = replacement;
}
}
class Program
{
private static Dictionary<MemberInfo, LambdaExpression> GetMappings()
{
var mappings = new Dictionary<MemberInfo, LambdaExpression>();
var mapping = GetMappingFor(model => model.HasEvenId, customer => (customer.Id % 2) == 0);
mappings.Add(mapping.Item1, mapping.Item2);
mapping = GetMappingFor(model => model.FullName, customer => customer.FirstName + " " + customer.LastName);
mappings.Add(mapping.Item1, mapping.Item2);
return mappings;
}
private static Tuple<MemberInfo, LambdaExpression> GetMappingFor<TValue>(Expression<Func<CustomerModel, TValue>> fromExpression, Expression<Func<Customer, TValue>> toExpression)
{
return Tuple.Create(((MemberExpression)fromExpression.Body).Member, (LambdaExpression)toExpression);
}
static void Main()
{
Expression<Func<CustomerModel, bool>> source = model => model.HasEvenId && model.FullName == "John Smith";
Expression<Func<Customer, bool>> desiredResult = model => (model.Id % 2) == 0 && (model.FirstName + " " + model.LastName) == "John Smith";
Expression output = new AToBConverter<CustomerModel, Customer>(GetMappings()).Visit(source);
Console.WriteLine("The two expressions do {0}match.", desiredResult.ToString() == output.ToString() ? null : "not ");
Console.ReadLine();
}
}
Another solution would be to use AutoMapper to map complex types and modify the resulting expression query with an ExpressionTransformer before it gets executed. I will try to explain with a complete sample:
Model classes
Some POCO classes just for holding data.
public enum CostUnitType
{
None = 0,
StockUnit = 1,
MachineUnit = 2,
MaintenanceUnit = 3
}
public class CostUnit
{
public string CostUnitId { get; set; }
public string WorkplaceId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public CostUnitType Type { get; set; }
public override string ToString()
{
return CostUnitId + " " + Name + " " + Type;
}
}
public class StockUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
public class MachineUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
public class MaintenanceUnit
{
public string Id { get; set; }
public string Name { get; set; }
}
The ExpressionTransformer class
Most of the time, using the Mapper static class is fine, but sometimes you need to map the same types with different configurations, so you need to explicitly use an IMappingEngine like this:
var configuration = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
var engine = new MappingEngine(configuration);
The way to create a MappingEngine is not obvious at all. I had to dig in the source code to find how it was done.
public static class ExpressionTransformer
{
private static readonly MappingEngine Mapper;
/// <summary>
/// Initializes the <see cref="ExpressionTransformer"/> class.
/// Creates an instance of AutoMapper. Initializes mappings.
/// </summary>
static ExpressionTransformer()
{
ConfigurationStore configurationStore = new ConfigurationStore(new TypeMapFactory(), MapperRegistry.Mappers);
// Create mapping
// Maps Id from StockUnit to CostUnitId from CostUnit
configurationStore.CreateMap<StockUnit, CostUnit>()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Maps Id from MachineUnit to CostUnitId from CostUnit
configurationStore.CreateMap<MachineUnit, CostUnit>()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Maps Id from MaintenanceUnit to CostUnitId from CostUnit
configurationStore.CreateMap<MaintenanceUnit, CostUnit>()
.ForMember(m => m.CostUnitId, opt => opt.MapFrom(src => src.Id));
// Create instance of AutoMapper
Mapper = new MappingEngine(configurationStore);
}
public static Expression<Func<TDestination, bool>> Tranform<TSource, TDestination>(Expression<Func<TSource, bool>> sourceExpression)
{
// Resolve mappings by AutoMapper for given types.
var map = Mapper.ConfigurationProvider.FindTypeMapFor(typeof(TSource), typeof(TDestination));
if (map == null)
{
throw new AutoMapperMappingException(string.Format("No Mapping found for {0} --> {1}.", typeof(TSource).Name, typeof(TDestination).Name));
}
// Transform from TSource to TDestination with specified mappings
var visitor = new ParameterTypeVisitor<TSource, TDestination>(sourceExpression, map.GetPropertyMaps());
var expression = visitor.Transform();
return expression;
}
private class ParameterTypeVisitor<TSource, TDestination> : ExpressionVisitor
{
private readonly Dictionary<string, ParameterExpression> _parameters;
private readonly Expression<Func<TSource, bool>> _expression;
private readonly IEnumerable<PropertyMap> _maps;
public ParameterTypeVisitor(Expression<Func<TSource, bool>> expression, IEnumerable<PropertyMap> maps)
{
_parameters = expression.Parameters
.ToDictionary(p => p.Name, p => Expression.Parameter(typeof(TDestination), p.Name));
_expression = expression;
_maps = maps;
}
public Expression<Func<TDestination, bool>> Transform()
{
return (Expression<Func<TDestination, bool>>) Visit(_expression);
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Member.DeclaringType == typeof(TSource))
{
var memberName = node.Member.Name;
var member = _maps.FirstOrDefault(p => typeof(TSource) == node.Expression.Type
&& p.SourceMember.Name == memberName);
if (member != null)
{
// Return Property from TDestination
var expression = Visit(node.Expression);
return Expression.MakeMemberAccess(expression, member.DestinationProperty.MemberInfo);
}
}
return base.VisitMember(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
var parameter = _parameters[node.Name];
return parameter;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
var expression = Visit(node.Body);
return Expression.Lambda(expression, _parameters.Select(x => x.Value));
}
}
}
Usage
To Convert an expression we just need to call Transform Method of ExpressionTransformer class
Expression<Func<StockUnit, bool>> stockQuery = unit => unit.Id == "0815" && unit.Name == "ABC";
// Call Transform<TSource, TDestination> method.
Expression<Func<CostUnit, bool>> costUnitQuery = ExpressionTransformer.Tranform<StockUnit, CostUnit>(stockQuery);
Result