NHibernate Change Auditing and Components - nhibernate

An integral part of our architecture as our system must provide a dashboard for users to explicitly publish data changes from environment to another. We looked at NH Evers, but we needed to many domain specific things to be baked into the architecture. We've been successfully using NHibernate's eventing model to track and log state changes (to another table) in our system, but recently stumbled across a snag with Components. When IPostInsertEventListener and IPostUpdateEventListener is fired it publishes a value arrays that denote the current state of the entity. In the case of updates, it publishes an array denoting previous state as well. We are using these arrays to save off "before" and "after" state into our table. When the property is a Component, the actual value (item in the area) is the component instance itself - i.e. an untyped complex object. Short of cross referencing the metamodel mapper and reflecting on that to pull out the individual values, how can I get at the actual values that make up the component? I'm able to get at the column names that map to the Component's members, but not the before and after values.
I've been digging through the NH source, and I'm not finding how to pull out these values, but obviously NH knows how to do this internally as it's able to publish the sql properly. Here's a modified/verbose version of the code I currently have that highlights the issue:
public static RowChangedReport Load(IChangeTrackable trackable, object entityId, AbstractEntityPersister persister, object[] state, object[] oldState)
{
var report = new RowChangedReport
{
Entity = trackable,
EntityTypeFullName = persister.EntityName,
TableName = new TableName(persister.GetTableName()),
EntityId = entityId,
};
var authContext = AuthenticationContext.Current;
if (authContext != null)
report.SecurityContextUserId = authContext.UserId;
if (persister.PropertyNames != null && state != null)
{
report.ChangedType = oldState == null ? RowChangedTypes.New : RowChangedTypes.Modified;
for (var index = 0; index < persister.PropertyNames.Length; index++)
{
var propertyName = persister.PropertyNames[index];
IType propertyType = persister.PropertyTypes[index];
if (!propertyType.IsCollectionType)
{
AddColumnChangeReport(persister, state, oldState, index, propertyName, report);
}
}
}
report.FinalizeState();
return report;
}
private static void AddColumnChangeReport(AbstractEntityPersister persister, object[] state, object[] oldState, int index, string propertyName, RowChangedReport report)
{
var currentValue = state[index];
// for simple properties, this is always a single element array
// for components, this is an array with an element for each member on the component - i.e. how the component is mapped
string[] columns = persister.GetPropertyColumnNames(propertyName);
var previousValue = oldState == null ? null : oldState[index];
if (!Equals(currentValue, previousValue))
{
if (report.ChangedType == RowChangedTypes.Modified && propertyName == IsActivePropertyName)
report.FlagAsDeleted();
foreach (var column in columns)
{
// if this is a component, both the currentValue and the previousValue are complex objects
// need to have a way to get the actual member value per column!
report.AddChange(new ColumnChangedReport(report, propertyName, column, previousValue, currentValue));
}
}
}

OK, after a few hours reading the NH code, I stumbled across Tuplizers and tracked them back to the property Type. So the solution is pretty simple - for components, you need to detect them as such, cast to the ComponentType Type and then ask ComponentType for the property values. Here some code that's working for me:
public static RowChangedReport Load(IChangeTrackable trackable, object entityId, AbstractEntityPersister persister, object[] state, object[] oldState)
{
var report = new RowChangedReport
{
Entity = trackable,
EntityTypeFullName = persister.EntityName,
TableName = new TableName(persister.GetTableName()),
EntityId = entityId,
};
var authContext = AuthenticationContext.Current;
if (authContext != null)
report.SecurityContextUserId = authContext.UserId;
if (persister.PropertyNames != null && state != null)
{
report.ChangedType = oldState == null ? RowChangedTypes.New : RowChangedTypes.Modified;
for (var index = 0; index < persister.PropertyNames.Length; index++)
{
var propertyName = persister.PropertyNames[index];
IType propertyType = persister.PropertyTypes[index];
if (!propertyType.IsCollectionType)
{
AddColumnChangeReport(persister, state, oldState, index, propertyName, propertyType, report);
}
}
}
report.FinalizeState();
return report;
}
private static void AddColumnChangeReport(AbstractEntityPersister persister, object[] state, object[] oldState, int index, string propertyName, IType propertyType, RowChangedReport report)
{
var currentValue = state[index];
string[] columns = persister.GetPropertyColumnNames(propertyName);
var previousValue = oldState == null ? null : oldState[index];
if (!Equals(currentValue, previousValue))
{
if (report.ChangedType == RowChangedTypes.Modified && propertyName == IsActivePropertyName)
report.FlagAsDeleted();
if (propertyType.IsComponentType)
{
ComponentType component = (ComponentType)propertyType;
object[] componentCurrentValues = null;
if (currentValue != null)
componentCurrentValues = component.GetPropertyValues(currentValue, EntityMode.Poco);
object[] componentPreviousValues = null;
if (currentValue != null)
componentPreviousValues = component.GetPropertyValues(previousValue, EntityMode.Poco);
if ((componentCurrentValues != null && componentCurrentValues.Length != columns.Length) ||
(componentPreviousValues != null && componentPreviousValues.Length != columns.Length))
throw new ConventionViolationException(GetComponentArraysExceptionMessage(persister, propertyName, columns, componentPreviousValues, componentCurrentValues));
for (int i = 0; i < columns.Length; i++)
{
var column = columns[i];
var componentPreviousValue = componentPreviousValues == null ? null : componentPreviousValues[i];
var componentCurrnetValue = componentCurrentValues == null ? null : componentCurrentValues[i];
report.AddChange(new ColumnChangedReport(report, propertyName, column, componentPreviousValue, componentCurrnetValue));
}
}
else
{
if (columns.Length > 1)
throw new ConventionViolationException("Expected only component properties to have multiple columns. Property '{0}' on entity {1} is violating that assumption.".FormatWith(propertyName, persister.EntityName));
report.AddChange(new ColumnChangedReport(report, propertyName, columns[0], previousValue, currentValue));
}
}
}

