I'm trying to use breeze EntityQuery with jsonResultsAdapter to consume a third party API like this (DataService setup omitted):
var jsonResultsAdapter = new breeze.JsonResultsAdapter({
name: "eventAdapter",
extractResults: function (json) {
return json.results;
},
visitNode: function (node, queryContext, nodeContext) {
var entityTypeName = 'Event';
var entityType = entityTypeName && queryContext.entityManager.metadataStore.getEntityType(entityTypeName, true);
var propertyName = nodeContext.propertyName;
var ignore = propertyName && propertyName.substr(0, 1) === "$";
return {
entityType: entityType,
nodeId: node.$id,
nodeRefId: node.$ref,
ignore: ignore
};
}
});
var query = new breeze.EntityQuery("Events")
.using(jsonResultsAdapter);
return this.manager.executeQuery(query);
But it's returning this error: "Cannot read property '$type' of null"
I think that the EntityManager is ignoring the .using(jsonResultAdapter) statement and it's trying to use the default jsonResultsAdapter defined at breeze.js:
ctor.prototype.jsonResultsAdapter = new JsonResultsAdapter({
name: "webApi_default",
visitNode: function (node, queryContext, nodeContext ) {
var entityTypeName = EntityType._getNormalizedTypeName(node.$type);
var entityType = entityTypeName && queryContext.entityManager.metadataStore.getEntityType(entityTypeName, true);
var propertyName = nodeContext.propertyName;
var ignore = propertyName && propertyName.substr(0, 1) === "$";
return {
entityType: entityType,
nodeId: node.$id,
nodeRefId: node.$ref,
ignore: ignore
};
}
Don't know how to tell breeze to use my jsonResultsAdapter instead default one.
As of v 1.2.7, this has been fixed.
----- previous post
Ok, that's a bug. It will be fixed in the next release.
For now, I think you can use the dataService's jsonResultsAdapter instead. i.e. something like this.
var ds = new DataService( { serviceName: yourServiceName, jsonResultsAdapter: yourAdapter });
var em = new EntityManager({ dataService: ds });
var query = new breeze.EntityQuery("Events")
return em.executeQuery(query);
Related
My controller code is lke this,
public async Task<IEnumerable<CalendarEvent>> Get()
{
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "xxxxx";
var clientId = "xxxxxx";
var clientSecret = "xxxx";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes);
if (User == null!)
{
var user = await graphServiceClient.Users["xxxxx.com"].Calendar
.Events
.Request()
.Select("subject,body,bodyPreview,organizer,attendees,start,end,location")
.GetAsync();
return (CalendarEvent)user;
}
}
Iam getting an error like
Unable to cast object of type 'Microsoft.Graph.CalendarEventsCollectionPage' to type 'System.Collections.Generic.IEnumerable'
I need query that sholud be given in controller.
It's not exactly clear what you are trying to achieve but you can't convert CalendarEventsCollectionPage to IEnumerable. I am assuming that you want to return all events of specific user.
public async Task<List<Event>> GetEventsOfUser(string userId)
{
var events = new List<Event>();
var eventsPages = _client.Users[userId].Calendar.Events.Request()
.Select("subject,body,bodyPreview,organizer,attendees,start,end,location");
while (eventsPages != null)
{
var current = await eventsPages.GetAsync();
events.AddRange(current.CurrentPage);
eventsPages = current.NextPageRequest;
}
return events;
}
You need to fetch every page with NextPageRequest in order to get all events.
I am trying to execute query with parameters against local cache or server (if nothing is found in cache).
public async Task<List<T>> Get(IDictionary<string, object> parameters, string resourceName = "", FetchSource fetchSource = FetchSource.None)
{
try
{
var query = resourceName == string.Empty ? EntityQuery.From<T>().WithParameters(parameters) : EntityQuery.From<T>(resourceName).WithParameters(parameters);
var queryResult = await this.ExecuteQuery(query, fetchSource);
var result = queryResult.ToList();
return result;
}
catch (Exception e)
{
return new List<T>(); // return empty result instead
}
}
FetchSource is our enum:
public enum FetchSource
{
None = 0,
FromServer = 1,
FromCache = 2,
FromCacheOrServer = 3
}
And here is ExecuteQuery method:
protected async Task<IEnumerable<T>> ExecuteQuery(EntityQuery<T> query, FetchSource fetchSource = FetchSource.None)
{
//...
if (fetchSource == FetchSource.FromCacheOrServer)
{
var result = query.ExecuteLocally(this.EntityManager); // Throws error
if (result != null && result.Any())
{
return result;
}
return await query.Execute(this.EntityManager);
}
//...
}
When I try to execute query locally this exception is thrown:
{"Unable to cast object of type
'WhereEnumerableIterator`1[StanleySteemer.Nimbus.Client.Common.Model.Proxy.RouteOrder]'
to type
'DataServiceOrderedQuery[StanleySteemer.Nimbus.Client.Common.Model.Proxy.RouteOrder]'."}
Although I couldn't find anything in docs specifically regarding to this subject, I have implemented similar functionality in BreezeJS which was working without issue(UPDATE: it doesn't work correctly):
findWithParametersInCacheOrServer = function (parameters, recordsLimit) {
var query = breeze.EntityQuery
.from(resourceName)
.withParameters(parameters);
var r = executeCacheQuery(query);
if (r) {
if (r.length > recordsLimit) {
return Q.resolve(r);
}
}
return executeQuery(query);
};
function executeCacheQuery(query) {
return entityManagerProvider.manager().executeQueryLocally(query);
}
Data architecture in JavaScript is similar to TempHire example.
Is this a known issue? Is there any workaround for it?
Not sure I understand, neither breeze.js nor breeze.sharp can automatically perform a 'local cache query' that involves parameters. This is because the interpretation of the parameters is only really defined on the server and not on the client.
It sounds as though what you have done is define a custom implementation of your specific 'with parameters' query in breeze.js that completely bypasses Breeze's internal implementation. Is this correct?
I'm in the process of customizing validation messages. It's working fine using the messageTemplates property. However it uses %displayName% to render the name of the property and I can't find out how to override this value ? Is there anyway to do that ?
I was wanting to do this also but I wanted to use the [DisplayName] attribute from my EF model. I couldn't find anyone that had an example of doing this so after I found a way I thought I would share.
First, I extended the metadata returned from my BreezeController:
[HttpGet]
public string Metadata()
{
// Extend metadata with extra attributes
JObject metadata = JObject.Parse(contextProvider.Metadata());
string nameSpace = metadata["schema"]["namespace"].ToString();
foreach (var entityType in metadata["schema"]["entityType"])
{
string typeName = entityType["name"].ToString();
Type t = Type.GetType(nameSpace + "." + typeName);
foreach (var prop in t.GetProperties())
{
foreach (var attr in prop.CustomAttributes)
{
string name = attr.GetType().Name;
foreach (var p in entityType["property"])
{
if (prop.Name == p["name"].ToString()) {
if (attr.AttributeType.Name == "DisplayNameAttribute") {
DisplayNameAttribute a = (DisplayNameAttribute)Attribute.GetCustomAttribute(prop, typeof(DisplayNameAttribute));
p["displayName"] = a.DisplayName;
break;
}
}
}
}
}
}
return metadata.ToString();
}
Then I added a little javascript after the metadata load to poke the display names from the augmented metadata where Breeze wants to find them.
manager.fetchMetadata().then(function (md) {
angular.forEach(md.schema.entityType, function (et) {
var etype = manager.metadataStore.getEntityType(et.name);
angular.forEach(et.property, function (p) {
var prop = etype.getProperty(p.name);
prop.displayName = p.displayName;
});
});
console.log("starting app");
angular.bootstrap($("#app"), ["app"]);
});
I'm using angular so if you aren't you can ignore the angular stuff and probably get the idea. This seems to work rather nicely. It should be pretty easy to extend this to other model attributes as well like the RegularExpression validation attribute. I'll probably work on that next.
FYI, some of this code is not optimized and could probably be refactored, prettied up a bit but I just got it working and thought I would share. If anyone has any suggestions of a better way let me know. Hopefully Breeze will allow extending the metadata in a more supported way in the future. This does seem like a bit of a hack.
This is not YET well documented but you can simply set the 'displayName' property on any dataProperty and this will override the autogenerated display name and will be used for all validation messages for this property. So
var custType = myEntityManager.metadataStore.getEntityType("Customer");
var dp = custType.getProperty("companyName");
dp.displayName = "My custom display name";
Also, please see "Customize the message templates" at the bottom of this page: Breeze Validation
Following jpcoder request for suggestions, here goes my slightly improved server portion:
JObject metadata = JObject.Parse(contextProvider.Metadata());
string nameSpace = metadata["schema"]["namespace"].ToString();
foreach (var entityType in metadata["schema"]["entityType"])
{
string typeName = entityType["name"].ToString();
Type t = Type.GetType(nameSpace + "." + typeName);
IEnumerable<JToken> metaProps = null;
if (entityType["property"].Type == JTokenType.Object)
metaProps = new[] { entityType["property"] };
else
metaProps = entityType["property"].AsEnumerable();
var props = from p in metaProps
let pname = p["name"].ToString()
let prop = t.GetProperties().SingleOrDefault(prop => prop.Name == pname)
where prop != null
from attr in prop.CustomAttributes
where attr.AttributeType.Name == "DisplayNameAttribute"
select new
{
Prop = p,
DisplayName = ((DisplayNameAttribute)Attribute.GetCustomAttribute(prop, typeof(DisplayNameAttribute))).DisplayName
};
foreach (var p in props)
p.Prop["displayName"] = p.DisplayName;
}
Looking at http://www.breezejs.com/sites/all/apidocs/files/a40_entityMetadata.js.html#l1452,
could this be improved by renaming the existing "name" value to nameOnServer (to satisfy the getDataProperty call) and inserting the DisplayNameAttribute value as "name"?
A necessary change to the server code is that, if your model classes are in different assemblies, you cannot use
Type t = Type.GetType(nameSpace + "." + typeName);
You need the namespace per type (which is in the metadata), and (I think) to use BuildManager to locate the appropriate types in different assemblies. The mapping from cSpaceOSpaceMapping might be achieved more elegantly, but I didn't have time to research the different json formatting options.
JObject metadata = JObject.Parse(UnitOfWork.Metadata());
string EFNameSpace = metadata["schema"]["namespace"].ToString();
string typeNameSpaces = metadata["schema"]["cSpaceOSpaceMapping"].ToString();
typeNameSpaces = "{" + typeNameSpaces.Replace("],[", "]|[").Replace("[", "").Replace("]", "").Replace(",", ":").Replace("|", ",") + "}";
JObject jTypeNameSpaces = JObject.Parse(typeNameSpaces);
foreach (var entityType in metadata["schema"]["entityType"])
{
string typeName = entityType["name"].ToString();
string defaultTypeNameSpace = EFNameSpace + "." + typeName;
string entityTypeNameSpace = jTypeNameSpaces[defaultTypeNameSpace].ToString();
Type t = BuildManager.GetType(entityTypeNameSpace, false);
IEnumerable<JToken> metaProps = null;
if (entityType["property"].Type == JTokenType.Object)
metaProps = new[] { entityType["property"] };
else
metaProps = entityType["property"].AsEnumerable();
var props = from p in metaProps
let pname = p["name"].ToString()
let prop = t.GetProperties().SingleOrDefault(prop => prop.Name == pname)
where prop != null
from attr in prop.CustomAttributes
where attr.AttributeType.Name == "DisplayNameAttribute"
select new
{
Prop = p,
DisplayName = ((DisplayNameAttribute)Attribute.GetCustomAttribute(prop, typeof(DisplayNameAttribute))).DisplayName
};
foreach (var p in props)
{
p.Prop["displayName"] = p.DisplayName;
}
}
JObject metadata = JObject.Parse(this._context.Metadata());
string EFNameSpace = metadata["schema"]["namespace"].ToString();
string typeNameSpaces = metadata["schema"]["cSpaceOSpaceMapping"].ToString();
typeNameSpaces = "{" + typeNameSpaces.Replace("],[", "]|[").Replace("[", "").Replace("]", "").Replace(",", ":").Replace("|", ",") + "}";
JObject jTypeNameSpaces = JObject.Parse(typeNameSpaces);
foreach (var entityType in metadata["schema"]["entityType"])
{
string typeName = entityType["name"].ToString();
string defaultTypeNameSpace = EFNameSpace + "." + typeName;
string entityTypeNameSpace = jTypeNameSpaces[defaultTypeNameSpace].ToString();
Type t = BuildManager.GetType(entityTypeNameSpace, false);
IEnumerable<JToken> metaProps = null;
if (entityType["property"].Type == JTokenType.Object)
metaProps = new[] { entityType["property"] };
else
metaProps = entityType["property"].AsEnumerable();
var props = from p in metaProps
let pname = p["name"].ToString()
let prop = t.GetProperties().SingleOrDefault(prop => prop.Name == pname)
where prop != null
from attr in prop.CustomAttributes
where attr.AttributeType.Name == "DisplayAttribute"
select new
{
Prop = p,
DisplayName = ((DisplayAttribute)Attribute.GetCustomAttribute(prop, typeof(DisplayAttribute))).Name
};
foreach (var p in props)
{
p.Prop["displayName"] = p.DisplayName;
}
}
return metadata.ToString();
Improving jpcoder's answer ...
For me most of my DisplayName changes were to replace "PascalCaseFieldName" or "camelCaseFieldName" with "Upper Case Field Name". So rather than set every property DisplayName on the server, I applied a default function to set displayName.
End result was much less EF annotation required. My TypeScript is:
manager.metadataStore.getEntityTypes().forEach(function (storeEntityType) {
if (!(storeEntityType instanceof breeze.EntityType)) {
throw new Error("loadExtendedMetadata found '" + storeEntityType
+ "' StructuralType that is not an EntityType (e.g. a ComplexType)");
}
var extEntityType = extendedMetadata.entitiesExtended.find((extendedEntityType) => {
return extendedEntityType.shortName + ":#" + extendedEntityType.nameSpace === storeEntityType.name;
});
(storeEntityType as breeze.EntityType).getProperties().forEach((storeProperty) => {
//Both NavigationProperty & DataProperty have displayName & nameOnServer properties
var storeDataProperty = <breeze.DataProperty>storeProperty;
var extProperty;
if (extEntityType) {
extProperty = extEntityType.propertiesExtented.find((extendedProperty) => {
return extendedProperty.name === storeDataProperty.nameOnServer;
});
}
//Smart default: nameOnServer "PascalCaseFieldName" or "camelCaseFieldName" converted to "Upper Case Field Name"
storeDataProperty.displayName = (extProperty && extProperty.displayName)
|| storeDataProperty.nameOnServer.replace(/^./, function (str) {
// first ensure the first character is uppercase
return str.toUpperCase();
// insert a space before all caps, remove first character (added space)
}).replace(/([A-Z])/g, " $1").substring(1);
});
});
Below is the code from the DotNetNuke Sample module that gets a collection of items from the database that belong to a particular module. What I want is add a second parameter for it filter by. I'm guessing this has something to do with modifying the scope item.cs class but am not sure how exactly.
public IEnumerable<Item> GetItems(int moduleId)
{
IEnumerable<Item> t;
using (IDataContext ctx = DataContext.Instance())
{
var rep = ctx.GetRepository<Item>();
t = rep.Get(moduleId);
}
return t;
}
Any ideas?
Another way to do it in DAL2 is using the .Find() method. This is good if you want to query on an indexed field in your table and you don't care about caching scope:
public IEnumerable<Item> GetItemByName(int moduleId, string itemname)
{
IEnumerable<Item> t;
using (IDataContext ctx = DataContext.Instance())
{
var rep = ctx.GetRepository<Item>();
t = rep.Find("WHERE ModuleId = #0 AND ItemName LIKE #1", moduleId, itemname);
}
return t;
}
Here's some sample code from my SignalRChat module that uses DAL2 (http://signalrchat.codeplex.com/SourceControl/changeset/view/71473#1272188)
public IEnumerable<Message> GetRecentMessages(int moduleId, int hoursBackInTime, int maxRecords)
{
var messages = (from a in this.GetMessages(moduleId) where a.MessageDate.Subtract(DateTime.UtcNow).TotalHours <= hoursBackInTime select a).Take(maxRecords).Reverse();
return messages.Any() ? messages : null;
}
That is one approach, you can also use a SQL statement within the controller as well (http://signalrchat.codeplex.com/SourceControl/changeset/view/71473#1272186)
public ConnectionRecord GetConnectionRecordByConnectionId(string connectionId)
{
ConnectionRecord t;
using (IDataContext ctx = DataContext.Instance())
{
var connections = ctx.ExecuteQuery<ConnectionRecord>(CommandType.Text,
string.Format(
"select top 1 * from {0}{1}SignalRChat_ConnectionRecords where ConnectionId = '{2}'",
_databaseOwner,
_objectQualifier,
connectionId)).ToList();
if (connections.Any())
{
t = connections[0];
}
else
return null;
}
return t;
}
I want to create a method which can takes the properties I possibly may update and leaving those not interested untouched.
Here is what I did:
public static void updateTable(int id, string field1, string field2, string field3){
using(var context = new Entities()){
var obj = context.Table.Where(x=>x.id == id).FirstOrDefault();
if(obj != null){
obj.field1 = field1;
...
obj.SaveChanges();
}
}
}
But in this pattern, I need to pass all 4 parameters into the method even I just want to update only one field. Is there any generic solution to update only the fields I passed in?
I came up something like this:
public static void updateTable(int id, object data_json){
using(var context = new Entities()){
var obj = context.Table.Where(x=>x.id == id).FirstOrDefault();
if(obj != null){
if(data_json['field1']!=null) //something like this
obj.field1 = data_json['field1'];
...
obj.SaveChanges();
}
}
}
But this can't handle the case that I do want to set a field to be null. Or is there any better solution?
If you don't care about updating relationships, you can use ApplyCurrentValues, which only updates the scalar properties.
E.g:
public static void updateTable(int id, object data_json){
using(var context = new Entities()) {
var obj = context.Table.Where(x=>x.id == id).FirstOrDefault();
context.ApplyCurrentValues("Table", data_json);
}
}
It assumes an entity with the same key is already attached in the graph. In this case, the query for var obj will ensure the object is in the graph, then it's contents are overridden with the scalar properties on the supplied object.
You might need an explicit cast on data_json to ensure it is of the same type contained in the entity set.
Using an ExpandoObject would allow you to send in only the properties you want to set, and would allow you to specify null values as well.
For example:
public static void updateTable(int id, dynamic data){
using(var context = new Entities()){
var obj = context.Table.Where(x=>x.id == id).FirstOrDefault();
if(obj != null){
if (((IDictionary<string, object>)data).ContainsKey("field1"))
obj.field1 = data.field1;
...
obj.SaveChanges();
}
}
}
and you could call it like this:
dynamic data = new ExpandoObject();
data.field1 = 123;
data.field2 = null;
data.field5 = "abc";
MyClass.updateTable(1, data);
Everything can be solved with a moment of reflection. This function solves the problem:
public void UpdateTable(int id, object values)
{
using (var entities = new MyEntities())
{
var valuesType = values.GetType();
var element = entities.MyTable.Where(t => t.ID == id).First();
//We are iterating through all properties of updated element and checking
//if there is value provided for there properties in values parameter
foreach (var property in element.GetType().GetProperties())
{
var valuesProperty = valuesType.GetProperty(property.Name);
//If values contain this property
if (valuesProperty != null)
{
//taking value out of values parameter
var value = valuesProperty.GetValue(values, null);
//setting it in our element to update
property.SetValue(element, value, null);
}
}
entities.SaveChanges();
}
}
Usage:
UpdateTable(125, new { FieldA = 1, FieldB = "ABCD" });
You can even make this method more universal by adding generic table type parameter.