NHibernate dynamic-update fails to update data changed in interceptor - nhibernate

If I set dynamic-update=true I've found that fields updated in my Interceptor do not get included in the update statement that goes to the database. When I set it to false all the columns including the time stamp get updated. I really want to use dynamic update.
public class Interceptor : EmptyInterceptor
{
public override Boolean OnFlushDirty(object entity, object id, object[] state,
object[] previousState, string[] propertyNames, IType[] types)
{
var auditEntity = entity as BaseAuditEntity;
if (auditEntity != null)
{
var now = DateTime.Now;
var index = Array.IndexOf(propertyNames, "LastModifiedTimestamp");
state[index] = now;
auditEntity.LastModifiedTimestamp = now;
}
return base.OnFlushDirty(entity, id, state, previousState, propertyNames, types);
}
}
I thought that this line would have marked my the last modified column as dirty.
auditEntity.LastModifiedTimestamp = now;
Is there something I should do in my interceptor to mark the time stamp field as dirty?

The API-Doc says: "returns true if the user modified the currentState in any way."
Did you try to return true instead of calling the empty base implementation?
public class Interceptor : EmptyInterceptor
{
public override Boolean OnFlushDirty(object entity, object id, object[] state,
object[] previousState, string[] propertyNames, IType[] types)
{
var auditEntity = entity as BaseAuditEntity;
if (auditEntity != null)
{
var now = DateTime.Now;
var index = Array.IndexOf(propertyNames, "LastModifiedTimestamp");
state[index] = now;
auditEntity.LastModifiedTimestamp = now;
return true;
}
return base.OnFlushDirty(entity, id, state, previousState, propertyNames, types);
}
}

Related

NHinernate - Serializing Criteria to json and back. for a web interface usage

I have a web interface with a lot of data grids that allow the user passing a search criteria to the server.
I don't want to actually build an explicit method that will handle each grid individually, so I thought to allow passing JSON criteria from the client to the server.
This code
string res = JsonConvert.SerializeObject(Restrictions.Eq("id", "1"));
will return will return {"PropertyName":"id","Value":"1"} which is exactly what I wanted to pass from the client - perfect.
but this code with or statement:
string res = JsonConvert.SerializeObject(Restrictions.Or(Restrictions.Eq("id", "1"),Restrictions.Eq("id", "2")))
will return {}
What is the way to convert JSON into a Criteria and back in nHibernate.
To be more accurate, I have something like this in the client, and I want my server to support it. I can serialize the client ui to json.
Thanks
The empty json serialization happens because newtonsoft can only serialize public fields.
The code below will serialize all fields of a class regardless of its visibility.
public class MyContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(p => base.CreateProperty(p, memberSerialization))
.Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Select(f => base.CreateProperty(f, memberSerialization)))
.ToList();
props.ForEach(p => { p.Writable = true; p.Readable = true; });
return props;
}
}
...
var myOrs = Restrictions.Or(Restrictions.Eq("id", "1"), Restrictions.Eq("id", "2"));
var settings = new JsonSerializerSettings()
{
ContractResolver = new MyContractResolver()
};
string json = JsonConvert.SerializeObject(myOrs, settings);
Now to deserialize this json we have a problem, the NHibernate classes are either abstract or don't have a public empty constructor.
You will need to create a custom class to deserialize.
public class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Not implemented yet");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
if (obj.Value<string>("Op") == "or")
{
var leftJObject = obj["LeftHandSide"];
var rightJObject = obj["RightHandSide"];
var left = new SimpleExpression(leftJObject.Value<string>("PropertyName"), GetJTokenValue(leftJObject["Value"]), leftJObject.Value<string>("Op"));
var right = new SimpleExpression(rightJObject.Value<string>("PropertyName"), GetJTokenValue(rightJObject["Value"]), rightJObject.Value<string>("Op"));
return Restrictions.Or(left, right);
}
//TODO: The rest of the restrictions
return null;
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return true;
}
private object GetJTokenValue(JToken token)
{
if (token.Type == JTokenType.String)
{
return token.Value<string>();
}
else if (token.Type == JTokenType.Integer)
{
return token.Value<Int32>();
}
//TODO: The rest of the types
return null;
}
}
...
var result = JsonConvert.DeserializeObject<AbstractCriterion>(json, new MyConverter());

