ASP.NET MVC + Dynamics NAV odata web services - how do I access related models? - asp.net-mvc

I'm building an asp.net mvc application using Dynamics NAV Odata web services. Evertyhing is working fine and I created a controller for Service Orders using Linq queries. Then I got to the next step: accessing related models, and I'm stuck.
Lets take an example using page 5900 - Service Order and page 5903 - Service Item Lines:
Getting Service Orders or any other single model works great:
var query = from c in nav.ServiceOrder
select c;
But accessing related data fails:
var query = nav.ServiceOrder
.Expand(x => x.ServiceOrderServItemLines)
.Where(x => x.No == "SO000008");
I can access ServiceOrderServItemLines with the following url:
/DynamicsNAV71/OData/Company('the company')/ServiceOrder(Document_Type='Order',No='SO000008')/ServiceOrderServItemLines
But using expand does not seem to work.
Im not sure what the problem is. Are there no relations between the models?
If so, is there a way for me to add my own models with relations, and connect them to the odata service?
Or is it just a matter of expand not being supported in the service?
Any input would be much appreciated.

It seems that your serviceOrder has two keys. one is the Document_Type and one is the No. So please try
var query = nav.ServiceOrder
.Expand(x => x.ServiceOrderServItemLines)
.Where(x => x.Document_Type == "Order" && x.No == "SO000008");
If it doesn't solve your problem, Could you please provide the url sent by the expand query?

So after beating my head against this for some time I think I have an answer. I'll post my findings here, and hopefully it will save someone some trouble in the future.
Dynamics NAV (2013 R2) have relationships between header and list items as described here:
http://blogs.msdn.com/b/freddyk/archive/2009/05/28/handling-sales-orders-from-page-based-web-services-in-nav-2009sp1-and-rtm.aspx
In my case I want to create and access Service Item Lines for a Service Order.
Using SOAP to create Service Orders (Service Header table) and Service Item Lines can be done like this:
static void Main(string[] args)
{
ServiceOrder_Binding ctx = new ServiceOrder_Binding();
ctx.UseDefaultCredentials = true;
//Create a new Service Order
ServiceOrder so = NewSo(ctx);
//Add a couple of Service Item Lines to the Service Order
for (int i = 0; i < 5; i++)
NewSil(ctx, so.No);
}
private static ServiceOrder NewSo(ServiceOrder_Binding ctx)
{
ServiceOrder so = new ServiceOrder();
so.Customer_No = "50000";
so.Description = "New Service Order";
ctx.Create(ref so);
return so;
}
private static void NewSil(ServiceOrder_Binding ctx, string documentNo)
{
ServiceOrder so = ctx.Read(documentNo);
List<Service_Order_Line> SilList = so.ServItemLines.ToList();
Service_Order_Line Sil = new Service_Order_Line();
Sil.ServiceItemNo = "20";
Sil.Description = "New Service Item Line";
SilList.Add(Sil);
so.ServItemLines = SilList.ToArray();
ctx.Update(ref so);
}
Using Odata to read Service Orders (Service Header table) and Service Item Lines can be done like this:
static void Main(string[] args)
{
NAV ctx = new NAV(new Uri("http://localhost:7048/DynamicsNAV71/OData/Company('CRONUS Sverige AB')"));
ctx.UseDefaultCredentials = true;
//Eager loading - DOES NOT WORK!
var so = from s in ctx.ServiceOrder.Expand("ServiceOrderServItemLines")
where s.No == "SO000016"
select s;
//Lazy loading - WORKS!
var so2 = from s in ctx.ServiceOrder
where s.No == "SO000016"
select s;
ctx.LoadProperty(so2.First(), "ServiceOrderServItemLines");
}
Notice that lazy loading works, but eager loading doesn't.
For other related data, like Service Items for Service Item Lines, there doesn't seem to be any relationships. I will return with an update if I find something else, but what I ended up doing for now is passing related items in the ViewBag like this:
In the Controller Action:
var service_items = from s in ctx.ServiceItemList
where s.Customer_No.Equals(customerNo)
select s;
var serviceItemList = service_items.ToList();
ViewBag.serviceItemList = new SelectList(serviceItemList, "No", "Description");
In the View:
#Html.DropDownListFor(model => model.Service_Item_No, (IEnumerable<SelectListItem>)ViewBag.serviceItemList)
Hope this helps someone who like me is new to working with Dynamics NAV and Web Services :).

