Web API OData custom query issue - oracle

I am new to Web API, Entity Framework and OData. I asked a similar question in another forum but haven't gotten a relevant response.
We have a OData compliant web api service for use in Salesforce. We have a custom complex query in Oracle that we need to expose.
I am not sure how to use a custom query like we want to also allow for odata parameter filtering to occur? ($filter, $top, $skip, etc) For example, when a $filter is used i want to apply that filter to the custom query and then send it back to the database to have it return the result set. How can i do this?
The issue i seem to have is that I can see the parameters as they come in but they are not translating to the query being passed to oracle. It seems that it will fire the query returning the full result set and then apply the parameters. This is very slow as the result set is very large.
I am hoping 2 figure out 2 things
1. How can i use custom sql and apply odata parameters to the underlying query?
2. When using EF or a custom query, how can i apply odata parameters to the query so that when the query is sent to the database that the $filter parameter, for example, is included in the query? I don't want the full result returned then apply the filter.
Can anyone give me some pointers on how to make this happen?
private static ODataValidationSettings _validationSettings = new ODataValidationSettings();
//public IHttpActionResult GetName()
//{ }
// GET: odata/ShareData
[ODataRoute("Orders")]
[EnableQuery(PageSize = 50)]
public IHttpActionResult GetOrders(ODataQueryOptions<Orders> queryOptions)
{
// validate the query.
try
{
queryOptions.Validate(_validationSettings);
}
catch (ODataException ex)
{
return BadRequest(ex.Message);
}
try
{
string connectionString = ConfigurationManager.ConnectionStrings["DNATestConnectionString"].ConnectionString;
var items = GetDataItems(connectionString);
return Ok<IEnumerable<Orders>>(items);
}
catch (Exception ex)
{
return StatusCode(HttpStatusCode.InternalServerError);
}
}
#region Load Data Methods
private static List<Orders> GetDataItems(string connectionString)
{
List<Orders> items = new List<Orders>();
using (OracleConnection con = new OracleConnection(connectionString))
{
con.Open();
using (OracleCommand cmd = con.CreateCommand())
{
cmd.CommandText = "select po_header_id, segment1, vendor_id, vendor_site_id from po_headers_all where vendor_id=4993";
using (OracleDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
items.Add(ToOrders(rdr));
}
}
}
return items;
}
private static Orders ToOrders(OracleDataReader rdr)
{
Orders data = new Orders();
data.VENDOR_ID = ToInt32(rdr, "VENDOR_ID");
data.VENDOR_SITE_ID = ToInt32(rdr, "VENDOR_SITE_ID");
data.PO_HEADER_ID = ToInt32(rdr, "PO_HEADER_ID");
data.SEGMENT1 = Convert.ToString(rdr["SEGMENT1"]);
return data;
}
private static int ToInt32(OracleDataReader rdr, string name)
{
int index = rdr.GetOrdinal(name);
return rdr.IsDBNull(index) ? 0 : Convert.ToInt32(rdr[index]);
}
#endregion

I don't think this is possible.
How can i use custom sql and apply odata parameters to the underlying query?
As far as I'm aware, you can't. The whole point of the OData library is that it needs to work off an IQueryable. By using custom SQL in a string like you have in your example, you can't combine it with the OData parameters that are being passed in.
One approach would be to have your custom SQL in a SQL view, then add the SQL view to your EF model in the same way as you would add a table - it will be represented as a DbSet just like tables are.
You can then get an IQueryable to represent the dataset and then apply the OData parameters as follows:
public IHttpActionResult GetOrders(ODataQueryOptions<OrdersView> queryOptions)
{
IQueryable<OrdersView> allData = // ... get the DbSet from entity framework...
// this will apply the OData query to the data set and only pull the data you want from the database
var filteredResults = queryOptions.ApplyTo(allData) as IQueryable<OrdersView>;
return Ok<IQueryable<OrdersView>>(filteredResults);
}

Related

Creating a group by parameter for the get method in a generic repository for entity framework

I am using the repository pattern with Entity Framework as described in this article: repository pattern with Entity Framework
In the part where the GenericRepository is described (Generic Repository) there is a method which is used to get entities from the database set called Get. It has an orderBy but no groupBy. I am wondering how one might implement a groupBy in the same manner as the orderBy so that you can specify which field to group by dynamically on the entity.
What I have come up with is this:
Func<IQueryable<TEntity>, IGrouping<string, TEntity>> groupBy = null
and then in the method code it should be used something like this:
if(groupBy != null)
{
query = groupBy(query).ToList();
}
But this is not compiling since the IGrouping is not queryable. Does someone know how to point me in the right direction or has a solution to this?
Edit: The reason for doing this instead of using groupby on the returned list is for performance reasons. I want the groupby to be sent as an sql statement to the database and resolved there.
Grouping has no sense without projection. So you have to define new method which returns IEnumerable with new type.
I have added sample of such method. Also removed includeProperties because EF Core ignores Includes during grouping.
Usage sample:
_orderRepostory
.GetGrouped(e => e.UserId, g => new { UserId = g.Key, Count = g.Count()});
And implementation:
public class GenericRepository<TEntity> where TEntity : class
{
... // other code
public virtual IEnumerable<TResult> GetGrouped<TKey, TResult>(
Expression<Func<TEntity, TKey>> groupingKey,
Expression<Func<IGrouping<TKey, TEntity>, TResult>> resultSelector,
Expression<Func<TEntity, bool>>? filter = null)
{
var query = dbSet.AsQueryable();
if (filter != null)
{
query = query.Where(filter);
}
return query.GroupBy(groupingKey).Select(resultSelector);
}
}

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

