The best way to modify a WebAPI OData QueryOptions.Filter - asp.net-web-api

I am using the OData sample project at http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/working-with-entity-relations. In the Get I want to be able to change the Filter in the QueryOptions of the EntitySetController:
public class ProductsController : EntitySetController<Product, int>
{
ProductsContext _context = new ProductsContext();
[Queryable(AllowedQueryOptions=AllowedQueryOptions.All)]
public override IQueryable<Product> Get()
{
var products = QueryOptions.ApplyTo(_context.Products).Cast<Product>();
return products.AsQueryable();
}
I would like to be able to find properties that are specifically referred to. I can do this by parsing this.QueryOptions.Filter.RawValue for the property names but I cannot update the RawValue as it is read only. I can however create another instance of FilterQueryOption from the modified RawValue but I cannot assign it to this.QueryOptions.Filter as this is read only too.
I guess I could call the new filter's ApplyTo passing it _context.Products, but then I will need to separately call the ApplyTo of the other properties of QueryOptions like Skip and OrderBy. Is there a better solution than this?
Update
I tried the following:
public override IQueryable<Product> Get()
{
IQueryable<Product> encryptedProducts = _context.Products;
var filter = QueryOptions.Filter;
if (filter != null && filter.RawValue.Contains("Name"))
{
var settings = new ODataQuerySettings();
var originalFilter = filter.RawValue;
var newFilter = ParseAndEncyptValue(originalFilter);
filter = new FilterQueryOption(newFilter, QueryOptions.Context);
encryptedProducts = filter.ApplyTo(encryptedProducts, settings).Cast<Product>();
if (QueryOptions.OrderBy != null)
{
QueryOptions.OrderBy.ApplyTo<Product>(encryptedProducts);
}
}
else
{
encryptedProducts = QueryOptions.ApplyTo(encryptedProducts).Cast<Product>();
}
var unencryptedProducts = encryptedProducts.Decrypt().ToList();
return unencryptedProducts.AsQueryable();
}
and it seems to be working up to a point. If I set a breakpoint I can see my products in the unencryptedProducts list, but when the method returns I don't get any items. I tried putting the [Queryable(AllowedQueryOptions=AllowedQueryOptions.All)] back on again but it had no effect. Any ideas why I am not getting an items?
Update 2
I discovered that my query was being applied twice even though I am not using the Queryable attribute. This meant that even though I had items to return the List was being queried with the unencrypted value and therefore no values were being returned.
I tried using an ODataController instead:
public class ODriversController : ODataController
{
//[Authorize()]
//[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Products> Get(ODataQueryOptions options)
{
and this worked! Does this indicate that there is a bug in EntitySetController?

You would probably need to regenerate ODataQueryOptions to solve your issue. Let's say if you want to modify to add $orderby, you can do this like:
string url = HttpContext.Current.Request.Url.AbsoluteUri;
url += "&$orderby=name";
var request = new HttpRequestMessage(HttpMethod.Get, url);
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Product>("Product");
var options = new ODataQueryOptions<Product>(new ODataQueryContext(modelBuilder.GetEdmModel(), typeof(Product)), request);

Related

How to get a standalone / unmanaged RealmObject using Realm Xamarin

Is there a way that when I read an object from Realm that it can become a standalone or unmanaged object? In EF, this is called no tracking. The usage for this would be when I want to implement more business logic on my data objects before they are updated on the persistent data storage. I may want to give the RealmObject to a ViewModel, but when the changes come back from the ViewModel, I want to compare the disconnected object to the object in the datastore to determine what was changed, so If there was a way that I could disconnect the object from Realm when I give it to the ViewModel, then I can better manage what properties have changed, using my biz logic to do what I need, then save the changes back to realm.
I understand Realm does a lot of magic and many people will not want to add a layer like this but in my app, I cant really have the UI directly updating the datastore, unless there is a event that is raised that I can subscribe too and then attach my business logic this way.
I only saw one event and it does not appear to perform this action.
Thanks for your assistance.
First, get json NUGET :
PM> Install-Package Newtonsoft.Json
And, try this "hack" :
Deserialize the modified IsManaged property does the tricks.
public d DetachObject<d>(d Model) where d : RealmObject
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<d>(
Newtonsoft.Json.JsonConvert.SerializeObject(Model)
.Replace(",\"IsManaged\":true", ",\"IsManaged\":false")
);
}
.
If you facing slow-down on JsonConvert:
According to source code
, the 'IsManaged' property only has get accessor and return true when private field _realm which is available
So, we has to set the instance of field _realm to null does the tricks
public d DetachObject<d>(d Model) where d : RealmObject
{
typeof(RealmObject).GetField("_realm",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
.SetValue(Model, null);
return Model.IsManaged ? null : Model;
}
.
You will get empty RealmObject body after Realm are now implemented same strategy as LazyLoad
Record down live RealmObject and (deactivate) realm instance in object by Reflection. And set back recorded values to RealmObject. With handled all the ILists inside too.
public d DetachObject<d>(d Model) where d : RealmObject
{
return (d)DetachObjectInternal(Model);
}
private object DetachObjectInternal(object Model)
{
//Record down properties and fields on RealmObject
var Properties = Model.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
.Where(x => x.Name != "ObjectSchema" && x.Name != "Realm" && x.Name != "IsValid" && x.Name != "IsManaged" && x.Name != "IsDefault")
.Select(x =>(x.PropertyType.Name == "IList`1")? ("-" + x.Name, x.GetValue(Model)) : (x.Name, x.GetValue(Model))).ToList();
var Fields = Model.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
.Select(x => (x.Name, x.GetValue(Model))).ToList();
//Unbind realm instance from object
typeof(RealmObject).GetField("_realm", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(Model, null);
//Set back the properties and fields into RealmObject
foreach (var field in Fields)
{
Model.GetType().GetField(field.Item1, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).SetValue(Model, field.Item2);
}
foreach (var property in Properties.OrderByDescending(x=>x.Item1[0]).ToList())
{
if (property.Item1[0] == '-')
{
int count = (int)property.Item2.GetType().GetMethod("get_Count").Invoke(property.Item2, null);
if (count > 0)
{
if (property.Item2.GetType().GenericTypeArguments[0].BaseType.Name == "RealmObject")
{
for (int i = 0; i < count; i++)
{
var seter = property.Item2.GetType().GetMethod("set_Item");
var geter = property.Item2.GetType().GetMethod("get_Item");
property.Item2.GetType().GetField("_realm", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public).SetValue(property.Item2, null);
DetachObjectInternal(geter.Invoke(property.Item2, new object[] { i }));
}
}
}
}
else
{
Model.GetType().GetProperty(property.Item1, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).SetValue(Model, property.Item2);
}
}
return Model;
}
.
For List of the RealmObject , Using Select():
DBs.All<MyRealmObject>().ToList().Select(t => DBs.DetachObject(t)).ToList();
.
(Java)You dont need this if youre in java:
Maybe some day, this feature will come to .NET Realm
Realm.copyFromRealm();
#xamarin #C# #Realm #RealmObject #detach #managed #IsManaged #copyFromRealm
Until its added to Realm for Xamarin, I added a property to my Model that creates a copy of the object. This seems to work for my use. The TwoWay Binding error messages are now also not an issue. For a more complicated application, I don't want to put business or data logic in the ViewModel. This allows all the Magic of xamarin forms to work and me to implement logic when its finally time to save the changes back to realm.
[Ignored]
public Contact ToStandalone()
{
return new Contact()
{
companyName = this.companyName,
dateAdded = this.dateAdded,
fullName = this.fullName,
gender = this.gender,
website = this.website
};
}
However, If there are any relationships this method does not work for the relationships. Copying the List is not really an option either as the relationship cant exist if the object is not attached to Realm, I read this some where, can't find it to ref now. So I guess we will be waiting for the additions to the framework.
Not currently in the Xamarin interface but we could add it. The Java interface already has copyFromRealm which performs a deep copy. That also has a paired merging copyToRealmOrUpdate.
See Realm github issue for further discussion.
However, as a design issue, is this really meeting your need in an optimal way?
I have used converters in WPF apps to insert logic into the binding - these are available in Xamarin Forms.
Another way in Xamarin forms is to use Behaviours, as introduced in the blog article and covered in the API.
These approaches are more about adding logic between the UI and ViewModel, which you could consider as part of the ViewModel, but before updates are propagated to bound values.
After wasting too much time in 3rd party libraries like AutoMapper, I've created my own extension function which works pretty well. This function simply uses Reflection with the recession. (Currently, Only for List type. You can extend the functionality for Dictionary and other types of the collection very easily or you can completely modify the functionality based on your own requirements.).
I didn't do too much time and complexity analysis. I've tested only for my test case which contains many nested RealmObject, built from a 3500+ line of JSON object, took only 15 milliseconds to clone the object.
Here the complete extension available via Github Gist. If you want to extend the functionality of this extension please update this Github Gist, So, other developers can take advantage of it.
Here the complete extension -
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Realms;
namespace ProjectName.Core.Extensions
{
public static class RealmExtension
{
public static T Clone<T>(this T source) where T: new()
{
//If source is null return null
if (source == null)
return default(T);
var target = new T();
var targetType = typeof(T);
//List of skip namespaces
var skipNamespaces = new List<string>
{
typeof(Realm).Namespace
};
//Get the Namespace name of Generic Collection
var collectionNamespace = typeof(List<string>).Namespace;
//flags to get properties
var flags = BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance;
//Get target properties list which follows the flags
var targetProperties = targetType.GetProperties(flags);
//if traget properties is null then return default target
if (targetProperties == null)
return target;
//enumerate properties
foreach (var property in targetProperties)
{
//skip property if it's belongs to namespace available in skipNamespaces list
if (skipNamespaces.Contains(property.DeclaringType.Namespace))
continue;
//Get property information and check if we can write value in it
var propertyInfo = targetType.GetProperty(property.Name, flags);
if (propertyInfo == null || !property.CanWrite)
continue;
//Get value from the source
var sourceValue = property.GetValue(source);
//If property derived from the RealmObject then Clone that too
if (property.PropertyType.IsSubclassOf(typeof(RealmObject)) && (sourceValue is RealmObject))
{
var propertyType = property.PropertyType;
var convertedSourceValue = Convert.ChangeType(sourceValue, propertyType);
sourceValue = typeof(RealmExtension).GetMethod("Clone", BindingFlags.Static | BindingFlags.Public)
.MakeGenericMethod(propertyType).Invoke(convertedSourceValue, new[] { convertedSourceValue });
}
//Check if property belongs to the collection namespace and original value is not null
if (property.PropertyType.Namespace == collectionNamespace && sourceValue != null)
{
//get the type of the property (currently only supported List)
var listType = property.PropertyType;
//Create new instance of listType
var newList = (IList)Activator.CreateInstance(listType);
//Convert source value into the list type
var convertedSourceValue = Convert.ChangeType(sourceValue, listType) as IEnumerable;
//Enumerate source list and recursively call Clone method on each object
foreach (var item in convertedSourceValue)
{
var value = typeof(RealmExtension).GetMethod("Clone", BindingFlags.Static | BindingFlags.Public)
.MakeGenericMethod(item.GetType()).Invoke(item, new[] { item });
newList.Add(value);
}
//update source value
sourceValue = newList;
}
//set updated original value into the target
propertyInfo.SetValue(target, sourceValue);
}
return target;
}
}
}

OData Get method returns Complex Type

I'm just getting started with OData using Asp.Net Web API.
I declare in WebApiConfig.cs
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Staff>("staffs");
return builder.GetEdmModel();
In StaffsController.cs
public class StaffsController : ODataController
{
UnitOfWork unitOfWork;
public StaffsController()
{
this.unitOfWork = new UnitOfWork();
}
[EnableQuery]
public IHttpActionResult Get()
{
var q = this.unitOfWork.StaffRepository.Data.Select(p => new { p.Name, p.Id, p.Ext });
// Or Grouping here to return a Complex Type
return Ok(q);
}
}
It return 406 error, but if I change
var q = this.unitOfWork.StaffRepository.Data.Select(p => new { p.Name, p.Id, p.Ext });
// Or Grouping here to return a Complex Type
to
var q = this.unitOfWork.StaffRepository.Data;
It works.
I've search on Google for a while but still found nothing. Could you help me out if we can do that or we have other ways to reach that?
The type that you pass into the generic parameter for builder.EntitySet - in your case Staff - needs to match the object that you return from the Get method. Because you used Staff, the variable q needs to be of type IQueryable<Staff>.
The Data property is of this type so it works, but the anonymous type for the version that isn't working obviously isn't so it doesn't work.
Following on from your comment
I want get Staff information list with some statistic data
In order to add different properties to the response, you need to create a new object type that you pass into the builder.EntitySet generic parameter and then create it in Select statement in the controller.
However, what you are doing in the example, seems to be just returning properties that are already on the Staff object. You could use an OData $select statement to do this. For example:
http://yoururl/staffs?$select=Name,Id,Ext

Linq in C# using IEnumerable

Appearently, I got an error if using the following code. It said:
Cannot implicity converrt type System.Linq.IQueryable<AnonymousType> to System.Collection.Generic.IEnumerable.
Please advise how I can fix this?
public IEnumerable<Session> GetAllListDetailConsumer(string refId)
{
ObjectQuery<Session> sessions = db.Sessions;
ObjectQuery<SessionsList> sessionsLists = db.SessionsList;
var query =
from s in sessions
join sList in sessionsLists on s.ReferralListID equals sList.ReferralListID
where s.ReferralListID == new Guid(refId)
select new SessionConsumerList
{
ReferralListID = s.ReferralListID,
SessionServerId = s.SessionServerID,
ApplicationID = s.ApplicationID,
// ...
ConsumerID = sList.ConsumerID,
ConsumerFirstName = sList.ConsumerFirstName,
ConsumerFamilyName = sList.ConsumerFamilyName,
// ...
};
return query.ToList();
}
You are selecting using select new, which would create an anonymous type, you need to project to class Session in your query like.
select new Session
{
....
But remember if your Session class is a representing a table in your database/data context, then you can't project to that class, instead you may have to create a temporary class and project the selection to that class.
EDIT (Since the question now has been edited)
Now you are selecting new SessionConsumerList and you are returning IEnumerable<Session>, you need to modify method signature to return IEnumerable<SessionConsumerList>
Why not separate the creation of the SessionConsumerList in another method? Makes the code a lot cleaner. Like this:
public static SessionConsumerList CreateSessionConsumerList(
Session s,
SessionsList sList)
{
return new SessionConsumerList
{
ReferralListID = s.ReferralListID,
SessionServerId = s.SessionServerID,
ApplicationID = s.ApplicationID,
// ...
ConsumerID = sList.ConsumerID,
ConsumerFirstName = sList.ConsumerFirstName,
ConsumerFamilyName = sList.ConsumerFamilyName,
// ...
};
}
And then:
var query =
from s in sessions
join sList in sessionsLists on s.ReferralListID equals sList.ReferralListID
where s.ReferralListID == new Guid(refId)
select CreateSessionConsumerList(s, sList);

Add Linq statement to a Web API Get with a filter, i'm trying to add $select

I trying to apply some linq statements to all my Get Web api commands. I figured I could do this using an ActionFilterAttribute.
I'm basically adding $select support in web api since its currently not supported. I'm not sure where to get the IQueryable results. I believe I need it before sql execution happens but after Get function has returned the IQueryable result. Any help would be great. I'm trying something similiar to this post, but his idea will not work because HttpResponseMessage response = actionExecutedContext.Result; is no longer in RC.
Thanks
Nick
solution
public override void OnActionExecuted(System.Web.Http.Filters.HttpActionExecutedContext actionExecutedContext)
{
HttpRequestMessage request = actionExecutedContext.Request;
HttpResponseMessage response = actionExecutedContext.Response;
IQueryable obj;
if (response != null && response.TryGetContentValue(out obj) && request.RequestUri.ParseQueryString()["$select"] != null)
{
System.Collections.Specialized.NameValueCollection QueryItems = request.RequestUri.ParseQueryString();
string select = QueryItems["$select"];
if (!string.IsNullOrWhiteSpace(select))
{
obj = obj.Select(string.Format("new ({0})", select));
}
//
//this should be generic not hard coded for Json
//
string json = JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented);
actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse();
actionExecutedContext.Response.Content = new StringContent(json);
actionExecutedContext.Response.Content.Headers.Clear();
actionExecutedContext.Response.Content.Headers.Add("Content-Type", "application/json");
actionExecutedContext.Response.StatusCode = System.Net.HttpStatusCode.OK;
}
}
see the original post above. I added the solution to the bottom.

Subsonic Single WHERE clause

Is it possible to apply a WHERE clause on a SubSonic query?
For example, I get get a single based on id...
db.Single<Storage>(id);
But how can I get a single based on a simple WHERE clause?
db.Single<Storage>(WHERE columnname == "value");
that's possible:
// Will return a Storage instance with property IsNew = true, if record does not exist
// since an object created with new never can be null
var storage1 = new Storage(1); // id = 1
var storage1 = new Storage(Storag.Columns.ColumnName, "value");
// Will return 0 if record not found (subsonic3 only)
var storage3 = (from s in Storage
where s.ColumnName == "value"
select s).SingleOrDefault();
// Will throw an exception if record not found (subsonic3 only)
var storage3 = (from s in Storage
where s.ColumnName == "value"
select s).Single();
Since db is a partial class you can extend it. Just create a new File within the same namespace (but another folder in your solution). This applies to subsonic 2 but will be similar to subsonic 3, I think.
public static partial class DB
{
public static T Single<T>(String columName, Object columnValue) where T: RecordBase<T>, new()
{
return Select().From<T>()
.Where(columnName).IsEqualTo(columnValue)
.ExecuteSingle<T>();
}
}
Thanks for the above, this was a help and eventually I simplified this to the below...
db.Single<Storage>(s => s.ColumnName == "value");

Resources