Related

Problem with Session Variable value in ASP.NET Core Project. Takes the value on second time the page Loads

I have a project in which I have some Records from database in a table. So it displays 10 out of all in each page.
The User check some rows(with checkBox) and I keep those rows Id's and send them with Ajax in GetCheckedList Method when the user push Next Page Button. This methods makes the values a string and Store them as Session Variable.
The problem is that when Index takes Session Value, after GetCheckedList execution the very first time the value is Null but when User navigates for another time in Next or Previous page the Session Value on index is Correct.
Something I lose here with which method runs first but I cant figure it out and I need some help.
Index
public async Task<IActionResult> Index(...)
{
int pageSize = 10;
var users = from a in _context.Users
select a;
...
//Session variable
var checkedList = HttpContext.Session.GetString("CheckedSession");
if (checkedList != null)
{
ViewBag.checkedListVB = checkedList;
HttpContext.Session.SetString("CheckedSession1", checkedList);
}
return View(await App.PaginatedList<User>.CreateAsync(users.AsNoTracking(), pageNumber ?? 1, pageSize));
}
GetCheckedList
public RedirectResult GetCheckedList(List<string> values, string text)
{
List<string> str = values;
str.RemoveAll(item => item == null);
List<int> intList = str.ConvertAll(int.Parse);
var finalCheckList = string.Join(",", intList);
if (HttpContext.Session.GetString("CheckedSession") != null)
{
var temp = HttpContext.Session.GetString("CheckedSession");
string[] tempStringList = temp.Split(",");
var removeEmptytemp = tempStringList.Where(o => o != "").ToArray();
int[] myIntsTemp = Array.ConvertAll(removeEmptytemp, int.Parse);
List<int> myIntsTempList = myIntsTemp.ToList();
List<int> ulist = myIntsTempList.Union(intList).ToList();
var now = string.Join(",", ulist);
finalCheckList = string.Join(",",now);
}
HttpContext.Session.SetString("CheckedSession", finalCheckList);
return new RedirectResult(url: "/Users/Index", permanent: false,
preserveMethod: true);
}

How to use GroupFormatter with ObjectListView control