Can LINQ be used in Dynamics CRM to get all Accounts not in a Collection?

How can a LINQ query be written to return all Accounts where the account number is not in a List?
The list is going to be pulled from an excel document.
private bool GetAccounts()
{
List<String> accountIds = new List<String>();
accountIds.Add( "[unknown]");
var query = from accounts in context.AccountSet where !accountIds.Contains(accounts.AccountNumber) select accounts;
}
It does not have to be a List.
EDIT
This is what happens when the above query runs - Is this CRM's fault?
I don't believe you can via linq. Here is the where clause limitations from the SDK.
where =>
The left side of the clause must be an attribute name and the right side of the clause must be a value. You cannot set the left side to a constant. Both the sides of the clause cannot be constants.
Supports the String functions Contains, StartsWith, EndsWith, and Equals.
You can get around these limitations by using QueryExpression or FetchExpressions. The query you want would look like this using QueryExpression. The only thing I would mention is if you are expecting a lot of record (5000+ I believe) you will most likely need to implement paging for your function as well.
private static IEnumerable<Account> GetAccounts(IOrganizationService proxy)
{
List<String> accountIds = new List<String>(new string[]{"654321", "12345"});
var results = proxy.RetrieveMultiple(new QueryExpression(Account.EntityLogicalName)
{
ColumnSet = new ColumnSet("accountid", "name", "accountnumber"),
Criteria = new FilterExpression()
{
Conditions = { new ConditionExpression("accountnumber", ConditionOperator.NotIn, accountIds) }
}
});
return results.Entities.Select(x=> x.ToEntity<Account>());
}

PrepareResponse().AsActionResult() throws unsupported exception DotNetOpenAuth CTP

Currently I'm developing an OAuth2 authorization server using DotNetOpenAuth CTP version. My authorization server is in asp.net MVC3, and it's based on the sample provided by the library. Everything works fine until the app reaches the point where the user authorizes the consumer client.
There's an action inside my OAuth controller which takes care of the authorization process, and is very similar to the equivalent action in the sample:
[Authorize, HttpPost, ValidateAntiForgeryToken]
public ActionResult AuthorizeResponse(bool isApproved)
{
var pendingRequest = this.authorizationServer.ReadAuthorizationRequest();
if (pendingRequest == null)
{
throw new HttpException((int)HttpStatusCode.BadRequest, "Missing authorization request.");
}
IDirectedProtocolMessage response;
if (isApproved)
{
var client = MvcApplication.DataContext.Clients.First(c => c.ClientIdentifier == pendingRequest.ClientIdentifier);
client.ClientAuthorizations.Add(
new ClientAuthorization
{
Scope = OAuthUtilities.JoinScopes(pendingRequest.Scope),
User = MvcApplication.LoggedInUser,
CreatedOn = DateTime.UtcNow,
});
MvcApplication.DataContext.SaveChanges();
response = this.authorizationServer.PrepareApproveAuthorizationRequest(pendingRequest, User.Identity.Name);
}
else
{
response = this.authorizationServer.PrepareRejectAuthorizationRequest(pendingRequest);
}
return this.authorizationServer.Channel.PrepareResponse(response).AsActionResult();
}
Everytime the program reaches this line:
this.authorizationServer.Channel.PrepareResponse(response).AsActionResult();
The system throws an exception which I have researched with no success. The exception is the following:
Only parameterless constructors and initializers are supported in LINQ to Entities.
The stack trace: http://pastebin.com/TibCax2t
The only thing I've done differently from the sample is that I used entity framework's code first approach, an I think the sample was done using a designer which autogenerated the entities.
Thank you in advance.
If you started from the example, the problem Andrew is talking about stays in DatabaseKeyNonceStore.cs. The exception is raised by one on these two methods:
public CryptoKey GetKey(string bucket, string handle) {
// It is critical that this lookup be case-sensitive, which can only be configured at the database.
var matches = from key in MvcApplication.DataContext.SymmetricCryptoKeys
where key.Bucket == bucket && key.Handle == handle
select new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc());
return matches.FirstOrDefault();
}
public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) {
return from key in MvcApplication.DataContext.SymmetricCryptoKeys
where key.Bucket == bucket
orderby key.ExpiresUtc descending
select new KeyValuePair<string, CryptoKey>(key.Handle, new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc()));
}
I've resolved moving initializations outside of the query:
public CryptoKey GetKey(string bucket, string handle) {
// It is critical that this lookup be case-sensitive, which can only be configured at the database.
var matches = from key in db.SymmetricCryptoKeys
where key.Bucket == bucket && key.Handle == handle
select key;
var match = matches.FirstOrDefault();
CryptoKey ck = new CryptoKey(match.Secret, match.ExpiresUtc.AsUtc());
return ck;
}
public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) {
var matches = from key in db.SymmetricCryptoKeys
where key.Bucket == bucket
orderby key.ExpiresUtc descending
select key;
List<KeyValuePair<string, CryptoKey>> en = new List<KeyValuePair<string, CryptoKey>>();
foreach (var key in matches)
en.Add(new KeyValuePair<string, CryptoKey>(key.Handle, new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc())));
return en.AsEnumerable<KeyValuePair<string,CryptoKey>>();
}
I'm not sure that this is the best way, but it works!
It looks like your ICryptoKeyStore implementation may be attempting to store CryptoKey directly, but it's not a class that is compatible with the Entity framework (due to not have a public default constructor). Instead, define your own entity class for storing the data in CryptoKey and your ICryptoKeyStore is responsible to transition between the two data types for persistence and retrieval.

