I know that "join" is not supported on client side with WCF DS thats why i decided to add a method on server side to do the "join" and return the result as custom type object.
Service looks like this:
public class CWcfDataService : DataService<CEntities>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.RegisterKnownType(typeof(CASE_STAGE_HISTORY_EXTENDED));
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
[WebGet]
public IQueryable<CASE_STAGE_HISTORY_EXTENDED> GetCASE_STAGE_HISTORY_EXTENDEDByDocId(int docId)
{
CEntities context = new CEntities();
return (from c in context.CASE_STAGE_HISTORY
join a in context.USRs on c.CREATOR_ID equals a.USRID
select new CASE_STAGE_HISTORY_EXTENDED()
{
CASE_STAGE_ID = c.CASE_STAGE_HISTORY_ID,
CASE_STAGE_NAME = c.CASE_STAGE_NAME,
CREATE_DATE = c.CREATE_DATE,
CREATOR_ID = c.CREATOR_ID,
DOC_ID = c.DOC_ID,
LAST_VARIANT_DOCUMENT_ID = c.LAST_VARIANT_DOCUEMENT_ID,
CREATOR_FULLNAME = a.FULLNAME
});
}
}
And custom class is:
[DataServiceKey("CASE_STAGE_ID")]
public class CASE_STAGE_HISTORY_EXTENDED
{
public int CASE_STAGE_ID { get; set; }
public int DOC_ID { get; set; }
public string CASE_STAGE_NAME { get; set; }
public int? LAST_VARIANT_DOCUMENT_ID { get; set; }
public DateTime? CREATE_DATE { get; set; }
public int? CREATOR_ID { get; set; }
public string CREATOR_FULLNAME { get; set; }
}
When i try to update service reference in Visual Studio i constantly get error:
The server encountered an error
processing the request. The exception
message is 'Unable to load metadata
for return type
'System.Linq.IQueryable1[CWcf.Code.CASE_STAGE_HISTORY_EXTENDED]'
of method
'System.Linq.IQueryable1[CWcf.Code.CASE_STAGE_HISTORY_EXTENDED]
GetCASE_STAGE_HISTORY_EXTENDEDByDocId(Int32)'.'.
See server logs for more details.
If i remove the public IQueryable<CASE_STAGE_HISTORY_EXTENDED> GetCASE_STAGE_HISTORY_EXTENDEDByDocId(int docId) part - on updating service reference i get another error:
The server encountered an error
processing the request. The exception
message is 'Internal Server Error. The
type
'CourtWcf.Code.CASE_STAGE_HISTORY_EXTENDED'
is not a complex type or an entity
type.'.
Environment: Visual Studio 2010, .NET 4.
I assume the data service is based on an Entity Framework model (the CEntities class is an ObjectContext).
If that's the case the types are completely read from the EF model (CSDL) and the class definitions are more or less ignored. So you need to define the type returned by your service operation in your EF model. Also note that for the IQueryable to work, that type has to be recognized by EF as an entity type (which might require mapping to the database, or something, EF experts will know more).
Added foreign keys and implemented LoadProperty. See the article from which i took this solution: http://thedatafarm.com/blog/data-access/the-cost-of-eager-loading-in-entity-framework/
Still if i have NO relations in database(for example i have no foreign keys)-i have another solution: create stored procedure and map it with import in your DB model. After that create complex type that will work in client too(tho you will have to access it by URI, not with lambda extensions). See example here. Many thanks for other answerers in this thread, you have made me look deeper into the subject.
For starters, WCF does not support IQueryable. That's your problem right there.
In your case IEnumerable should work.
You should look at a service as something that has methods and returns "data". This data could be single values, object instances or collections of objects. The client should not have to think about call a service to do a join, but rather, "give me the data for such and such - given these parameters".
You method name imparts the correct "intent" GetCaseStageHistoryExtendedByDocId(int docId), and what you get back is a collection of CASE_STAGE_HISTORY_EXTENDED objects. That's it.
An IQueryable implies something completely different and that concept does not pertain to a service as such.
EDIT
Try converting your Queryable to a List by calling the ToList() method on it.
return (from c in context.CASE_STAGE_HISTORY
join a in context.USRs on c.CREATOR_ID equals a.USRID
select new CASE_STAGE_HISTORY_EXTENDED()
{
CASE_STAGE_ID = c.CASE_STAGE_HISTORY_ID,
CASE_STAGE_NAME = c.CASE_STAGE_NAME,
CREATE_DATE = c.CREATE_DATE,
CREATOR_ID = c.CREATOR_ID,
DOC_ID = c.DOC_ID,
LAST_VARIANT_DOCUMENT_ID = c.LAST_VARIANT_DOCUEMENT_ID,
CREATOR_FULLNAME = a.FULLNAME
}).ToList();
Note that you can only send serializable objects across to the client. So first make sure your objects are serializable.
Related
I am stumped on how to save/pass MongoDB UpdateDefinition for logging and later use
I have created general functions for MongoDB in Azure use on a collection for get, insert, delete, update that work well.
The purpose is to be able to have a standard, pre-configured way to interact with the collection. For update especially, the goal is to be able to flexibly pass in an appropriate UpdateDefinition where that business logic is done elsewhere and passed in.
I can create/update/set/combine the UpdateDefinition itself, but when i try to log it by serializing it, it shows null:
JsonConvert.SerializeObject(updateDef)
When I try to log it, save it to another a class or pass it to another function it displays null:
public class Account
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
}
var updateBuilder = Builders<Account>.Update;
var updates = new List<UpdateDefinition<Account>>();
//just using one update here for brevity - purpose is there could be 1:many depending on fields updated
updates.Add(updateBuilder.Set(a => a.Email, email));
//Once all the logic and field update determinations are made
var updateDef = updateBuilder.Combine(updates);
//The updateDef does not serialize to string, it displays null when logging.
_logger.LogInformation("{0} - Update Definition: {1}", actionName, JsonConvert.SerializeObject(updateDef));
//Class Created for passing the Account Update Information for Use by update function
public class AccountUpdateInfo
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Update")]
public UpdateDefinition<Account> UpdateDef { get; set; }
}
var acct = new AccountUpdateInfo();
acctInfo.UpdateDef = updateDef
//This also logs a null value for the Update Definition field when the class is serialized.
_logger.LogInformation("{0} - AccountUpdateInfo: {1}", actionName, JsonConvert.SerializeObject(acct));
Any thoughts or ideas on what is happening? I am stumped on why I cannot serialize for logging or pass the value in a class around like I would expect
give this a try:
var json = updateDef.Render(
BsonSerializer.SerializerRegistry.GetSerializer<Account>(),
BsonSerializer.SerializerRegistry)
.AsBsonDocument
.ToString();
and to turn a json string back to an update definition (using implicit operator), you can do:
UpdateDefinition<Account> updateDef = json;
this is off the top of my head and untested. the only thing i'm unsure of (without an IDE) is the .Document.ToString() part above.
I'm learning MongoDB and I want to try it with using C#. Is it possible to operate on strongly typed MongoDB documents using C# official MongoDB driver?
I have classes Album and Photo:
public class Album : IEnumerable<Photo>
{
[Required]
[BsonElement("name")]
public string Name { get; set; }
[Required]
[BsonElement("description")]
public string Description { get; set; }
[BsonElement("owner")]
public string Owner { get; set; }
[BsonElement("title_photo")]
public Photo TitlePhoto { get; set; }
[BsonElement("pictures")]
public List<Photo> Pictures { get; set; }
//rest of the class code
}
public class Photo : IEquatable<Photo>
{
[BsonElement("name")]
public string Name;
[BsonElement("description")]
public string Description;
[BsonElement("path")]
public string ServerPath;
//rest of the class code
}
I want to insert a new document into a collection albums in the test database. I don't want to operate on BsonDocument, but I would prefer to use strongly typed Album. I thought it would be something like:
IMongoClient client = new MongoClient();
IMongoDatabase db = client.GetDatabase("test");
IMongoCollection<Album> collection = database.GetCollection<Album>("album");
var document = new Album
{
Name = album.Name,
Owner = HttpContext.Current.User.Identity.Name,
Description = album.Description,
TitlePhoto = album.TitlePhoto,
Pictures = album.Pictures
};
collection.InsertOne(document);
But it gives me the following error:
An exception of type 'MongoDB.Driver.MongoCommandException' occurred
in MongoDB.Driver.Core.dll but was not handled in user code
Additional information: Command insert failed: error parsing element 0
of field documents :: caused by :: wrong type for '0' field, expected
object, found 0: [].
What am I doing wrong and if it's possible to achieve?
It looks like the driver is treating your object as a BSON array because it implements IEnumerable<Photo>. The database is expecting a BSON document instead. You'll get a similar error if you try to insert, for example, an Int32 into a collection.
Unfortunately, I don't know how to configure the serializer to treat your Album object as a BSON document. The static BsonSerializer.SerializerRegistry property shows the driver is choosing to use EnumerableInterfaceImplementerSerializer<Album,Photo> as Album's serializer by default.
Removing the IEnumerable<Photo> implementation from Album causes the driver to serialize with BsonClassMapSerializer<Album>, producing a BSON document. While it works, the downside is Album is no longer enumerable; application consumers will need to enumerate the Pictures property.
Adding the IEnumerable<Photo> implementation back in, then forcing the aforementioned serializer (using the [BsonSerializer(typeof(BsonClassMapSerializer<Album>))] attribute) results in:
System.MissingMethodException: No parameterless constructor defined for this object.
Based on the stack trace (referring to BsonSerializerAttribute.CreateSerializer), the object the message refers to appears to be something serialization-related, not the data objects themselves (I defined parameterless constructors for both). I don't know if there's a way around this problem with further configuration, or if the driver just won't allow an IEnumerable to be used this way.
I am attempting to get ServiceStack to return a list of objects to a C# client, but I keep getting this exception:
"... System.Runtime.Serialization.SerializationException: Type definitions should start with a '{' ...."
The model I am trying to return:
public class ServiceCallModel
{
public ServiceCallModel()
{
call_uid = 0;
}
public ServiceCallModel(int callUid)
{
this.call_uid = callUid;
}
public int call_uid { get; set; }
public int store_uid { get; set; }
...... <many more properties> ......
public bool cap_expense { get; set; }
public bool is_new { get; set; }
// An array of properties to exclude from property building
public string[] excludedProperties = { "" };
}
The response:
public class ServiceCallResponse
{
public List<ServiceCallModel> Result { get; set; }
public ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
And the service:
public class ServiceCallsService : Service
{
// An instance of model factory
ModelFactory MyModelFactory = new ModelFactory();
public object Any(ServiceCallModel request)
{
if (request.call_uid != 0)
{
return MyModelFactory.GetServiceCalls(request.call_uid);
} else {
return MyModelFactory.GetServiceCalls() ;
}
}
}
The client accesses the service with:
JsonServiceClient client = new ServiceStack.ServiceClient.Web.JsonServiceClient("http://172.16.0.15/");
client.SetCredentials("user", "1234");
client.AlwaysSendBasicAuthHeader = true;
ServiceCallResponse response = client.Get<ServiceCallResponse>("/sc");
The "model factory" class is a DB access class which returns a list. Everything seems to work just fine when I access the service through a web browser. The JSON returned from the service starts:
"[{"call_uid":70...."
And ends with:
"....false,"is_new":true}]"
My question is, what here might be causing serialization/deserialization to fail?
Solution
Thanks to the answer from mythz, I was able to figure out what I was doing wrong. My misunderstanding was in exactly how many DTO types there are and exactly what they do. In my mind I had them sort of merged together in some incorrect way. So now as I understand it:
Object to return (In my case, called "ServiceCallModel": The actual class you wish the client to have once ServiceStack has done its job. In my case, a ServiceCallModel is a key class in my program which many other classes consume and create.
Request DTO: This is what the client sends to the server and contains anything related to making a request. Variables, etc.
Response DTO: The response that the server sends back to the requesting client. This contains a single data object (ServiceCallModel), or in my case... a list of ServiceCallModel.
Further, exactly as Mythz said, I now understand the reason for adding "IReturn" to the request DTO is so the client will know precisely what the server will send back to it. In my case I am using the list of ServiceCallModel as the data source for a ListView in Android. So its nice to be able to tell a ListViewAdapter that "response.Result" is in fact already a useful list.
Thanks Mythz for your help.
This error:
Type definitions should start with a '{'
Happens when the shape of the JSON doesn't match what it's expecting, which for this example:
ServiceCallResponse response = client.Get<ServiceCallResponse>("/sc");
The client is expecting the Service to return a ServiceCallResponse, but it's not clear from the info provided that this is happening - though the error is suggesting it's not.
Add Type Safety
Although it doesn't change the behavior, if you specify types in your services you can assert that it returns the expected type, e.g Change object to ServiceCallResponse, e.g:
public ServiceCallResponse Any(ServiceCallModel request)
{
...
}
To save clients guessing what a service returns, you can just specify it on the Request DTO with:
public class ServiceCallModel : IReturn<ServiceCallResponse>
{
...
}
This lets your clients have a more succinct and typed API, e.g:
ServiceCallResponse response = client.Get(new ServiceCallModel());
instead of:
ServiceCallResponse response = client.Get<ServiceCallResponse>("/sc");
See the New API and C# Clients docs for more info.
I've got a project that uses IronPython as a scripting engine to perform various tasks. One of those tasks needs to do some table lookup's on the Azure Table storage, however the table layouts are different, and will change often, so I need the model classes to be defined in Python.
Here is the problem I'm running into, whenever I run a query it complains that a base class from my project is not supported by the client library.
Unhandled Exception: System.InvalidOperationException: The type 'IronPython.NewTypes.IPTest.BaseModelClass_1$1' is not supported by the client library.
Python Code:
import clr
import System
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
class MyTable(AzureTableService.BaseModelClass):
def __new__(self, partitionKey, rowKey):
self.PartitionKey = partitionKey
self.RowKey = rowKey
return super.__new__(self)
MyTableDetails = "";
#I can manually create an entity, and it recognizes the base class, but not when I try to return from a query
#working = MyTable("10", "10040")
#print working.PartitionKey
y = AzureTableService.GetAzureTableQuery[MyTable]("MyTable")
z = y.Where(lambda c: c.PartitionKey == "10" and c.RowKey == "10040")
print(z.Single())
C# Code:
public class AzureTableService {
private CloudStorageAccount mStorageAccount;
public AzureTableService() {
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) => {
var connectionString = ConfigurationManager.AppSettings[configName];
configSetter(connectionString);
});
mStorageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
}
private TableServiceContext AzureTableServiceContext {
get {
var context = mStorageAccount.CreateCloudTableClient().GetDataServiceContext();
context.IgnoreResourceNotFoundException = true;
return context;
}
}
public IQueryable<T> GetAzureTableQuery<T>(string TableName) {
return AzureTableServiceContext.CreateQuery<T>(TableName);
}
public class BaseModelClass : TableServiceEntity {
public BaseModelClass(string partitionKey, string rowKey) : base(partitionKey, rowKey) { }
public BaseModelClass() : base(Guid.NewGuid().ToString(), String.Empty) { }
}
}
Is there anything obvious that I'm missing? In my commented code, it seems to recognize my base class properties when I manually create it, however it does not when I try returning it from a query.
The problem you are facing is related to System.Data.Services.Client which is used by Microsoft.WindowsAzure.StorageClient.
It restricts which types can be used in the data services client. This seems to prevent any implementation of IDynamicMetaObjectProvider (basically any dynamic object and therefore any object of an IronPython class) to be used during deserialization of your query result.
I tested the scenario using Azure Storage 1.7.0.0, 2.0.6.0 and 2.1.0.0-rc confirming your results.
You could always have a look at the source and see if another deserializer for AtomPub could be used.
Scenario: An entity from data model is passed into a WCF Web Service with various information, saved into a database and then returned back with the object fully populated with additional information.
public class Request
{
public virtual Guid RequestID { get; set; }
public virtual string RequestType { get; set; }
public virtual System.DateTime CreatedDate { get; set; }
//More properties here populated from DB
}
[OperationContract]
Request CreateRequest(Request input);
In this example, the RequestID and CreatedDate are populated only when the record is inserted into the database, and therefore should not be visible during the initial request. They should be visible when the object is returned however.
The current approach that we are going with is to create two classes (RequestInput, RequestOutput) in our web service implementation project which inherit from the entity.
We will then add [DataMember] attributes on various properties that are required and [IgnoreDataMember] on those that should be ignored.
Is this the correct approach?
I wouldn't say it is a correct or incorrect way. But it is more usual to use names something along the line of
[DataContract]
Request{...}
and
[DataContract]
Response{...}
the Request and Response should ideally be decoupled from the model representation you are using in the client and the server - ie you have a facade or adaptor that maps them to your model from your service code.
this is along the lines of how I would do it - but this is very subjective dependant on size of entities etc - you may want to involve an auto-mapper somehow.
// higher level code
var entity = new Entity { properties we know before call };
// pass down to service layer
var response = service.CreateRequest(new Request { Prop1 = entity.Prop1... } );
entity.RequestID = response.RequestId;
entity.CreatedDate = response.CreatedDate;