I cannot seem to find anywhere, any examples on how to make use of the GroupFormatter delegate to allow me to add footers to my groups when using the ObjectListView control.
Does anyone have any examples that could demonstrate this? I want to remove the text from the group header and add a footer (different text per footer). As well as changing font, etc.
Any examples would be very helpful.
You can analyze the code for the
public void MakeGroupies<T>(T[] values, string[] descriptions, object[] images, string[] subtitles, string[] tasks)
method of the ObjectListView class. That explicitly sets the GroupKeyGetter, GroupKeyToTitleConverter and GroupFormatter property delegates.
This is C# but your VB adaptation should be straightforward. I am using this small test class as the object type to bind to the list view.
public class TestClass
{
private readonly string _s;
private readonly float _f;
public TestClass( string p1, float p2 )
{
this._s = p1;
this._f = p2;
}
[OLVColumn(DisplayIndex = 1, Name="S", Title="String")]
public string S {get {return this._s;}}
[OLVColumn( DisplayIndex = 2, Name = "F", Title = "Float" )]
public float F {get {return this._f;}}
}
So as not to manually define column traits I am using attributes inside the bound object and a
BrightIdeasSoftware.Generator.GenerateColumns( this.olv, typeof( TestClass ) );
call in the form/user control where I am using the list view. In fact here is the method that completely isolates ObjectListView configuration:
void SetData( TestClass[] objects )
{
// build list columns
Generator.GenerateColumns( this.olv, typeof( TestClass ) );
// use groups and make current column the priimary sort column
this.olv.ShowGroups = true;
this.olv.SortGroupItemsByPrimaryColumn = false;
// loop through columns and set properties
foreach( OLVColumn col in this.olv.Columns )
{
col.Groupable = true;
col.Sortable = true;
if( col.Name == "F" )
{
col.MakeGroupies<float>( new float[] { 10f, 100f, 1000f }, new string[] { "<10", "10-100", "100-1000", ">1000" } );
}
else if( col.Name == "S" )
{
col.UseInitialLetterForGroup = false;
//
col.GroupKeyGetter = ( obj ) =>
{
TestClass tc = (TestClass)obj;
switch( char.ToLower( tc.S[0] ) )
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u': return true;
default: return false;
}
};
//
col.GroupKeyToTitleConverter = ( o ) => { bool b = (bool)o; return b ? "vowel" : "consonant"; };
//
col.GroupFormatter = ( /*OLVGroup*/ group, /*GroupingParameters*/ parms ) =>
{
string s = string.Format ("{0} {1}", group.GroupId, group.Id);
//group.BottomDescription = "BottomDescription: " + s;
//group.TopDescription = "TopDescription: " + s;
group.Footer = "Footer: " + s;
};
}
}
//
this.olv.RebuildColumns();
//
this.olv.SetObjects( objects );
}
You will definitely have one different footer per each group.

Enum dropdownlistfor issue

I've read a possible solution to this, but would require a lot of rewriting, the possible solution is linked here, but there wouldn't be any sense to doing it that way if I am just a couple words off in my dropdownlistfor.
I'm having an issue with my dropdownlistfor as this is all new to me:
#Html.DropDownListFor(model => model.pageID, new SelectList (Enum.GetNames(typeof(PageIndex)), EnumHelper.GetSelectedItemList<PageIndex>().SelectedValue))
Trying to grab the "description" of my enum values as the drop down lists text values, then have an integer value returned to the database on POST.
Here's my enum:
public enum PageIndex : int
{
[Description("Developmental Disabilities Tip Sheet")]
ddTipSheets = 1,
[Description("Hiiiiiiiiiiiiiiiiiiii")]
Example1 = 2,
[Description("I don't know what I'm doing")]
Example2 = 3
};
and my EnumHelper:
public class EnumHelper
{
public static SelectList GetSelectedItemList<T>() where T : struct
{
T t = default(T);
if (!t.GetType().IsEnum) { throw new ArgumentNullException("Please make sure that T is of Enum Type"); }
var nameList = t.GetType().GetEnumNames();
int counter = 0;
Dictionary<int, String> myDictionary = new Dictionary<int, string>();
if (nameList != null && nameList.Length > 0)
{
foreach (var name in nameList)
{
T newEnum = (T) Enum.Parse(t.GetType(), name);
string description = getDescriptionFromEnumValue(newEnum as Enum);
if (!myDictionary.ContainsKey(counter))
{
myDictionary.Add(counter, description);
}
counter++;
}
counter = 0;
return new SelectList(myDictionary, "Key", "Value");
}
return null;
}
private static string getDescriptionFromEnumValue(Enum value)
{
DescriptionAttribute descriptionAttribute =
value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.SingleOrDefault() as DescriptionAttribute;
return descriptionAttribute == null ?
value.ToString() : descriptionAttribute.Description;
}
}

Are There any better way than using ObservableCollection for fast Binding in XAML?