Related

elastic search setup in asp.net mvc

I am working on an asp.net application where I have huge database. I want to implement elastic search. Here is what I have done in code:
var node = new Uri("http://localhost:9200/");
var setting = new ConnectionSettings(node);
setting.DefaultIndex("businessuser");
client = new ElasticClient(setting);
CanadaBusinessDBEntities db = new CanadaBusinessDBEntities();
client.DeleteIndex("businessuser", null);
var ListofBusiness = db.CanadaTables.ToList();
foreach (var Business in ListofBusiness)
{
var resutl = client.Index(Business, null);
}
This code is written in constructor which gets all records and then index them using elastic search. indexing is taking long time. I want to ask if this is correct way? I am new to elastic search. please suggest better way to do this.
Thanks.
You have to use ElasticsearchContext Class of Nest.
private readonly ElasticsearchContext _elasticsearchContext;
private const string ConnectionString = "http://localhost:9200";
private readonly IElasticsearchMappingResolver _elasticsearchMappingResolver;
_elasticsearchMappingResolver = new ElasticsearchMappingResolver();
_elasticsearchContext = new ElasticsearchContext(ConnectionString, new ElasticsearchSerializerConfiguration(_elasticsearchMappingResolver,true,true));
for Delete purpose you can use like that below
public void DeleteSkill(int Id)
{
_elasticsearchContext.DeleteDocument<Tag>(Id);
_elasticsearchContext.SaveChanges();
}

Add Pagination MVC and Azure table storage

