Switch data model structure based upon property c# - c#

I am using a client to call an API. In the API - I want the model to populate from the request body - but I want the model to be structured differently depending upon the name of a single property. Basically I want to create something like a switch/case scenario with a data model, but am unsure how to implement this. The last model contains pseudo code based upon what I want to acheive (obviously generic type won't work in the way I described, but I feel it completes my example). Here's my example:
Controller:
[HttpPost("customer", Name = "Submit Customer")]
public IActionResult ActivateCustomer([FromBody]Customer customer)
{
//Do something with the Customer object.
return Ok();
}
Customer Model:
public class Customer
{
public CustomerInfo customerInfo { get; set; }
public SponserInfo sponserInfo { get; set; }
}
CustomerInfo:
public class CustomerInfo
{
public int CustomerId { get; set; }
public string CustomerName { get; set; }
//etc.
}
SponserA:
public class SponserA
{
public int ReferenceId { get; set; }
public string Password { get; set; }
}
SponserB:
public class SponserB
{
public string UserName{ get; set; }
public string Relation { get; set; }
public string Department { get; set; }
}
SponserInfo: (pseudo-code of what I would like)
public class SponserInfo
{
public string SponserName { get; set; }
public T SponserInfo { get; set; }
switch(this.SponserName)
{
case "Sponser A's Name":
T = SponserA;
break;
case "Sponser B's Name":
T = SponserB;
break;
}
}

How about something like this:
public abstract class SponsorInfo
{
public string SponserName { get; set; }
protected SponsorInfo(string sponserName)
{
SponserName = sponserName;
}
}
public class SponsorA : SponsorInfo
{
public int ReferenceId { get; set; }
public string Password { get; set; }
public SponsorA(string sponserName, int referenceId, string password)
: base(sponserName)
{
ReferenceId = referenceId;
Password = password;
}
}
public class SponsorB : SponsorInfo
{
public string UserName { get; set; }
public string Relation { get; set; }
public string Department { get; set; }
public SponsorB(string sponsorName, string userName, string relation, string department)
: base(sponsorName)
{
UserName = userName;
Relation = relation;
Department = department;
}
}
Then, leave your Customer class alone (but fix the typo):
public class Customer
{
public CustomerInfo customerInfo { get; set; }
public SponsorInfo sponsorInfo { get; set; }
}
and in your controller, add the switch statement and construct either a SponsorA or a SponsorB depending on what the data looks like. Either of those is a SponsorInfo, so you can attach it as the sponsorInfo in your Customer object.

Here's one extensible way.
An attribute maps the sponsor name to the subclass, so SponsorInfo doesn't have to be aware of all subclasses.
It uses an abstract base class (Sponsor) for all Sponsor types (as also recommended by #Flydog57).
When SponsorInfo.SponsorName is assigned, the instance of a subclass of this is created (so you have to assign SponsorName first).
You can adjust that depending on how you actually map the properties from your model.
using System;
using System.Linq;
using System.Reflection;
/// <summary>
/// Attribute to indicate the name mapped to a <see cref="Sponsor"/> subclass.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class SponsorAttribute : Attribute
{
public SponsorAttribute(string name)
{
this.Name = name;
}
/// <summary>
/// The value that <see cref="SponserInfo.SponserName"/> must match for the attribute class to be used.
/// </summary>
public virtual string Name { get; set; }
}
public abstract class Sponsor
{
public int ReferenceId { get; set; }
public string Password { get; set; }
}
[Sponsor("Sponser A's Name")]
public class SponsorA : Sponsor
{
}
[Sponsor("Sponser B's Name")]
public class SponsorB : Sponsor
{
public string Department { get; set; }
}
// More subclasses can be added.
public class SponsorInfo
{
/// <summary>
/// The Sponsor name.
/// Changing this sets <see cref="Sponsor"/> to a new instance of the corresponding class.
/// </summary>
public string SponsorName
{
get { return _sponsorName; }
set
{
if (_sponsorName != value)
{
_sponsorName = value;
// Find a Sponsor subclass with a SponsorAttribute.Name matching the given value:
Type sponsorType = Assembly.GetExecutingAssembly().GetTypes() // you might want to also scan other assemblies
.Where(t =>
t.IsSubclassOf(typeof(Sponsor))
&& (t.GetCustomAttribute<SponsorAttribute>()?.Name?.Equals(_sponsorName) ?? false)
).FirstOrDefault(); // null if none is found
if (sponsorType == null)
Sponsor = null; // no matching class
else
Sponsor = (Sponsor)Activator.CreateInstance(sponsorType); // new instance of the matching class
}
}
}
private string _sponsorName;
public Sponsor Sponsor { get; set; } // renamed from "SponsorInfo" because that's the name of this class
}
This is dual licensed as public domain (CC0) and the normal licensing of Stack Overflow.

Why not create a model called Sponsor that has all your fields, and then if ReferenceId is null, you'll know which kind of sponsor it is?
public class SponsorInfo
{
public int? ReferenceId { get; set; }
public string Password { get; set; }
public string UserName { get; set; }
public string Relation { get; set; }
public string Department { get; set; }
}
[HttpPost("customer", Name = "Submit Customer")]
public IActionResult ActivateCustomer([FromBody]Customer customer)
{
//Do something with the Customer object.
if (customer.sponsorInfo.ReferenceId == null || !customer.sponsorInfo.ReferenceId.HasValue)
{
//is SponsorB
}
else
{
//is SponsorA
}
return Ok();
}

Related

Searching in json array

{
"medic":[
{
"ace":[
{
"name":"lisinopril",
"strength":"10 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}
],
"anti":[
{
"name":"nitroglycerin",
"strength":"0.4 mg Sublingual Tab",
"dose":"1 tab",
"route":"SL",
"sig":"q15min PRN",
"pillCount":"#30",
"refills":"Refill 1"
}
],
"anticoag":[
{
"name":"warfarin sodium",
"strength":"3 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}
],
}
]
}
class Program
{
static void Main(string[] args)
{
// ""reporttype"":""post"",
string jsonString = #"..."; //The above json
Console.WriteLine("Enter the Medication name in which you want to Find STRENGTH value :");
string medicname = Console.ReadLine();
var rootInstance = JsonConvert.DeserializeObject<Rootobject>(jsonString);
}
}
var result = rootInstance.medications[0].Where(x=>x.name == medicname ).Select(t => t.strength).ToList();
But when i run the above query, I get this below error:
'Medication' does not contain a definition for 'Where' and no accessible extension method 'Where' accepting a first argument of type 'Medication' could be found (are you missing a using directive or an assembly reference?)
I have added all necessary namespaces to my code.
and Here is my object class
public class Rootobject
{
public List<Medication> medications { get; set; }
}
public class Medication
{
public List<aceInhibitors> aceinhibitors { get ; set ; }
public List<anti> antianginal {get; set; }
public List<anticoag> anticoagulants {get; set; }
}
public class aceInhibitors
{
[JsonProperty("name")]
public string name { get; set; }
[JsonProperty("strength")]
public string strength { get; set; }
[JsonProperty("dose")]
public string dose { get; set; }
[JsonProperty("route")]
public string route { get; set; }
[JsonProperty("sig")]
public string sig { get; set; }
[JsonProperty("pillCount")]
public string pillCount { get; set; }
[JsonProperty("refills")]
public string refills { get; set; }
}
public class anti
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
public class anticoag
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
Your Medication object itself is not searchable. Instead it holds a bunch of list and each contains a different type (where all properties are the same). So maybe you should use some base class for the medicine and add another property to your Medication class. In that case you would have a class layout something like this:
public class Rootobject
{
public List<Medication> medications { get; set; }
}
public class Medication
{
public List<aceInhibitors> aceinhibitors { get; set; }
public List<antianginal> antianginal { get; set; }
public List<anticoagulants> anticoagulants { get; set; }
public List<betaBlocker> betablocker { get; set; }
public List<diuretic> diuretic { get; set; }
public List<Mineral> mineral { get; set; }
public IEnumerable<Medicine> Medicines => Enumerable.Empty<Medicine>()
.Concat(aceinhibitors)
.Concat(antianginal)
.Concat(anticoagulants)
.Concat(betablocker)
.Concat(diuretic)
.Concat(mineral);
}
public class Medicine
{
[JsonProperty("name")]
public string name { get; set; }
[JsonProperty("strength")]
public string strength { get; set; }
[JsonProperty("dose")]
public string dose { get; set; }
[JsonProperty("route")]
public string route { get; set; }
[JsonProperty("sig")]
public string sig { get; set; }
[JsonProperty("pillCount")]
public string pillCount { get; set; }
[JsonProperty("refills")]
public string refills { get; set; }
}
public class aceInhibitors : Medicine
{
}
public class antianginal : Medicine
{
}
public class anticoagulants : Medicine
{
}
public class betaBlocker : Medicine
{
}
public class diuretic : Medicine
{
}
public class Mineral : Medicine
{
}
And prepared with that you could now ask something like that:
var result = rootInstance.medications[0].Medicines
.Where(x => x.name == medicname)
.Select(t => t.strength)
.ToList();
If the model of the classes really matches your desires is up to you, but it should give you starting point.
If you want it more inline you could also do something like this:
public class Medication : IEnumerable<Medicine>
{
public List<aceInhibitors> aceinhibitors { get; set; }
public List<antianginal> antianginal { get; set; }
public List<anticoagulants> anticoagulants { get; set; }
public List<betaBlocker> betablocker { get; set; }
public List<diuretic> diuretic { get; set; }
public List<Mineral> mineral { get; set; }
public IEnumerator<Medicine> GetEnumerator()
{
return Enumerable.Empty<Medicine>()
.Concat(aceinhibitors)
.Concat(antianginal)
.Concat(anticoagulants)
.Concat(betablocker)
.Concat(diuretic)
.Concat(mineral)
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
And in that case you could write something like this:
var result = rootInstance.medications[0]
.Where(x => x.name == medicname)
.Select(t => t.strength)
.ToList();
Your domain model is bit suboptimal as it was pointed out by Oliver. If you need to stick to this model, then you can do the following.
Introduce an interface for fields that are interesting from your query point of view:
public interface InterestingFields
{
string name { get; }
string strength { get; }
}
Each medication class can be easily adjusted to implement it, like:
public class Mineral: InterestingFields
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
Make the properties of the Medication class queryable
var properties = typeof(Medication).GetProperties()
.Where(prop => prop.PropertyType.IsGenericType
&& prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)
&& typeof(InterestingFields).IsAssignableFrom(prop.PropertyType.GetGenericArguments()[0]))
.ToList();
I've used reflection where the property's type is a List<T> and T is assignable to InterestingFields
Go through the properties, retrieve the actual value and do the filtering based on that
var medication = rootInstance.medications[0];
var result = from property in properties
let collection = property.GetValue(medication) as IEnumerable<InterestingFields>
let element = collection?.ToArray().First()
where element?.name == medicname
select element.strength;
Console.WriteLine(result.First());
Proper design would lead to a separation from the data handling and the way that your data is stored. This way, it is easy to reuse the stored data for other handling, it is easier to unit test the data handling with test code, you can change the way that the data is stored, to for instance a CSV file, or XML, without having to change the data handling code.
So you need a class Medication:
class Medication
{
public string Name {get; set;}
public string Strength {get; set;}
public string Dose {get; set;}
... // etc.
}
Consider to change Dose and Strength to a numerical value.
Apparently you have stored all Medications somewhere. A proper software design would hide where it is stored, and what format it is stored in. All you know is, that you can store Medications in it, and fetch it back later, even after your program is restarted. Such a storage is often called a Repository:
class MedicationRepository
{
public IEnumerable<Medication> ReadMedications() {...}
}
The actual implementation is up to you. I think you'll use Nuget Package NewtonSoft Json for this. Maybe you also want methods to Add / Change / Remove Medications?
Consider to let the Repository class implement IEnumerable<Medication>, or even ICollection<Medication>, depending on what is most efficient in your case.
class MedicationRepository : IEnumerable<Medication>
{
public IEnumerator<Medication> GetEnumerator()
{
return this.ReadMedications().GetEnumerator();
}
...
}
Now that you've got a method to read all Medications, we can get back to your LINQ problem:
I need get input string from user(which is medication name in json) i need to check if input matches the name in medication and need to display corresponding strength value.
So you've got a procedure to read the medication name:
public string ReadMedicationName() {...}
And you want the Strength of all Medications with this name.
MedicationRepository medications = ...
string requestedMedicationName = this.ReadMedicationName();
string medicationStrength = medications
.Where(medication => medication.Name == requestedMedicationName)
.Select(medication => medication.Strength)
.FirstOrDefault();
In words: from all Medications, keep only those Medications that have a name that equals requestedMedicationName. If the name is unique, then there will be zero or one Medication left. From all remaining Medications, take only the value of property Strength, and take the first strength, or null if there is no Medication with this Name at all.
Can it be that there are several Medications with this name? Which one do you want in that case, just any Strength (= .FirstOrDefault()), all Strengths (= ToList())? In the latter case: how do you distinguish which Medication with this name contains which Strength? Consider to Select more properties in that case.
Conclusion
By separating the storage of the data and how you get the requested Medication Name from the data handling, it is easier to change the storage (to XML, to CSV, to a database), and it is easier to unit test the LINQ using specific test data.
Similarly: you've hidden how you get the name of the requested Medication: is it a DOS prompt? Did you read it from a file? Maybe you've changed it to a WinForms application and you read it from a Textbox, or a ComboBox. Because you separated, the LINQ doesn't have to change, and can be reused in several platforms.

AutoMapper throwing StackOverflowException when calling ProjectTo<T>() on IQueryable

I have created classes using EF Code First that have collections of each other.
Entities:
public class Field
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<AppUser> Teachers { get; set; }
public Field()
{
Teachers = new List<AppUser>();
}
}
public class AppUser
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public virtual List<Field> Fields { get; set; }
public AppUser()
{
Fields = new List<FieldDTO>();
}
}
DTOs:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<AppUserDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<AppUserDTO>();
}
}
public class AppUserDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
Mappings:
Mapper.CreateMap<Field, FieldDTO>();
Mapper.CreateMap<FieldDTO, Field>();
Mapper.CreateMap<AppUserDTO, AppUser>();
Mapper.CreateMap<AppUser, AppUserDTO>();
And I am getting StackOverflowException when calling this code (Context is my dbContext):
protected override IQueryable<FieldDTO> GetQueryable()
{
IQueryable<Field> query = Context.Fields;
return query.ProjectTo<FieldDTO>();//exception thrown here
}
I guess this happens because it loops in Lists calling each other endlessly. But I do not understand why this happens. Are my mappings wrong?
You have self-referencing entities AND self-referencing DTOs. Generally speaking self-referencing DTOs are a bad idea. Especially when doing a projection - EF does not know how to join together and join together and join together a hierarchy of items.
You have two choices.
First, you can force a specific depth of hierarchy by explicitly modeling your DTOs with a hierarchy in mind:
public class FieldDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<TeacherDTO> Teachers { get; set; }
public FieldDTO()
{
Teachers = new List<TeacherDTO>();
}
}
public class TeacherDTO
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string UserName => Email;
}
public class AppUserDTO : TeacherDTO
{
public List<FieldDTO> Fields { get; set; }
public AppUserDTO()
{
Fields = new List<FieldDTO>();
}
}
This is the preferred way, as it's the most obvious and explicit.
The less obvious, less explicit way is to configure AutoMapper to have a maximum depth it will go to traverse hierarchical relationships:
CreateMap<AppUser, AppUserDTO>().MaxDepth(3);
I prefer to go #1 because it's the most easily understood, but #2 works as well.
Other option is using PreserveReferences() method.
CreateMap<AppUser, AppUserDTO>().PreserveReferences();
I use this generic method:
public static TTarget Convert<TSource, TTarget>(TSource sourceItem)
{
if (null == sourceItem)
{
return default(TTarget);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace, ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
var serializedObject = JsonConvert.SerializeObject(sourceItem, deserializeSettings);
return JsonConvert.DeserializeObject<TTarget>(serializedObject);
}
...
MapperConfiguration(cfg =>
{
cfg.ForAllMaps((map, exp) => exp.MaxDepth(1));
...
When you giving 1 navigation_property to 2nd entity and visa-versa it go in an infinite loop state. So, the compiler automatically throws a Stackoverflow exception.
So, to avoid that, you just need to remove one navigation_property from any of the entities.

How to stop abstract class attributes to be pass in end result - WebAPI?

I have following scenario where I am getting OrderBase obstract class from ThirdParty library. And I have to inherit this abstract class into my model Order to get base attributes. Only below base attributes are required to be return as part of response.
Id
Name
OrderHistory
But actually it return all the base attributes as part of response due to inheritance. So is there any way by which we can restrict no of base attributes to be pass in the result without introduction of intermediate model(s) and mappings.
Code Sample- Third Party:
[DataContract]
[Serializable]
public abstract class OrderBase
{
public OrderBase(DatabaseObject obj)
{
this.Id = obj.Id;
this.Name = obj.Name;
this.Description = obj.Description;
this.ClosingDate = obj.ClosingDate;
this.Price = obj.Price;
}
[DataMember]
public string Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public decimal Price { get; set; }
[DataMember]
public string ClosingDate { get; set; }
}
public class DatabaseObject
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string ClosingDate { get; set; }
public string OrderHistory { get; set; }
}
Model:
[DataContract]
[Serializable]
public class Order : OrderBase
{
public Order(DatabaseObject dbObject)
: base(dbObject)
{
this.OrderHistory = dbObject.OrderHistory;
}
[DataMember]
public string OrderHistory { get; set; }
}
API Code:
public class OrderController : ApiController
{
public Order GetOrder()
{
var dbObj = new DatabaseObject
{
Id = "O001",
Name = "Masala Packets",
ClosingDate = "01/02/2016",
Description = "Payment Successful",
OrderHistory = "",
Price = 10000
};
var orderObj = new Order(dbObj);
return orderObj;
}
}
Current JSON Result:
{
"OrderHistory": "",
"Id": "O001",
"Name": "Masala Packets",
"Description": "Payment Successful",
"Price": 10000.0,
"ClosingDate": "01/02/2016"
}
Expected JSON Result:
{
"OrderHistory": "",
"Id": "O001",
"Name": "Masala Packets"
}
You're serializing your domain models directly. That may not be a good idea. It's better to create a view model to send your serialized data and you will have complete control of what to serialize as well as better separation of concerns. Something like an OrderDTO
public class OrderDTO {
public string Id { get; set; }
public string Name { get; set; }
public string OrderHistory { get; set; }
}
In your web api method:
public class OrderController : ApiController
{
public OrderDTO GetOrder()
{
// return an OrderDTO instead;
}
}
Or you can use JsonIgnore property to exclude properties from serialization in case you want to expose your domain classes:
[DataContract]
[Serializable]
public abstract class OrderBase
{
public OrderBase(DatabaseObject obj)
{
this.Id = obj.Id;
this.Name = obj.Name;
this.Description = obj.Description;
this.ClosingDate = obj.ClosingDate;
this.Price = obj.Price;
}
[DataMember]
public string Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
[JsonIgnore]
public string Description { get; set; }
[DataMember]
[JsonIgnore]
public decimal Price { get; set; }
[DataMember]
[JsonIgnore]
public string ClosingDate { get; set; }
}
Use the [ScriptIgnore] attribute on the property you don't want to serialize as JSON.
If you don't want to do this in the parent class, you should shadow or overload the property in your child class and add the attribute there.
How to exclude property from Json Serialization

How to seed this entity model framework for complex relationship

I have the following scenario. We need to be able to fill forms for some tables, examples Companies (Empresa in Spanish), however we want the administrator to be able to extend the entity itself with additional fields.
I designed the following classes, and I need to seed at least one row, however its unclear to me how to seed one row of type CampoAdicional
Entity class:
public abstract class Entidad
{
[Key]
public int Id { get; set; }
}
Company Class (Empresas)
public class Empresa : Entidad
{
public string Nombre { get; set; }
public string NIT { get; set; }
public string NombreRepresentanteLegal { get; set; }
public string TelefonoRepresentanteLegal { get; set; }
public string NombreContacto { get; set; }
public string TelefonoContacto { get; set; }
public virtual ICollection<CampoAdicional> CamposAdicionales { get; set; }
}
And the Additional Fields (Campo Adicional)
public class CampoAdicional
{
[Key]
public int Id { get; set; }
public string NombreCampo { get; set; }
public virtual Tiposcampo TipoCampo { get; set; }
public virtual Entidad Entidad { get; set; }
}
However I dont know how to seed this class or table, because entity should be of subtype Company
Obviously the typeof doesnt compile
context.CampoAdicionals.Add(new CampoAdicional() { Entidad = typeof(Empresa), Id = 1, NombreCampo = "TwitterHandle", TipoCampo = Tiposcampo.TextoUnaLinea });
Update 1: Please note that the additional fields are for the entire entity company not for each company.
Unfortunately, I don't think you'll be able to use EF to automatically create that kind of relationship. You might be able to do something similar with special getters and such:
public class Entidad
{
// stuff...
public IEnumerable<CampoAdicional> CamposAdicionales
{
get { return CampoAdicional.GetAll(this); }
}
}
public class CampoAdicional
{
[Key]
public int Id { get; set; }
public string NombreCampo { get; set; }
public virtual Tiposcampo TipoCampo { get; set; }
protected string EntidadType { get; set; }
// You will need some mapping between Type and the EntidadType string
// that will be stored in the database.
// Maybe Type.FullName and Type.GetType(string)?
protected Type MapEntidadTypeToType();
protected string MapTypeToEntidadType(Type t);
[NotMapped]
public Type
{
get { return MapEntidadTypeToType(); }
// maybe also check that Entidad.IsAssignableFrom(value) == true
set { EntidadType = MapTypeToEntidadType(value); }
}
public static IEnumerable<CampoAdicional> GetAll(Entidad ent)
{
return context.CampoAdicionals
.Where(a => a.EntidadType == MapTypeToEntidadType(ent.GetType()));
}
}

Ria Services and navigation property issues

I'm encountering an issue using Silverlight4, Ria Services and Entity Framework.
From my sl client I try to get some data through ria services, in my domainService class this method gets called:
public IQueryable<LastMinuteWachtLijstPromotie> GetLastMinuteWachtLijstPromoties(){
IQueryable<LastMinuteWachtLijstPromotie> list = (IQueryable<LastMinuteWachtLijstPromotie>)this.ObjectContext.LastMinuteWachtLijstPromoties.Include("Promotie");
return (from LastMinuteWachtLijstPromotie lwmp in list where lwmp.Actief select lwmp);
}
when I check the contents of the list, in debug mode, it's filled with objects of type LastMinuteWachtLijstPromotie.
these objects have a navigation property to an Object named Promotie.
And i can access the properties of these Promotie objects.
On the silveright client however a method gets invoked when loading is complete:
public void OnLoadEntitiesCompleted(ServiceLoadResult<T> result) {
}
In this method I get all the requested LastMinuteWachtLijstPromotie objects as expected, the property
Promotie however is null.
I have set the [Include] tag on the property Promotie in the auto generated metadata class
and I use the .Include("Promotie")
These same methods are used for different objects from my Domain Model, this works perfectly.
Also, I cannot seem to find differences in the .edmx file with the database mappings and navigation properties.
Has anyone encountered the same issue or know a solution for it?
the metadata classes:
[MetadataTypeAttribute(typeof(LastMinuteWachtLijstPromotie.LastMinuteWachtLijstPromotieMetadata))]
public partial class LastMinuteWachtLijstPromotie
{
// This class allows you to attach custom attributes to properties
// of the LastMinuteWachtLijstPromotie class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class LastMinuteWachtLijstPromotieMetadata
{
// Metadata classes are not meant to be instantiated.
private LastMinuteWachtLijstPromotieMetadata()
{
}
public int AlertId { get; set; }
public string ArtikelNummer { get; set; }
public Nullable<int> ArtikelVariant { get; set; }
public int LastMinuteWachtLijstPromotieId { get; set; }
[Include]
public Promotie Promotie { get; set; }
public int PromotieArtikelId { get; set; }
public int PromotieId { get; set; }
public bool Actief { get; set; }
public DateTime Aanmaakdatum { get; set; }
}
}
[MetadataTypeAttribute(typeof(Promotie.PromotieMetadata))]
public partial class Promotie
{
// This class allows you to attach custom attributes to properties
// of the Promotie class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class PromotieMetadata
{
// Metadata classes are not meant to be instantiated.
private PromotieMetadata()
{
}
public string ActieType { get; set; }
public string AssortimentsManagerNaam { get; set; }
public string AssortimentsManagerTeamIds { get; set; }
[Display(Name = "Commerciele tekst")]
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Nokavision.ReclameFolder.UI.Web.Resources.ValidationResources))]
public string CommercieleTekst { get; set; }
[Display(Name = " ")]
public string CommercieleTekstDetails { get; set; }
[Include]
public Frame Frame { get; set; }
public Nullable<int> FrameId { get; set; }
public Nullable<DateTime> LastMinuteWijzigingsDatum { get; set; }
public string Opmerkingen { get; set; }
[Display(Name = "Op wachtlijst")]
public Nullable<bool> OpWachtLijst { get; set; }
//public Nullable<int> PromotieCopyId { get; set; }
public int PromotieId { get; set; }
[Include]
public EntityCollection<PromotieLeverancier> PromotieLeveranciers { get; set; }
[Include]
public EntityCollection<PromotieMutatie> PromotieMutaties{ get; set; }
//public Nullable<int> PromotieOrigineleId { get; set; }
[Include]
public EntityCollection<PromotieSymbool> PromotieSymbolen { get; set; }
public string Status { get; set; }
[Display(Name = "Promotie inhoud")]
public string PromotieInhoud { get; set; }
[Display(Name = "Promotie eenheid")]
public string PromotieEenheid { get; set; }
[Display(Name = "Promotie prijs")]
public decimal PromotiePrijs { get; set; }
}
}
Add the Composition attribute to the property Promotie property of the LastMinuteWachtLijstPromotieMetadata class. Then it should work.
public partial class LastMinuteWachtLijstPromotie {
internal sealed class LastMinuteWachtLijstPromotieMetadata{
[Include]
[Composition]
public Promotie Promotie { get; set; }
}
}
I know this is an older thread and it may well have been answered elsewhere but I just stumbled upon it and since nobody has provided a link or a better answer.
I'm currently using Silverlight 5 and this is what worked for me (I think the process is the same in SL4 IIRC).
When propegating navigation properties to the client you need to tell RIA services that there is a relationship somewhere using the [Key] and [Association] attributes, this, not unlike the entity framework just describes how to map the relationship to the proper object.
First the metadata classes:
[MetadataTypeAttribute(typeof(Category.CategoryMetadata))]
public partial class Category
{
internal sealed class CategoryMetadata
{
private CategoryMetadata() {
}
[Key]
public int Id { get; set; }
public string NAME { get; set; }
[Association("CategoryToProducts", "Id", "CAT")]
[Include]
public EntityCollection<Product> Products { get; set; }
}
}
[MetadataTypeAttribute(typeof(Order.OrderMetadata))]
public partial class Order
{
internal sealed class OrderMetadata
{
// Metadata classes are not meant to be instantiated.
private OrderMetadata() {
}
[Key]
public int Id { get; set; }
public int PRODID { get; set; }
public DateTime DATE { get; set; }
public bool DONE { get; set; }
public int QTY { get; set; }
[Association("OrderToProduct", "PRODID", "Id", IsForeignKey = true)]
[Include]
public Product Product { get; set; }
}
}
[MetadataTypeAttribute(typeof(Product.ProductMetadata))]
public partial class Product
{
internal sealed class ProductMetadata
{
private ProductMetadata() {
}
[Key]
public int Id { get; set; }
public int CAT { get; set; }
public string NAME { get; set; }
public string DESC { get; set; }
public decimal PRICE { get; set; }
public int QTY { get; set; }
public long UPC { get; set; }
[Association("ProdToCat", "CAT", "Id", IsForeignKey = true)]
[Include]
public Category Category { get; set; }
[Association("ProductToOrders", "Id", "PRODID")]
[Include]
public EntityCollection<Order> Orders { get; set; }
}
}
Now we need to tell RIA services we want it to load the association:
(Note: Intellisense says it's a dot separated list of property names to include, however I tried something like .Include("Category.SubCategory") and this failed with an exception... though .Include("Category").Include("SubCategory") worked like a charm!)
public IQueryable<Product> GetProducts() {
return this.ObjectContext.Products.Include("Category");
}
I can now access my "Category" property from the Silverlight client and it is not NULL :)
Same as SilverX: just had the issue, solved it and thought it could be useful to someone.
I too had all the configuration stuff correct ([Include] for RIA S, Include() for EF) but a navigation property was still null on the Silverlight side.
Turns out the domain service method was using the [Invoke] attribute (and returning a IEnumerable<T>). Removing this attribute solved the issue.
(just for the record, [Invoke] was being used because the method had a List<Entity> parameter)

Categories

Resources