I have two classes which has multiple common properties but they have their own properties as well. I created a base class and then two child classes but now in my APIs, I want one return type because we have one API for both children. What should be my response class?
Base Class Response
public class BaseClass
{
public string? id { get; set; }
public string? Name { get; set; }
public string Address { get; set; }
public string Type {get; set;}
public string? PhoneNumber { get; set; } = null;
}
Child 1
public class Child1 : BaseClass
{
public string? RollNumber { get; set; }
public string HighSchoolGrade{ get; set; }
}
Child 2
public class Child2 : BaseClass
{
public string? Height { get; set; }
public string Weight{ get; set; }
}
Now in the API, what should be my response object
public async Task<BaseClass?> GetAllData(string id)
{
//Query
var data = //Query
if (data.type = ChildX){
return //Run the mapper to convert to Child1 DTO
} else {
return //Run the mapper to convert to Child2 DTO
}
}
If I do the above, it does not return the properties of the child classes and only return base class because of the response object. Is there a way to have a specific response type which would include Child1 or Child2 properties as well based on the data.type
We are using Document DB so there are no specific properties in the DB as it is JSON data. I want the classes to be separate because in future we can have a lot more properties. Is there a way to have generic response type or just remove the response type? What are the best practices in this instance?
CodePudding user response:
On the contrary, it is returning all the fields and properties. However there is no guarantee which type is being returned at compile time, so they are not automatically available at compile time, so you have to cast it at run time.
To explain, imagine if you wrote this:
var result = await GetAllData();
Console.WriteLine(result.RollNumber); //This would fail if Child2 was returned
Console.WriteLine(result.Height); //This would fail if Child1 was returned
Instead you have to check the type and cast it:
var result = await GetAllData();
if (result is Child1)
{
Console.WriteLine(((Child1)result).RollNumber);
}
if (result is Child2)
{
Console.WriteLine(((Child2)result).Height);
}
Since this is so common, there are various c# constructs to help you along, e.g. you could also write
var c1= result as Child1;
if (c1!= null) Console.WriteLine(c1.RollNumber);
or
switch (result)
{
case Child1 c1:
Console.WriteLine(c1.RollNumber);
break;
case Child1 c2:
Console.WriteLine(c2.Height);
break;
}
The latter is known as "pattern matching"
As an alternative, you could use dynamic
, like this:
dynamic result = await GetAllData();
Console.WriteLine(result.RollNumber);
Console.WriteLine(result.Height);
However this does not perform as well (as it has to use Reflection to find the property at run-time) and this will raise a nasty runtime error if you accidentally access the wrong property. So it is discouraged.
I recommend you use the pattern matching approach since that is what it is for.
CodePudding user response:
So, you can't return different types from the same class, you could do something generic but the caller would need to specify the type and, if the caller can do that, they could just call the correct, typed overload.
Instead, you could extend your base class a little,
public abstract class BaseClass
{
...
public abstract SomeTypeIndicator GetType();
}
So that the caller can ask the instance what type it is.
CodePudding user response:
remove base class from the task, usually it is
public async Task<ActionResult> GetAllData(string id)
{
}
and use something like this when you get data from the API
var data = JObject.Parse(response.Content);
if (data["type"] = "ChildX")
return data.ToObject<Child1>();
else return data.ToObject<Child1>();
CodePudding user response:
First change action signiture to this:
public async Task<IActionResult> GetAllData(string id)
{
//Code
}
And then you can use conditions on mapping like this:
public async Task<IActionResult> GetAllData(string id)
{
var query= table.Find(_ => true);
if (data.type = ChildX)
{
var data1 = query.Project(TableMapping.MapChild1()).SingleOrDefualt();
return data1;
}
else
{
var data2 = query.Project(TableMapping.MapChild2()).SingleOrDefualt();
return data2;
}
}
public static class TableMapping
{
public static FindExpressionProjectionDefinition<YOUR_TABLE, Child1> MapChild1()
{
var config = new FindExpressionProjectionDefinition<YOUR_TABLE, Child1>(tbl => new Child1
{
//populate your model like this:
RollNumber = tbl.roleNumber
});
return config;
}
public static FindExpressionProjectionDefinition<YOUR_TABLE, Child2> MapChild2()
{
var config = new FindExpressionProjectionDefinition<YOUR_TABLE, Child2>(tbl => new Child2
{
//populate your model like this:
Height = tbl.height
});
return config;
}
}
If you aren't Document Db you can use Experssion<Func<TABLE,Child1>>
in method signiture.