Iam trying to apply Pagination to my MVC application. Iam using Azure table storage
Here is what I have tried:-
public List<T> GetPagination(string partitionKey, int start, int take)
{
List<T> entities = new List<T>();
TableQuery<T> query = new TableQuery<T>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey.ToLower()));
entities = Table.ExecuteQuery(query).Skip(start).Take(take).ToList();
return entities;
}
Controller:-
public ActionResult Index()
{
key= System.Web.HttpContext.Current.Request[Constants.Key];
if (String.IsNullOrEmpty(key))
return RedirectToAction("NoContext", "Error");
var items= _StorageHelper.GetPagination(key,0,3);
ItemCollection itemCollection = new ItemCollection();
itemCollection .Items= Mapper.Map<List<ItemChart>, List<ItemModel>>(items);
itemCollection .Items.ForEach(g => g.Object = g.Object.Replace(key, ""));
return View(itemCollection);
}
This currently gives me the first 3 entries from my data. Now how can I show and implement the "Previous" and "Next" to show the rest of the entries on next page? How do I implement the rest of the controller and HTML page?
Any help is appreciated.
When it comes to pagination, there are a few things to consider:
Not all LINQ operators (and in turn OData query options) are supported for Table Service. For example Skip is not supported. For a list of supported operators, please see this link: https://msdn.microsoft.com/en-us/library/azure/dd135725.aspx.
The way pagination works with Table Service is that when you query your table to fetch some data, maximum number of entities that can be returned by table service is 1000. There's no guarantee that always 1000 entities will be returned. It may be less than 1000 or even 0 depending on how you're querying. However if there are more results available, Table Service returns something called a Continuation Token. You must use this token to fetch next set of results from table service. Please see this link for more information on query timeout and pagination: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx.
Taking these two factors into consideration, you can't really implement a paging solution where a user can directly jump to a particular page (for example, user is sitting on page 1 and then the user can't go to page 4). At the most you can implement next page, previous page and first page kind of functionality.
To implement next page kind of functionality, store the continuation token returned by table service and use that in your query.
To implement previous page kind of functionality, you must store all the continuation tokens returned in an array or something and keep track of which page a user is on currently (that would be the current page index). When a user wants to go to previous page, you just get the continuation token for the previous index (i.e. current page index - 1) and use that in your query.
To implement first page kind of functionality, just issue your query without continuation token.
Do take a look at ExecuteQuerySegmented method in Storage Client Library if you want to implement pagination.
UPDATE
Please see the sample code below. For the sake of simplicity, I have only kept first page and next page functionality:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
using Microsoft.WindowsAzure.Storage.Table;
namespace TablePaginationSample
{
class Program
{
static string accountName = "";
static string accountKey = "";
static string tableName = "";
static int maxEntitiesToFetch = 10;
static TableContinuationToken token = null;
static void Main(string[] args)
{
var cloudStorageAccount = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
var cloudTableClient = cloudStorageAccount.CreateCloudTableClient();
var table = cloudTableClient.GetTableReference(tableName);
Console.WriteLine("Press \"N\" to go to next page\nPress \"F\" to go first page\nPress any other key to exit program");
var query = new TableQuery().Take(maxEntitiesToFetch);
var continueLoop = true;
do
{
Console.WriteLine("Fetching entities. Please wait....");
Console.WriteLine("-------------------------------------------------------------");
var queryResult = table.ExecuteQuerySegmented(query, token);
token = queryResult.ContinuationToken;
var entities = queryResult.Results;
foreach (var entity in entities)
{
Console.WriteLine(string.Format("PartitionKey = {0}; RowKey = {1}", entity.PartitionKey, entity.RowKey));
}
Console.WriteLine("-------------------------------------------------------------");
if (token == null)//No more token available. We've reached end of table
{
Console.WriteLine("All entities have been fetched. The program will now terminate.");
break;
}
else
{
Console.WriteLine("More entities available. Press \"N\" to go to next page or Press \"F\" to go first page or Press any other key to exit program.");
Console.WriteLine("-------------------------------------------------------------");
var key = Console.ReadKey();
switch(key.KeyChar)
{
case 'N':
case 'n':
continue;
case 'F':
case 'f':
token = null;
continue;
default:
continueLoop = false;
break;
}
}
} while (continueLoop);
Console.WriteLine("Press any key to terminate the application.");
Console.ReadLine();
}
}
}

How to operate on the table many to many EF

I am working with ASP.NET MVC and I am using Entity Framework. While generating my database which has been created by DataBase First I got this relation which is shown below. My question is: how can I assign a role for a particular patient?
This slightly depends on how your project is structured, and any design patterns you're using, but the below code should point you in the right direction.
// query the DB for existing patient/role
var dbContext = new MyDbContext()
var patient = dbContext.Set<Patient>().FirstOrDefault(x => x.PatientID = patientId);
var role = dbContext.Set<Role>().FirstOrDefault(x => x.RoleID = roleId);
patient.Roles.Add(role);
dbContext.SaveChanges();
EDIT
Or something like this for a new instance of a patient...
var newPatient = new Patient {
Name = "NameHere"
....
};
newPatient.Roles.Add(role);
dbContext.Entry(newPatient).State = System.Data.EntityState.Added;
dbContext.SaveChanges();

solrnet with StructureMap