How can I bind a comma separated list in a URL to an array of objects?

I have a class named VerseRangeReference that has the properties Chapter, FirstVerse and LastVerse.
I have decorated it with a TypeConverterAttribute [TypeConverter(typeof(VerseRangeReferenceConverter))]
I have an action on a controller like this
public Task<ViewResult> Verses(VerseRangeReference[] verses)
But the value of verses is always a single element with the value null. Here is my type converter
public class VerseRangeReferenceConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.GetType() == typeof(string))
{
string source = (string)value;
return VerseRangeReference.ParseMultiple(source);
}
return null;
}
}
The result of VerseRangeReference.ParseMultiple(source) is a valid array of instances of VerseRange.
I had to implement a custom model binder. If someone can think of a way to do this with a TypeConverter then I will accept that answer instead because model binders are more complicated.
public class VerseRangeReferenceArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult != ValueProviderResult.None)
{
VerseRangeReference[] verseRangeReferences = VerseRangeReference.ParseMultiple(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(verseRangeReferences);
}
return Task.CompletedTask;
}
}
public class VerseRangerReferenceArrayModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(VerseRangeReference[]))
return new BinderTypeModelBinder(typeof(VerseRangeReferenceArrayModelBinder));
return null;
}
}
This must be registered.
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new VerseRangerReferenceArrayModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You can use a type converter to bind a comma separated string to a sequence of values. However, the type converter should convert from the string to the sequence directly. This means that the type converter should be configured for something like IEnumerable<T> or T[]. To simplify matters I will continue my explanation for IEnumerable<int> but if you want to use arrays instead you should just make sure that the type converter converts to an array instead of something that implements IEnumerable<T>.
You can configure a type converter for IEnumerable<int> using TypeDescriptor.AddAttributes:
TypeDescriptor.AddAttributes(
typeof(IEnumerable<int>),
new TypeConverterAttribute(typeof(EnumerableIntTypeConverter)));
This configures EnumerableIntTypeConverter as a type converter that can convert IEnumerable<int>.
This call has to be made when the process starts and in the case of ASP.NET Core this can conveniently be done in the Startup.Configure method.
Here is the EnumerableIntTypeConverter that converts the comma separated string of numbers to a list of ints:
internal class EnumerableIntTypeConverter : TypeConverter
{
private const char Separator = ',';
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string #string))
throw new NotSupportedException($"{GetType().Name} cannot convert from {(value != null ? value.GetType().FullName : "(null)")}.");
if (#string.Length == 0)
return Enumerable.Empty<int>();
var numbers = new List<int>();
var start = 0;
var end = GetEnd(#string, start);
while (true)
{
if (!int.TryParse(
#string.AsSpan(start, end - start),
NumberStyles.AllowLeadingSign,
culture,
out var number))
throw new FormatException($"{GetType().Name} cannot parse string with invalid format.");
numbers.Add(number);
if (end == #string.Length)
break;
start = end + 1;
end = GetEnd(#string, start);
}
return numbers;
}
private static int GetEnd(string #string, int start)
{
var end = #string.IndexOf(Separator, start);
return end >= 0 ? end : #string.Length;
}
}
The parsing uses System.Memory to avoid allocating a new string for each number in the list. If your framework doesn't have the int.TryParse overload that accepts a Span<char> you can use string.Substring instead.

Issue with Web Api Custom Model Binder in MVC4

I am using Mvc4 with WebApi.
I am using Dto objects for the webApi.
I am having enum as below.
public enum Status
{
[FlexinumDefault]
Unknown = -1,
Active = 0,
Inactive = 100,
}
Dto structure is as follows.
[DataContract]
public class abc()
{
[DataMemebr]
[Required]
int Id{get;set;}
[DataMember]
[Required]
Status status{get;set}
}
I have created Custom Model Binder which will validate the enum(status) property in the dto object and return false if the enum value is not passed.
if the status enum property is not passed in the dto object,we should throw exception
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (input != null && !string.IsNullOrEmpty(input.AttemptedValue))
{
if (bindingContext.ModelType == typeof(Enum))
{
//var actualValue = null;
var value = input.RawValue;
in the api controller,i have action method like
public void Create([FromUri(BinderType = typeof(EnumCustomModelBinder))]abcdto abc)
{
In global.asax.cs
i have set like
GlobalConfiguration.Configuration.BindParameter(typeof(Enum), new EnumCustomModelBinder());
the issue i am facing is the custombinder
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
,the input value is coming as null.
Please sugggest
I found the solution
This works fine,but the default implementation of model binder is missing.
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var json = actionContext.Request.Content.ReadAsStringAsync().Result;
if (!string.IsNullOrEmpty(json))
{
var jsonObject = (JObject) Newtonsoft.Json.JsonConvert.DeserializeObject(json);
var jsonPropertyNames = jsonObject.Properties().Select(p => p.Name).ToList();
var requiredProperties = bindingContext.ModelType.GetProperties().Where(p =>p.GetCustomAttributes(typeof(RequiredAttribute),
false).Any()).ToList();
var missingProperties = requiredProperties.Where(bindingProperty => !jsonPropertyNames.Contains(bindingProperty.Name)).ToList();
if (missingProperties.Count > 0)
{
missingProperties.ForEach(
prop =>
{
if (prop.PropertyType.IsEnum)
actionContext.ModelState.AddModelError(prop.Name, prop.Name + " is Required");
});
}
var nullProperties = requiredProperties.Except(missingProperties).ToList();
if (nullProperties.Count > 0)
{
nullProperties.ForEach(p =>
{
var jsonvalue = JObject.Parse(json);
var value = (JValue)jsonvalue[p.Name];
if (value.Value == null)
{
actionContext.ModelState.AddModelError(p.Name, p.Name + " is Required");
}
});
}
}
// Now we can try to eval the object's properties using reflection.
return true;
}

MVC RenderAction from View Error after model error

I am experiencing a bizarre problem with RenderAction. As my view model is very complex I will try to streamline the process in broad strokes, as follows:
Browser requests controller action.
Action populates complex view model and passes to a view
View contains a renderAction to build child/partial view. (I
uses renderAction instead of a partialView at this point
due to the complexity of the model the partial view needs.)
This proceeds as necessary and the view is shown in the browser without error.
If I create a model error after posting back some bad data and then return the same model to the same view, an error is thrown when the renderAction is called. When debugging, the controller is accessed, but the action is skipped and the app goes directly to disposing at the bottom of the controller.
It would seem that the only difference is that main controller action (not the partial) that populates the big view model is at first reached via a get and then fails when reached via a post. I played around for hours with the model - even recreated the model from scratch - so it can't be related to the model.
Here is the stack trace:
at System.Web.HttpServerUtility.ExecuteInternal(IHttpHandler handler, TextWriter writer, Boolean preserveForm, Boolean setPreviousPage, VirtualPath path, VirtualPath filePath, String physPath, Exception error, String queryStringOverride)
at System.Web.HttpServerUtility.Execute(IHttpHandler handler, TextWriter writer, Boolean preserveForm, Boolean setPreviousPage)
at System.Web.HttpServerUtility.Execute(IHttpHandler handler, TextWriter writer, Boolean preserveForm)
at System.Web.HttpServerUtilityWrapper.Execute(IHttpHandler handler, TextWriter writer, Boolean preserveForm)
at System.Web.Mvc.Html.ChildActionExtensions.ActionHelper(HtmlHelper htmlHelper, String actionName, String controllerName, RouteValueDictionary routeValues, TextWriter textWriter)
at System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper htmlHelper, String actionName, Object routeValues)
at ASP._Page_Views_Pricelist__drawProductPricelistProductRow_cshtml.Execute() in c:\Users\Administrator\Documents\ProofPix_TFS\ProofPix\ProofPixAdmin\Views\Pricelist\_drawProductPricelistProductRow.cshtml:line 35
at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
at System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance)
at System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer)
at System.Web.Mvc.HtmlHelper.RenderPartialInternal(String partialViewName, ViewDataDictionary viewData, Object model, TextWriter writer, ViewEngineCollection viewEngineCollection)
at System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper htmlHelper, String partialViewName, Object model)
at ASP._Page_Views_Pricelist_edit_cshtml.Execute() in c:\Users\Administrator\Documents\ProofPix_TFS\ProofPix\ProofPixAdmin\Views\Pricelist\Edit.cshtml:line 121
at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
at System.Web.Mvc.WebViewPage.ExecutePageHierarchy()
at System.Web.WebPages.StartPage.RunPage()
at System.Web.WebPages.StartPage.ExecutePageHierarchy()
at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
at System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance)
at System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer)
at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass1a.<InvokeActionResultWithFilters>b__17()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation)
Here is the renderAction:
Html.RenderAction("_pricelistProductOptions", new { id = Model.Product.ProductId, ShowHtml = false });
You're help is greatly appreciated. BTW (kinda a newb)!
EDIT (added action)
// POST: /Pricelist/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(adminEditPricelistVM adminEditPricelistVM)
{
if (ModelState.IsValid)
{
//Code removed for simplicity
return RedirectToAction("Edit", new { id = pricelist.PricelistId });
}
int vendorId = (int)adminEditPricelistVM.VendorId;
Vendor vendor = (from b in db.Vendors where b.VendorId == vendorId select b).SingleOrDefault();
adminEditPricelistVM.ProductCategories = (from a in db.ProductCategories
from b in db.VendorProductCategory
where b.VendorId == vendorId && a.ProductCategoryId == b.ProductCategoryId
select a).OrderBy(o => o.SortOrder).ToList();
List<Product> products = (from a in db.Products where a.DiscontinuedDate == null && a.VendorId == 1 select a).OrderBy(o => o.SortOrder).ToList();
//repopulate ProductCategory and Vendor nav properties in the Formula items as these are no longer populated after post
foreach(PricingFormula pf in adminEditPricelistVM.PricingFormulas){
pf.ProductCategory = (from a in adminEditPricelistVM.ProductCategories where a.ProductCategoryId == pf.ProductCategoryId select a).SingleOrDefault();
pf.Vendor = vendor;
}
adminEditPricelistVM.Pricelist.PricingFormulas = new List<PricingFormula>();
adminEditPricelistVM.Pricelist.PricingFormulas.AddRange(adminEditPricelistVM.PricingFormulas);
List<PricelistProduct> thisFilteredPP = (from a in adminEditPricelistVM.Pricelist.PricelistProducts where a.ProductId > 0 select a).ToList();
List<PricelistProductOption> thisOptionsToDelete = new List<PricelistProductOption>();
List<PricelistProductOptionsDetail> thisOptionDetailsToDelete = new List<PricelistProductOptionsDetail>();
//filter pricelistProducts so only selected options remain in list
foreach (PricelistProduct pp in thisFilteredPP)
{
pp.PricelistId = adminEditPricelistVM.Pricelist.PricelistId;
var x = pp.ProductId;
foreach (PricelistProductOption ppo in pp.PricelistProductOptions)
{
//repopulate PricelistProduct object
ppo.PricelistProduct = pp;
ppo.PricelistProductId = pp.PricelistProductId;
int numPODs = (from a in ppo.PricelistProductOptionsDetails where a.ProductOptionsDetailId > 0 select a).Count();
if (numPODs == 0)
{
thisOptionsToDelete.Add(ppo);
}
else
{
foreach (PricelistProductOptionsDetail ppod in ppo.PricelistProductOptionsDetails)
{
//repopulate PricelistProductOption object
ppod.PricelistProductOption = ppo;
ppod.PricelistProductOptionsId = ppo.PricelistProductOptionId;
if (ppod.ProductOptionsDetailId == 0)
{
thisOptionDetailsToDelete.Add(ppod);
}
else //POD is selected but if it is the default option and it is the only option and it is priced at 0.00, then we will remove it to as it is the default setting.
{
if (ppod.Price == 0 && numPODs == 1)
{
ProductOptionsDetail prodOpDet = (from c in db.ProductOptionsDetails where c.ProductOptionsDetailId == ppod.ProductOptionsDetailId select c).SingleOrDefault();
if (prodOpDet.IsDefault == true)
{
thisOptionsToDelete.Add(ppo);
}
}
}
}
foreach (PricelistProductOptionsDetail dppod in thisOptionDetailsToDelete)
{
ppo.PricelistProductOptionsDetails.Remove(dppod);
}
thisOptionDetailsToDelete.Clear();
}
}
foreach (PricelistProductOption dppo in thisOptionsToDelete)
{
pp.PricelistProductOptions.Remove(dppo);
}
thisOptionsToDelete.Clear();
}
adminEditPricelistVM.Pricelist.PricelistProducts = new List<PricelistProduct>();
adminEditPricelistVM.Pricelist.PricelistProducts.AddRange(thisFilteredPP);
adminEditPricelistVM.PPPVMs =
(from product in products
join pricelistProduct in adminEditPricelistVM.Pricelist.PricelistProducts on product.ProductId equals pricelistProduct.ProductId into gj
from subpricelistProduct in gj.DefaultIfEmpty()
select new adminEditProductsPricelistProductsVM()
{
CategoryId = (int)product.ProductCategoryId,
Product = product,
PricelistProduct = subpricelistProduct
}).ToList();
//repopulate PricelistProducts.Product and PricelistProducts.ProductCategory in Pricelist
foreach (PricelistProduct pp in adminEditPricelistVM.Pricelist.PricelistProducts)
{
pp.Product = (from p in products where p.ProductId == pp.ProductId select p).SingleOrDefault();
pp.ProductCategory = (from a in adminEditPricelistVM.ProductCategories where a.ProductCategoryId == pp.ProductCategoryId select a).SingleOrDefault();
pp.Pricelist = adminEditPricelistVM.Pricelist;
}
ViewBag.PricingFormulaRoundingTypes = (from c in db.PricingFormulaRoundingTypes select c).ToList();
var errors = ModelState.Select(x => x.Value.Errors).ToList();
return View(adminEditPricelistVM);
}
This answer is just a hunge, as the actual error isn't showing, just the stacktrace.
//repopulate PricelistProducts.Product and PricelistProducts.ProductCategory in Pricelist
foreach (PricelistProduct pp in adminEditPricelistVM.Pricelist.PricelistProducts)
{
pp.Product = (from p in products where p.ProductId == pp.ProductId select p).SingleOrDefault();
There are probably more products in your PricelistProducts, with a ProductId that doesn't match with any of the products in your products list, resulting in pp.Product being set to null.
When rendering your action for each object in the Model.PricelistProducts with
html.RenderAction("_pricelistProductOptions", new { id = Model.Product.ProductId, ShowHtml = false });
it will (probably) throw a System.NullReferenceException: Object reference not set to an instance of an object. as you can't call Model.Product.ProductId, since Model.Product is null.
You are using Razor engine there (*.cshtml files)? In that case you have to use:
#Html.Action("actionName", "controllerName", new { id="myId" })
and, it seems you put name of the view there "_pricelistProductOptions", instead of action. I doubt that you have: public ActionResult _pricelistProductOptions(....) right?

How to access property of anonymous type?

Considering this IronPython script
def SensorEvent(d):
print d
print d.Message
... how do I access properties of d?
First line of the SensorEvent method successfully prints
{ Message = blah blubb }
however second line throws an exception:
'<>f_anonymousType[str]' object has no attribute 'Message'
Explanation
d is an instance of an anonymous type provided by an invoke from a C# method. I'm invoking it like this:
public static async void ExecutePyFunc(string name, dynamic data)
{
try
{
var f = strategyScope.GetVariable<Action<object>>(name);
if (f != null)
{
await Task.Run(() => f((object)data));
}
}
catch (Exception x)
{
StaticLog("[Callback Exception] Fehler beim Ausführen einer Python Funktion: {0}", x.Message);
}
}
d is a dictionary. Access it like so:
d['Message']
My solution using DynamicObject: I've introduced a class that converts an anonymous type into a known type by copying its properties via reflection (I don't need anything but the properties but it could probably be enhanced for use with fields, methods, functions as well).
Here's what I've come up with:
public class IronPythonKnownType : DynamicObject
{
public IronPythonKnownType(dynamic obj)
{
var properties = obj.GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
var val = prop.GetValue(obj);
this.Set(prop.Name, val);
}
}
private Dictionary<string, object> _dict = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (_dict.ContainsKey(binder.Name))
{
result = _dict[binder.Name];
return true;
}
return base.TryGetMember(binder, out result);
}
private void Set(string name, object value)
{
_dict[name] = value;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_dict[binder.Name] = value;
return true;
}
}
which effectively converts the anonymous object into something IronPython can handle.
Now I can do that:
def Blubb(a):
print a.Message
without getting the mentioned exception.