We are developing a Windows Phone application (CurrencyExchange) and there is a page that includes a textbox and a listbox. ListBox's itemsource is it's viewmodel's property type of observable collection. Textbox's textchange event is changing observable collection's all items but when observable changing and trying to bind listbox's items, the page is locking. I have seen a customobservable collection named Fastobservablecollection that is not running in viewmodel because it is using DispatcherObject and Dispatcherprioty that can't use in viewmodel. Are there any alternative that better than ObservableCollection?
List<Currency> newList = new List<Currency>(CurrencyConversions.ToList());
foreach (var item in newList)
{
Double result;
if (Double.TryParse(AmountPhone, NumberStyles.Any, new System.Globalization.CultureInfo("tr-TR"), out result))
item.CalculatedValue = Math.Round(result * (Direction == "0" ? item.ConversionRateSell : item.ConversionRateBuy), 2);
else
item.CalculatedValue = 0;
}
CurrencyConversions = new ObservableCollection<Currency>(newList);
or
List<Currency> newList = new List<Currency>();
foreach (var item in CurrencyConversions)
{
Double result;
if (Double.TryParse(AmountPhone, NumberStyles.Any, new System.Globalization.CultureInfo("tr-TR"), out result))
item.CalculatedValue = Math.Round(result * (Direction == "0" ? item.ConversionRateSell : item.ConversionRateBuy), 2);
else
item.CalculatedValue = 0;
newList.Add(item);
}
CurrencyConversions = new ObservableCollection<Currency>(newList);
Thanks.
With SmartCollection
List<Currency> newList = new List<Currency>(CurrencyConversions.ToList());
foreach (var item in newList)
{
Double result;
if (Double.TryParse(Amount, NumberStyles.Any, new System.Globalization.CultureInfo("tr-TR"), out result))
item.CalculatedValue =Math.Round( result * (Direction == "0" ? item.ConversionRateSell : item.ConversionRateBuy),2);
else
item.CalculatedValue = 0;
}
CurrencyConversions = new SmartCollection<Currency>(newList);
You should not create a new SmartCollection each time, but more use only one instance and make modifications to the collection. I would suggest to change the implementation of your property CurrencyConversions to the following.
private SmartCollection<Currency> _conversions;
public SmartCollection<Currency> CurrencyConversions{
get{
if (_conversions == null) {
_conversions = new SmartCollection<Currency>();
}
return _conversions;
}
}
Then you can call it like so in your code:
CurrencyConversions.Reset(newList);
As I currently understand your code, you are not changing (adding and removing items) the collection, but rather change the property CalculatedValue of the type Currency. When the type Currency implements the interface INotifyPropertyChanged there is no need to change the collection to update the UI, so changing your code to
foreach (var item in CurrencyConversions) {
Double result;
if (Double.TryParse(Amount, NumberStyles.Any, new System.Globalization.CultureInfo("tr-TR"), out result))
item.CalculatedValue =Math.Round( result * (Direction == "0" ? item.ConversionRateSell : item.ConversionRateBuy),2);
else
item.CalculatedValue = 0;
}
would be enough when your type implements INotifyPropertyChanged. For that to work the property CalculatedValue should look like:
private double _value;
public double CalculatedValue{
get{
return _value;
}
set{
_value = value;
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs("CalculatedValue"));
}
}
}

nhibernate, 0 in foreign key column

I got a legacy database where the value of 0 was used in FK columns to indicate that no relation have been specified.
This is not something that I can change in a trivial way. Is it possible to tell NHibernate to treat 0 as null in specified columns?
Edit
I know about not-found, but I just want to ignore those with 0.
This solution worked great for us: http://nhibernate.info/blog/2011/01/28/how-to-use-0-instead-of-null-for-foreign-keys.html
In short:
Add the following class:
public class NullableTuplizer : PocoEntityTuplizer
{
public NullableTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity)
: base(entityMetamodel, mappedEntity)
{
}
public override object[] GetPropertyValuesToInsert(
object entity, IDictionary mergeMap, ISessionImplementor session)
{
object[] values = base.GetPropertyValuesToInsert(entity, mergeMap, session);
//dirty hack 1
for (int i = 0; i < values.Length; i++)
{
if (values[i ] == null && typeof (IEntity).IsAssignableFrom(getters[i ].ReturnType))
{
values[i ] = ProxyFactory.GetProxy(0, null);
}
}
return values;
}
public override object[] GetPropertyValues(object entity)
{
object[] values = base.GetPropertyValues(entity);
//dirty hack 2
for (int i = 0; i < values.Length; i++)
{
if (values[i ] == null && typeof (IEntity).IsAssignableFrom(getters[i ].ReturnType))
{
values[i ] = ProxyFactory.GetProxy(0, null);
}
}
return values;
}
public override void SetPropertyValues(object entity, object[] values)
{
//dirty hack 3.
for (int i = 0; i < values.Length; i++)
{
if (typeof (IEntity).IsAssignableFrom(getters[i ].ReturnType)
&& ((IEntity) values[i ]).Id == 0)
{
values[i ] = null;
}
}
base.SetPropertyValues(entity, values);
}
}
Then register it for every relevant mapping:
foreach (var persistentClass in configuration.ClassMappings)
{
persistentClass.AddTuplizer(EntityMode.Poco, typeof(NullableTuplizer).AssemblyQualifiedName);
}
There is a similar question: NHibernate Saving 0 to many-to-one column instead of null. There is an interesting, but a bit weird solution by Noel Kennedy.
I would probably store a record with the ID 0 in the database, which represents a Null Object.