I have a question about solrnet, and more specifically about mapping fields to C# objects.
I have the following code:
var mgr = new MappingManager();
mgr.Add(typeof(Article).GetProperty("Title"), "newsTitle");
SolrServerElement news = new SolrServerElement();
news.Id = "news";
news.DocumentType = typeof(Article).AssemblyQualifiedName;
news.Url = "http://127.0.0.1:8080/solrNews/news";
SolrServers servers = new SolrServers();
servers.Add(news);
ObjectFactory.Initialize(
x =>
{
x.AddRegistry(
new SolrNetRegistry(servers)
);
x.For<IReadOnlyMappingManager>().Use(mgr);
}
);
_solr = ObjectFactory.GetInstance<ISolrOperations<Article>>();
_solr.Ping();
It does not appear to map the solr fields to the object properties in C#. Any ideas?
IIRC StructureMap has a convention of "last registration wins", i.e. it becomes the default for the service. So if you register your IReadOnlyMappingManager after adding the SolrNetRegistry it should become the default.
In order to get the MappingManager working correctly you need to eject IReadOnlyMappingManager from ObjectFactory first of all, and then configure it to use your own MappingManager.
So it would be something like this:
var mgr = new MappingManager();
mgr.Add(typeof(Article).GetProperty("Title"), "newsTitle");
ObjectFactory.EjectAllInstancesOf<IReadOnlyMappingManager>();
ObjectFactory.Configure(x => x.For<IReadOnlyMappingManager>().Use(mgr));

Entity Framework - Disconnexted Behavior in nTier

I am new to EF but I will try my best to describe the scenario. I have 3 tables in My DB namely RecommendationTopic, Recommendation and Question. Each RecommendationTopic can have multiple Recommendations and each Recommendation may have multiple questions. Assume that I already have predefined questions in my Question table.
I have one service that returns me list of questions like below:
public List<Question> FetchQuestions(int categoryID)
{
using (Entities context = new Entities())
{
questions = context.Questions.Where(i => i.ID >= 0).ToList();
}
}
I have another service which is used to create RecommendationTopic and Recommendation whose code is something like below:
public void ManageRecommendation(RecommendationTopic recommendationTopic)
{
using (Entities context = new Entities())
{
context.AddObject("RecommendationTopics", recommendationTopic);
context.SaveChanges();
}
}
My client code looks like below:
List<Question> questions;
using (QuestionServiceClient client = new QuestionServiceClient())
{
questions = client.FetchQuestions();
}
using (RecommendationServiceClient client = new RecommendationServiceClient())
{
RecommendationTopic rTopic = new RecommendationTopic();
rTopic.CategoryID = 3;
rTopic.Name = "Topic From Client";
Recommendation rCom = new Recommendation();
rCom.Text = "Dont!";
rCom.RecommendationTopic = rTopic;
rCom.ConditionText = "Some condition";
rCom.Questions.Add(questions[0]);
rCom.Questions.Add(questions[1]);
client.ManageRecommendation(rTopic);
}
Since the client makes 2 separate service calls, the context would be different for both the calls. When I try to run this and check the EF profiler, it not only generates query to insert into RecommendationTopic and Recommendation but also Question table!
I am sure this is caused due to different context for both the calls as when I execute a similar code within a single context, it works as it's supposed to work.
Question is, how do I make it work in a disconnected scenario?
My client could be Silverlight client where I need to fill a Question drop down with a separate call and save Recommendation topic in a separate call. For this reason I am using self tracking entities as well.
Any input appreciated!
-Vinod
If you are using STEs (self tracking entities) your ManageRecommendation should look like:
public void ManageRecommendation(RecommendationTopic recommendationTopic)
{
using (Entities context = new Entities())
{
context.RecommendationTopics.ApplyChanges(recommendationTopic);
context.SaveChanges();
}
}
Calling AddObject skips self tracking behavior of your entity. If you are not using STEs you must iterate through all questions and change their state to Unchanged:
public void ManageRecommendation(RecommendationTopic recommendationTopic)
{
using (Entities context = new Entities())
{
context.RecommendationTopics.AddObject(recommendationTopic);
foreach (var question in recommendationTopic.Questions)
{
context.ObjectStateManager.ChangeObjectState(recommendationTopic, EntityState.Unchanged);
}
context.SaveChanges();
}
}

Resources