How to access data into IQueryable?

I have IQueryable object and I need to take the data inside the IQueryable to put it into Textboxs controls. Is this possible?
I try something like:
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.????
}
Update:
I'm doing this:
public IQueryable getData(String tableName, Hashtable myparams)
{
decimal id = 0;
if (myparams.ContainsKey("id") == true)
id = (decimal)myparams["id"];
Type myType= Type.GetType("ORM_Linq." + tableName + ", ORM_Linq");
return this.GetTable(tableName , "select * from Articu where id_tipo_p = '" + id + "'");
}
public IQueryable<T> GetTable<T>(System.Linq.Expressions.Expression<Func<T, bool>> predicate) where T : class
{
return _datacontext.GetTable<T>().Where(predicate);
}
This returns a {System.Data.Linq.SqlClient.SqlProvider+OneTimeEnumerable1[ORM_Linq.Articu]}`
I don't see any method like you tell me. I see Cast<>, Expression, ToString...
EDIT: Updated based on additional info from your other posts...
Your getData method is returning IQueryable instead of a strongly typed result, which is why you end up casting it. Try changing it to:
public IQueryable<ORM_Linq.Articu> getData(...)
Are you trying to query for "Articu" from different tables?
With the above change in place, your code can be rewritten as follows:
ORM_Linq.Articu result = mydata.SingleOrDefault();
if (result != null)
{
TextBoxCode.Text = result.id.ToString();
TextBoxName.Text = result.descrip;
}
If you have a single result use SingleOrDefault which will return a default value if no results are returned:
var result = mydata.SingleOrDefault();
if (result != null)
{
textbox1.text = result.ProductName; // use the column name
}
else
{
// do something
}
If you have multiple results then loop over them:
foreach (var item in mydata)
{
string name = item.ProductName;
int id = item.ProductId;
// etc..
}
First, you should be using a strongly-typed version of IQueryable. Say that your objects are of type MyObject and that MyObject has a property called Name of type string. Then, first change the parameter mydata to be of type IQueryable<MyObject>:
public void setdata (IQueryable<MyObject> mydata)
Then we can write a body like so to actually get some data out of. Let's say that we just want the first result from the query:
public void setdata (IQueryable<MyObject> mydata) {
MyObject first = mydata.FirstOrDefault();
if(first != null) {
textbox1.Text = first.Name;
}
}
Or, if you want to concatenate all the names:
public void setdata(IQueryable<MyObject> mydata) {
string text = String.Join(", ", mydata.Select(x => x.Name).ToArray());
textbo1.Text = text;
}
Well, as the name suggests, an object implementing IQueryable is... Queryable! You'll need to write a linq query to get at the internal details of your IQueryable object. In your linq query you'll be able to pull out its data and assign bits of it where ever you'd like - like your text box.
Here's a great starting place for learning Linq.
I think you find the same mental struggle when coming from FoxPro and from DataSet. Really nice, powerful string-based capabilities(sql for query, access to tables and columns name) in these worlds are not available, but replaced with a compiled, strongly-typed set of capabilities.
This is very nice if you are statically defining the UI for search and results display against a data source known at compile time. Not so nice if you are trying to build a system which attaches to existing data sources known only at runtime and defined by configuration data.
If you expect only one value just call FirstOrDefault() method.
public void setdata (IQueryable mydata)
{
textbox1.text = mydata.FirstOrDefault().PropertyName;
}

Resources