I am looking for a way to trim my leading and trailing spaces inside my af:inputListOfValues components. I can access the View Criteria programmatically, and trim all my values from VOImpl custom class, but I am not sure how to expose it to the actual component. Is the a way to expose my custom methods to the LOV component? Thank you in advance!
In order to trim the values inside my LOV component I had to override executeQueryForCollection. This way I was be able to access my parameters and trim them.
#Override
protected void executeQueryForCollection(Object qc, Object[] params, int noUserParams) {
ArrayList<Object[]> alParams = new ArrayList<Object[]>();
//Pass along any explicit (user entered) parameters for the query. Also some implicit parameters.
if(params != null && params.length > 0){
for (Object o : params) {
alParams.add((Object[])o);
}
//Access the value of each object and trim it
for (Object[] p: alParams){
if(p.length > 1){
p[1] = trimCriteria(p[1]);
}
}
Object[] trimParams = alParams.toArray();
super.executeQueryForCollection(qc, trimParams, noUserParams);
} else {
super.executeQueryForCollection(qc, params, noUserParams);
}
}
public Object trimCriteria (Object searchCriteria){
if(searchCriteria instanceof String)
if(searchCriteria != null){
searchCriteria = ((String)searchCriteria).trim();
}
return searchCriteria;
}`
Related
I have 3 Oracle databases; production, test, development. For the most part, they are all identical. In my application, I would like the changes to be applied to multiple databases. For example:
using (var context = new Context())
{
context.People.Add(new Person { name = "sean" });
context.SaveChanges();
}
I then tried to override the SaveChanges method and save to multiple databases by doing this:
public void SaveChanges(int auditPersonNumber)
{
OracleCredentials.Default.Server = "VDev";
base.SaveChanges();
OracleCredentials.Default.Server = "VTest";
base.SaveChanges();
OracleCredentials.Default.Server = "VProd";
base.SaveChanges();
}
This didn't work but should explain what I am trying to achieve.
I haven't yet used EntityFramework against an Oracle database, but it should be similar to connecting against SQL Server in that the database name is specified via a ConnectionString. Your project should have a config file (web.config, app.config, or if it's a .NET Core application it could be in appsettings.json) with that ConnectionString in it.
For example:
<add name="YourConnectionString" providerName="YourOracleProviderName" connectionString="User Id=test;Password=testpassword;Data Source=eftest" />
The DbContext base constructor accepts a string argument that specifies which ConnectionString it should use, and thus which database to connect to. If you look into your context class, the default constructor should call the base constructor with that argument.
public YourDbContext() : base("YourConnectionString") {}
In order to save to multiple databases you will need to work against different instances of DbContext each with a different ConnectionString argument. So, your config will need to list a few different connection strings for every Db and you'll probably want your DbContext class to allow the argument in its constructor as well.
Perhaps the SaveChanges method implementation could instantiate the other DbContexts you'd need to use:
public void SaveChanges(int auditPersonNumber)
{
using (var context = new Context("OtherConnectionString1"))
{
// apply same changes
context.SaveChanges();
}
using (var context = new Context("OtherConnectionString2"))
{
// apply same changes
context.SaveChanges();
}
base.SaveChanges();
}
As for the applying the same changes, I would expect you can read them out from the DbContext ChangeTracker. There's an explanation about that using EF Core here but in earlier versions it's similar: http://www.entityframeworktutorial.net/efcore/changetracker-in-ef-core.aspx
Also keep in mind that the SaveChanges call to OtherConnectionString1 could succeed while others could fail, so the data might be inconsistent in your different databases. You may have to look into using transactions across multiple databases but I haven't done this yet myself.
I was able to figure out a solution thanks to the help of Sangman.
public class Context : Shared.Data.Context
{
new public void SaveChanges(int auditPersonNumber)
{
var errors = string.Empty;
var testConnectionString = "ConnectionString";
var developmentConnectionString = "ConnectionString";
//Save to test database
if (SecurityMaintenanceUser.ApplyToTest)
errors = ApplyToDatabase(testConnectionString, auditPersonNumber, "Test");
if (!string.IsNullOrWhiteSpace(errors))
errors += "\n\n";
//Save to development database
if (SecurityMaintenanceUser.ApplyToDevelopment)
errors += ApplyToDatabase(developmentConnectionString, auditPersonNumber, "Development");
if (!string.IsNullOrWhiteSpace(errors))
MessageBox.Show(errors, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
//Save to production database
base.SaveChanges(auditPersonNumber);
}
private string ApplyToDatabase(string connectionString, int auditPersonNumber, string server)
{
try
{
using (var context = new Context(connectionString))
{
context.Configuration.ValidateOnSaveEnabled = false;
foreach (var entry in ChangeTracker.Entries())
{
var dataSet = context.Set(entry.Entity.GetType());
if (entry.State == EntityState.Added)
{
dataSet.Add(entry.Entity);
}
else if (entry.State == EntityState.Deleted)
{
var contextEntity = dataSet.Find(GetPrimaryKeyValues(entry));
context.DeleteEntity(contextEntity, auditPersonNumber);
}
else if (entry.State == EntityState.Modified)
{
var contextEntity = dataSet.Find(GetPrimaryKeyValues(entry));
context.Entry(CopyProperties(entry.Entity, contextEntity)).State = EntityState.Modified;
}
}
context.SaveChanges(auditPersonNumber);
return string.Empty;
}
}
catch (Exception e)
{
return $"Failed to apply database changes to {server}.\n{e.GetFullMessage()}";
}
}
private object CopyProperties(object source, object destination)
{
if (source == null || destination == null)
throw new Exception("Source or/and Destination Objects are null");
var typeDest = destination.GetType();
var typeSrc = source.GetType();
foreach (var srcProp in typeSrc.GetProperties())
{
if (srcProp.Name == "Type" || srcProp.Name == "AuthenticationLog")
continue;
//This blocks any complex objects attached to the entity, will need to be changed for your application
if (srcProp.PropertyType.FullName.Contains("Library.Shared"))
continue;
if (!srcProp.CanRead)
continue;
var targetProperty = typeDest.GetProperty(srcProp.Name);
if (targetProperty == null)
continue;
if (!targetProperty.CanWrite)
continue;
if (targetProperty.GetSetMethod(true)?.IsPrivate == true)
continue;
if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
continue;
if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
continue;
targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
}
return destination;
}
private object GetPrimaryKeyValues(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
return objectStateEntry.EntityKey.EntityKeyValues[0].Value;
}
public static string GetFullMessage(this Exception ex)
{
return ex.InnerException == null ? ex.Message : $"{ex.Message}\n{ex.InnerException.GetFullMessage()}";
}
public static string Replace(this string source, string oldString, string newString, StringComparison comp)
{
int index = source.IndexOf(oldString, comp);
if (index >= 0)
{
source = source.Remove(index, oldString.Length);
source = source.Insert(index, newString);
}
if (source.IndexOf(oldString, comp) != -1)
source = Replace(source, oldString, newString, comp);
return source;
}
}
first: we use infragistics xamdatagrid 11.1.20111.2053
our problem:
We use the grid with generic lists. So it's very dynamic and must be prepared for any situation. We set for each field type theSortComparer, FilterComparer, the editor type, Edita type and style editor.
For some properties of a model, we use special TypeConverter.
For example, in a cell, some values ??can not be displayed.
0 = string.Empty
1 = 1
2 = 2
first solution, we only use the type converter and a special sort comparer:
public class HideZeroIntEntryConverter : Int32Converter
{
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
if (value is int) {
if (destinationType == typeof(string)) {
return ((int)value != 0) ? value.ToString() : string.Empty;
}
return ((int)value != 0) ? value : Binding.DoNothing; // this is the best solution to tell the grid the cell is empty
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
this works perfect if we not decide to filter, but if we want to filter the values we see the ugly "Binding.DoNothing" in the filter drop down items (the sorting and filtering is also wrong).
also, we can not filter for "0" because we the converter says string.empty...
second solution, we use a special XamTextEditor:
public class HideZeroIntEntryTextEditor : XamTextEditor
{
public HideZeroIntEntryTextEditor() {
this.ValueToDisplayTextConverter = new HideZeroIntEntryValueConverter();
}
}
public class HideZeroIntEntryValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is int) {
if (targetType == typeof(string)) {
return ((int)value != 0) ? value.ToString() : string.Empty;
}
// this never happens
return ((int)value != 0) ? value : Binding.DoNothing;
}
// this never happens
return targetType == typeof(string) && value != null ? value.ToString() : value;
}
}
and for the field settings
field.Settings.EditAsType = typeof(int);
field.Converter = null;
field.Settings.EditorType = typeof(HideZeroIntEntryTextEditor);
field.Settings.SortComparer = GenericComparer<int>.Instance;
field.Settings.FilterComparer = GenericComparer<int>.Instance;
field.Settings.GroupByComparer = GroupByRecordComparer<int>.Instance;
Now we can filter to "0", even if this does not appear in the list.
But, in both cases, we can not filter by empty entries, because it actually does not exist!
We want to though!
In our opinion, this could be if we could make our own special filter. But this is unfortunately not so easy.
Yes, we can remove the special filter blanks and NonBlanks, BUT that applies to all grids.
A special filter to override is very complicated and does not even correct.
Ok, we want see empty cell, and want filter this cells, but the special filter doesn't works correct!
What can we do, any ideas?
here is the question at infragistics
I answered this on the Infragistics forums right here with a sample.
You can use the RecordFilterDropDownPopulating event to remove the existing FilterDropDownItem for "(Blanks)" and replace it with a custom one for the value of 0 when the editor for the field is HideZeroIntEntryTextEditor. The following code accomplishes this:
void XamDataGrid1_RecordFilterDropDownPopulating(
object sender,
//Infragistics.Windows.DataPresenter.Events.
RecordFilterDropDownPopulatingEventArgs e)
{
if (e.Field.EditorTypeResolved == typeof(HideZeroIntEntryTextEditor)) {
bool found = false;
int index = 0;
while (index < e.DropDownItems.Count && !found) {
FilterDropDownItem item = e.DropDownItems[index];
if (item.DisplayText == "(Blanks)") {
e.DropDownItems.Remove(item);
e.DropDownItems.Insert(index, new FilterDropDownItem(
new ComparisonCondition(ComparisonOperator.Equals, 0), "(Blanks)"));
found = true;
}
index++;
}
}
}
Context
For a WPF application using the MVVM pattern I validate my entity(/business object) using the IDataErrorInfo interface on the entity so that validation rules in my entity are automatically called by WPF and the validationerrors automatically appear in the View. (inspired by Josh Smith in this article: http://joshsmithonwpf.wordpress.com/2008/11/14/using-a-viewmodel-to-provide-meaningful-validation-error-messages/
This works OK for simple validation rules like (name > 10 characters, value must be > 0)
But what to do when the validation rule in the model is more complex (like name must be unique / max value of the property is defined in another entity). I first thought of solving this by let the entity have a reference to a repository, but this doesn't feel good because I think there should only be references from the repository to the entity and not the other way (creating a cyclic reference)
Is it 'legal' to have a reference from the Recipe entity to the ConfigurationRepository. Or do you have a better suggestion?
Do you have suggestions how to implement Entity/Business object validation where the validation is dependent on other Entity/Service, like in the example below.
Below the simplified code of my real world problem.
In the Recipe entity I want to validate that the maximum temperature is less than the value stored in Configuration.MaximumTemperature. How would you solve this?
The Configuration entity (Stores the maximal allowed temperature for a recipe)
public class Configuration: INotifyPropertyChanged, IDataErrorInfo
{
private int _MaxTemperatureSetpoint;
public int MaxTemperatureSetpoint
{
get { return _MaxTemperatureSetpoint; }
set
{
if (value != _MaxTemperatureSetpoint)
{
_Setpoint = value;
RaisePropertyChanged("MaxTemperatureSetpoint");
}
}
}
The Simplified Recipe (Class where the user configures a recipe with a desired temperature (TemperatureSetpoint) and a desired Time (TimeMilliSeconds). The TemperatureSetpoint must be < Configuration.MaxTemperature)
public class Recipe: INotifyPropertyChanged, IDataErrorInfo
{
private int _TemperatureSetpoint;
public int TemperatureSetpoint
{
get { return _TemperatureSetpoint; }
set
{
if (value != _TemperatureSetpoint)
{
_Setpoint = value;
RaisePropertyChanged("Setpoint");
}
}
}
private int _TimeMilliSeconds;
public int TimeMilliSeconds
{
get { return _TimeMilliSeconds; }
set
{
if (value != _TimeMilliSeconds)
{
_TimeMilliSeconds= value;
RaisePropertyChanged("TimeMilliSeconds");
}
}
}
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string propertyName]
{
get
{
switch(propertyName)
{
case "TimeMilliSeconds":
//TimeMilliSeconds must be < 30 seconds
if (TimeMilliSeconds < 30000)
{ return "TimeMilliSeconds must be > 0 milliseconds";}
case "TemperatureSetpoint":
//MaxTemperatureSetpoint < maxTemperature stored in the ConfigurationRepository
int maxTemperatureSetpoint = ConfigurationRepository.GetConfiguration().MaxTemperatureSetpoint;
if (TemperatureSetpoint> maxTemperatureSetpoint )
{ return "TemperatureSetpoint must be < " + maxTemperatureSetpoint.ToString();}
}
}
#endregion
}
Recipe Repository
public interface IRecipeRepository
{
/// <summary>
/// Returns the Recipe with the specified key(s) or <code>null</code> when not found
/// </summary>
/// <param name="recipeId"></param>
/// <returns></returns>
TemperatureRecipe Get(int recipeId);
.. Create + Update + Delete methods
}
Configuration Repository
public interface IConfigurationRepository
{
void Configuration GetConfiguration();
}
For validation that is based on business rules, I usually expose a Validation Delegate that my ViewModel can set.
For example, the ViewModel for the Recipe might contain code that looks like this:
public GetRecipe(id)
{
CurrentRecipe = DAL.GetRecipe(id);
CurrentRecipe.AddValidationErrorDelegate(ValidateRecipe);
}
private string ValidateRecipe(string propertyName)
{
if (propertyName == "TemperatureSetpoint")
{
var maxTemp = Configuration.MaxTemperatureSetpoint;
if (CurrentRecipe.TemperatureSetpoint >= maxTemp )
{
return string.Format("Temperature cannot be greater than {0}", maxTemp);
}
}
return null;
}
The idea is that your Model should only contain raw data, therefore it should only validate raw data. This can include validating things like maximum lengths, required fields, and allowed characters. Business Logic, which includes business rules, should be validated in the ViewModel, and this allows that to happen.
The actual implementation of my IDataErrorInfo on the Recipe class would look like this:
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated
/// </summary>
protected List<string> ValidatedProperties = new List<string>();
#region Validation Delegate
public delegate string ValidationErrorDelegate(string propertyName);
private List<ValidationErrorDelegate> _validationDelegates = new List<ValidationErrorDelegate>();
public void AddValidationErrorDelegate(ValidationErrorDelegate func)
{
_validationDelegates.Add(func);
}
#endregion // Validation Delegate
#region IDataErrorInfo for binding errors
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public string GetValidationError(string propertyName)
{
// If user specified properties to validate, check to see if this one exists in the list
if (ValidatedProperties.IndexOf(propertyName) < 0)
{
//Debug.Fail("Unexpected property being validated on " + this.GetType().ToString() + ": " + propertyName);
return null;
}
string s = null;
// If user specified a Validation method to use, Validate property
if (_validationDelegates.Count > 0)
{
foreach (ValidationErrorDelegate func in _validationDelegates)
{
s = func(propertyName);
if (s != null)
{
return s;
}
}
}
return s;
}
#endregion // IDataErrorInfo for binding errors
#region IsValid Property
public bool IsValid
{
get
{
return (GetValidationError() == null);
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (error != null)
{
return error;
}
}
}
return error;
}
#endregion // IsValid Property
#endregion // IDataErrorInfo & Validation Members
To be honest, I found that the baked in WPF validation methods are not complete and/or elegant enough. I find that using the WPF methods would scatter validation code and logic throughout my application and would even put some in my UI. Like you, I used Custom Business Objects (CBOs) for everything, and I was really wanting to keep my validation in my objects, since I was using them across several projects (a web service, UI, mobile, etc).
What I did was take my CBO (Recipe, in this case), and add some validation methods as properties. Eg:
public Func<string> NameValidation
{
get
{
return () =>
{
string result = null;
if (String.IsNullOrEmpty(Name)) result = "Name cannot be blank";
else if (Name.Length > 100) result = "Name cannot be longer than 100 characters";
return result;
};
}
}
After that, I decorated it with a custom attribute:
[AttributeUsage(AttributeTargets.Property)]
public class CustomValidationMethod : Attribute
{
}
then I created a Validate() method for object-level validation:
public override void Validate()
{
var a = GetType().GetProperties().Where(w => w.GetCustomAttributes(typeof(CustomValidationMethod), true).Length > 0);
foreach (var a2 in a)
{
var result = a2.GetValue(this, null) as Func<string>;
if (result != null)
{
var message = result();
if (message != null)
//There was an error, do something
else if (message == null && Errors.ContainsKey(a2.Name))
//There was no error
}
}
}
then I created custom controls that support my validation. In this case, it was a ComboBox that I derived from the standard ComboBox and added this code:
public Func<string> ValidationMethod
{
get { return (Func<string>) GetValue(ValidationMethodProperty); }
set { SetValue(ValidationMethodProperty, value); }
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (ValidationMethod != null && !String.IsNullOrEmpty(ValidationMethod()))
SetControlAsInvalid();
else
SetControlAsValid();
}
Once this is all set up, I can add field validation in the validation methods (which are stored in my CBOs instead of scattered throughout my code), I can add object-level validation in my Validate() method. As well, I can customize with ease how the control should behave with regards to validation.
To use this, in my VM I would call .Validate() first, then deal with any problems before saving. In my case specifically, I would store error messages in a collection and then query them (this also allowed me to store several error messages instead of the first one)
I have tried all variaions that I can find others using, and frankly they all seem to boil down to what I already have.
I want a generic system for invoking methods based on generic inputs. Not sure that really captures it fully, but not sure how to state it.
Here we go:
I want to make breadcrumbs from a params of Expression>
here, SelectedDivisions is ObservableCollection and ModelId is long?.
So, the point is that I want to feed in a list of varying properties, have them processed by the data class such that each is processed by the appropriate method inside of data
data.MakeBreadCrumbs(() => dc.SelectedDivisions, () => dc.ModelId);
data contains the following code:
public void MakeBreadCrumbs(params Expression<Func<object>>[] propertyExpressions) {
foreach (Expression<Func<object>> propertyExpression in propertyExpressions) {
MemberExpression member = propertyExpression.Body as MemberExpression;
if (member == null) {
UnaryExpression uExp = propertyExpression.Body as UnaryExpression;
member = uExp.Operand as MemberExpression;
}
PropertyInfo propInfo = member.Member as PropertyInfo;
Type[] propTypes = propInfo.PropertyType.GetGenericArguments();/
MethodInfo methodInfo = typeof(BreadcrumbData).GetGenericMethod("MakeBreadCrumb", new Type[] { propInfo.PropertyType, typeof(string) }); //
if (methodInfo.IsGenericMethod) {
methodInfo.MakeGenericMethod(propTypes[0]);
}
ConstantExpression ce = Expression.Constant(propertyExpression.Compile().Invoke());
string criterionName = ReadCriterionName(propertyExpression);
methodInfo.Invoke(this, new object[] { ce.Value, criterionName });
}
the last line fails with "Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true." when I am processing the property expression for the ObservableCollection item.
Here are the methods defined in the data class which are available, and which are correctly selected, but the one for the ObservableCollection fails on invocation
(LookupTypeBase is a class particular to my solution, but insert any type here that works with the type of a fake ObservableCollection property)
public void MakeBreadCrumb<T>(ObservableCollection<T> selections, string criterionName) where T : LookupTypeBase {...}
public void MakeBreadCrumb(long? value, string criterionName) {...}
public static class xxx {
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] parameterTypes) {
var methods = type.GetMethods();
foreach (var method in methods.Where(m => m.Name == name)) {
var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
if (methodParameterTypes.SequenceEqual(parameterTypes, new SimpleTypeComparer())) {
return method;
}
}
return null;
}
private class SimpleTypeComparer : IEqualityComparer<Type> {
public bool Equals(Type x, Type y) {
return x.Assembly == y.Assembly && x.Namespace == y.Namespace && x.Name == y.Name;
}
public int GetHashCode(Type obj) {
throw new NotImplementedException();
}
}
}
It's silverlight 4 so you have latebound method invocation with the dynamic keyword, which should make this easy and fast:
BreadcrumbData.MakeBreadCrumb((dynamic)ce.Value, (dynamic)criterionName);
Aside from that i think your issue is that MakeGenericMethod returns a new methodinfo which you ignore instead of keeping around like so:
if (methodInfo.IsGenericMethod) {
methodInfo = methodInfo.MakeGenericMethod(propTypes[0]);
}
Given a reference to an object defined in XAML, is it possible to determine what (if any) x:Name the object has, or can I only do this by accessing the FrameworkElement.Name property (if the object is a FrameworkElement)?
One approach you could take is to first check if the object is a FrameworkElement, and if not, try reflection to get the name:
public static string GetName(object obj)
{
// First see if it is a FrameworkElement
var element = obj as FrameworkElement;
if (element != null)
return element.Name;
// If not, try reflection to get the value of a Name property.
try { return (string) obj.GetType().GetProperty("Name").GetValue(obj, null); }
catch
{
// Last of all, try reflection to get the value of a Name field.
try { return (string) obj.GetType().GetField("Name").GetValue(obj); }
catch { return null; }
}
}