How to exclude a property in WebAPI on demand - c#

I am new to WebApi, so please excuse if the question is amateurish: I use AngularJS's "$resource" to communicate with the WebApi-Controller "BondController". This works great.
My problem: The entity "Bond" has a reference to a list of entity "Price":
public class Bond
{
public int ID { get; set; }
...
public virtual List<Price> Prices { get; set; }
}
What I am looking for is a way to exclude the nested list "Prices" such as
[JsonIgnore]
BUT, in some other situation, I still need a way to retrieve Bonds including this nested list, e.g. via a second controller "Bond2".
What can I do?
Will I need some ViewModel on top of the entity Bond?
Can I somehow exclude the List of Prices in the controller itself:
public IQueryable<Bond> GetBonds()
{
return db.Bonds [ + *some Linq-Magic that excludes the list of Prices*]
}
Background: the list of Prices might become rather long and the Get-Requests would easily become > 1MB. In most cases, the prices don't even need to be displayed to the user, so I'd like to exclude them from the response. But in one case, they do... Thank you for your input!
EDIT:
I see that, for some sort of Linq Magic, I would need a new type "PricelessBond"
EDIT2
Found a nice example of using DTO here and will use that.
The solution is to create a non-persistent BondDTO class that acts as a "shell" and that has only those properties you desire to be visible in a certain use-case and then, in the BondDTOController, transform the selection of Bond => BondDTO via means of a Linq Lambda Select expression.

I am no expert in WebApi but it seems that you have more than one problem.
Why won't you create a class hierarchy?
public class PricelessBond // :)
{
public int ID {get; set;}
}
public class Bond : PricelessBond
{
public List<Price> Prices {get; set;}
}
Then you can expose data via two different methods:
public class BondsController : ApiController
{
[Route("api/bonds/get-bond-without-price/{id}")]
public PricelessBond GetBondWithoutPrice(int id)
{
return DataAccess.GetBondWithoutPrice(id);
}
[Route("api/bonds/get-bond/{id}")]
public Bond GetBond()
{
return DataAccess.GetBond(id);
}
}
And in your DataAccess class:
public class DataAccess
{
public PricelessBond GetBondWithoutPrice(int id)
{
return db.Bonds
.Select(b => new PricelessBond
{
ID = b.ID
})
.Single(b => b.ID == id);
}
public Bond GetBond(int id)
{
return db.Bonds
.Select(b => new Bond
{
ID = b.ID,
Prices = b.Prices.Select(p => new Price{}).ToArray()
})
.Single(b => b.ID == id);
}
}
Of course, having two data access methods implies some code overhead but since you say the response could get greater than 1MB this also means that you should spare your database server and not fetch data that you don't need.
So, in your data access layer load only required data for each operation.
I have tested this in a scratch project and it worked.

Related

How to use an ISpecimenBuilder / ICustomization based on another value

I started using AutoFixture for building my test suite, and I'm pretty convinced that this is what I should be using
to make my tests clear, however, there are a couple of things which I simply don't know how to implement this.
First, let me try you to explain the concept.
I do have class which represents a "Company" entity.
public sealed class Compnay
{
public string Name { get; set; }
public DateTime FoundingDate { get; set; }
public List<Person> Persons { get; set; }
}
And I do have a class of "Person" entities, which represents the persons which are working in a specific company.
public sealed class Person
{
public DateTime DateOfBirth { get; set; }
public DateTime DateOfMarriage { get; set; }
}
Now, I do have an interface to abstract away the current Date/Time.
public interface IDateTimeProvider
{
DateTime Now { get; }
}
And I have a function that queries the companies where there are persons working that are born in the current year.
IEnumerable<Company> Get()
{
return this.DB.Companies.Include(x => x.Persons)
.Where(x => x.DateOfBirth.Year == this.dateTimeProvider.Now)
.Select(x => new {
// ... Implementation ...
});
}
Now, in my unit test, I would like to verify that the entities which are returned are correct.
So I need AutoFixture to generate a random date (because I need to have random dates, so ensure that my code does work
with different Date/Time(s)).
But the problem is that the rest of my test needs to have access to this date because, in order to built my assertion, I
need to calculate which persons are going to be returned (which is dependent) on the current Date/Time.
One option would be to freeze the Date/Time(s) which are created by AutoFixture, but than suddenly, all Date/Times, even the founding date of a company would be this date, which is something which I don't want, since my query might be dependent on that also.
How can I tackle this problem?
Might be important to know that I'm using the "AutoData" attribute to avoid having Fixture configuration inside my tests.
I think that instead of injecting the IDateTimeProvider in your repository, you should instead pass the filtering arguments as parameters into your Get() method. This way you could just pick a date out of the entities generated by AutoFixture and you don't need to freeze values at all.
/* repository */
public IEnumerable<Company> Get(DateTime foundingDate)
{
return this.context.Companies.Include(x => x.Persons)
.Where(x => x.FoundingDate == foundingDate)
.Select(x => x);
}
/* test method */
[Theory, PersistenceData]
public void Foo(
List<Company> companies, [Frozen]MyContext context,
CompaniesRepository repository)
{
context.Companies.AddRange(companies);
context.SaveChanges();
var actual = repository.Get(companies[2].FoundingDate);
Assert.Equal(new[] { companies[2] }, actual);
}
In this example I am using PersistenceData that is a auto data attribute created with EntityFrameworkCore.AutoFixture.

How to compute object property in database with C# code?

As an example let's say my database has a table with thousands of ships with every ship potentially having thousands of passengers as a navigation property:
public DbSet<Ship> Ship { get; set; }
public DbSet<Passenger> Passenger { get; set; }
public class Ship
{
public List<Passenger> passengers { get; set; }
//properties omitted for example
}
public class Passenger
{
//properties omitted for example
}
The example use case is that someone is fetching all ships per API and would like to know for each ship whether it is empty (0 passengers), so the returned JSON will contain a list of ships each with a bool whether it is empty.
My current code seems very inefficient (including all passengers just to determine if a ship is empty):
List<Ship> ships = dbContext.Ship
.Include(x => x.passengers)
.ToList();
and later when the ships are serialized to JSON:
jsonShip.isEmpty = !ship.passengers.Any();
I would like a more performant (and not bloated) alternative to including all passengers. What options do I have?
I have looked at computed columns but they only seem to support sql as string. If possible I would like to stay in the C# code world, so for example having a property which is set correctly by being automatically woven in the SQL query would be optimal.
Create a Data Transfer Object for Ship that reflects the shape of your JSON result, like -
public class ShipDto
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsEmpty { get; set; }
}
Then use projection in your query -
var ships = dbCtx.Ships
.Select(p => new ShipDto
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
Usually, APIs need to produce responses of various shapes and DTOs give you well defined models to represent the shape of your API response. Domain entities are not always suitable for this.
If your domain entity (Ship) has a lot of properties, then copying all those properties in the .Select() method might be cumbersome. You can use AutoMapper to map them for you. AutoMapper has a ProjectTo<T>() method that can generate the SQL and return the projected result. For example, you can achieve the above result with a mapping configuration -
CreateMap<Ship, ShipDto>()
.ForMember(d => d.IsEmpty, opt => opt.MapFrom(s => !s.Passengers.Any()));
and a query -
var ships = Mapper.ProjectTo<ShipDto>(dbCtx.Ships).ToList();
assuming all other properties in ShipDto are named similar as in Ship entity.
EDIT :
If you don't want a DTO model -
you can add a NotMapped property in Ship model -
public class Ship
{
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public bool IsEmpty { get; set; }
public List<Passenger> passengers { get; set; }
}
and then do the query like -
var ships = dbCtx.Ships
.Select(p => new Ship
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
You can return an anonymous type -
var ships = dbCtx.Ships
.Select(p => new
{
Id = p.Id,
Name = p.Name,
IsEmpty = !p.Passengers.Any()
})
.ToList();
If I understand your intention correctly...
One way is to store the number of passengers inside each Ship entity. This can work well if you use Domain Driven Design, treat the Ship as an aggregate root, and only add or remove passengers through methods exposed on the given Ship entity, e.g. RegisterPassenger() / RemovePassenger(). Inside these methods, increment or decrement the passenger number along with adding or removing the passenger.
Then, obviously, you can query the Ships dbset with a PassengerCount < 0 projection to the bool you need. And, again obviously, it won't even touch the Passengers table.
In traditional anemic domain ASP.NET systems this sort of data redundancy might be a bit more risky, because properties are usually publicly mutable, and you have multiple services that 'massage' the entities, which is a potential source of data integrity loss.

How to handle encoded and decoded version of the same c# class

Scenario
I am working on updating my .NET API to encode all database key fields so that the sequential key is not exposed to the end user. I'm using hashids.org for this and have built helper methods to quickly decode/encode properties in my automapper mapping. However, there are multiple versions of the API and only the most current version should be updated with this functionality, which means that I can't simply overwrite my existing classes. I've implemented a few solutions that work, but they all have a bad code smell that I'm hoping to clear up.
Solutions
I am currently performing the encoding at the controller layer. I can see the merits of doing this at the data access layer as well, but feel there is more risk of leaks/missed conversions at that layer, especially since the API has many different data sources. Plus, hiding keys is an issue with the outside world, for which the controller is the gatekeeper, so it feels appropriate there.
The application currently has the following model pattern, which cannot be changed: Model (model that exists in DB) > ValueObject (service model, VO) > DTO (API model).
(1) Initial attempt
Below is an example of a class that needs to support an encoded and decoded state, where Utils.Encode() and Utils.Decode() are helper methods that will convert the field between int and string using Hashids.
//EquipmentDTO.cs
public class EquipmentDTO //encoded class
{
public string Id {get; set;}
public string Name {get; set;}
}
public class EquipmentUnencodedDTO //decoded class
{
public int Id {get; set;}
public string Name {get; set;}
}
//Automapper.cs
CreateMap<EquipmentUnencodedDTO, EquipmentDTO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Encode(src.Id)));
CreateMap<EquipmentDTO, EquipmentUnencodedDTO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Decode(src.Id)));
CreateMap<EquipmentVO, EquipmentDTO>() //mapping from service model to controller model
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Encode(src.Id)));
CreateMap<EquipmentDTO, EquipmentVO>()
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => Utils.Decode(src.Id)));
CreateMap<Equipment, EquipmentVO>() //mapping from DB model to service model
.ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.Id));
I chose to make the existing EquipmentDTO the encoded version
because I want this to become the new standard, which would
eventually lead to the deprecation and removal of
EquipmentUnencodedDTO as the old controllers eventually get
updated.
I chose to not copy CreateMap<EquipmentVO, EquipmentDTO> for CreateMap<EquipmentVO, EquipmentUnencodedDTO> (and the reverse) because
it would lead to a lot of duplication in the AutoMapper file, which
is already huge (though maybe this isn't a real problem?)
I do not like this solution because in my old controllers, the mapping is now confusing. In a POST, for example, the unencoded input DTO has to be converted to the service model via: Mapper.Map<EquipmentVO>(Mapper.Map<EquipmentDTO>(unencodedEquipmentInput)) which is super ugly.
That being said, this is supposedly a temporary problem, so is this a real problem?
This problem would go away if I created CreateMap<EquipmentVO, EquipmentUnencodedDTO>
I do not like this solution because my classes have a lot of duplicated fields that are not changing between the encoded and decoded versions
(2) Second Attempt
The two bullet points above led me to refactor to this:
public class EquipmentDTO
{
public string Id {get; set;}
public string Name {get; set;}
public Decoded Decode(){
return Mapper.Map<Decoded>(this);
}
public class Decoded: EquipmentDTO {
public new int Id {get; set;}
public EquipmentDTO Encode(){
return Mapper.Map<EquipmentDTO>(this);
}
}
}
// Automappers are the same, except EquipmentUnencodedDTO is now EquipmentDTO.Decoded
I like how simple it is to switch between encoded and decoded states now, reducing my double mapping above to: Mapper.Map<EquipmentVO>(unencodedEquipmentInput.Encode());
I like the nested class because it codifies the relationship between the two classes and also does a better job at identifying which fields get encoded/decoded
I think this smells a lot worse
(3) Next Attempt
My next attempt was to add in the missing mappings for the decoded class to the service model and to undo the changes from attempt #2. This created a ton of duplicated mapping code, I'm still stuck with duplicated properties in both classes without a clear indication to which fields get decoded/encoded, and it all feels much more cumbersome than necessary.
Thanks for any advice!
This is one of those answers that doesn't really answer your question directly, but is a different kind of approach to the problem at hand. Based on my comment above.
I would not try to bake in a "hardcoded" transformation, or make the aliasing some intrinsic part of object lifecycle. The idea here is that the transformation of identifiers should be obvious, explicit, and pluggable.
Let's start with an interface:
public interface IObscuredIDProvider
{
public string GetObscuredID(int id);
public void SetObscuredID(int id, string obscuredID);
}
Then, for our testing, a very simple mapper that just returns the int as a string. Your production version can be backed by the hashids.org project or whatever you like:
public class NonObscuredIDProvider : IObscuredIDProvider
{
public string GetObscuredID(int id)
{
return id.ToString();
}
public void SetObscuredID(int id, string obscuredID)
{
// noop
}
}
You'll need to inject the instance of IObscuredIDProvider into whatever layer transforms your "outside/untrusted" data into "trusted/domain" data. This is the place where you will assign the entity IDs from the obscured version to the internal version, and vice versa.
Does that make sense? Hopefully, this is a much more simple to understand and implement solution than baking in a complex, nested transformation....
After a lot of playing around, I ended up going the route of not using automapper and only having a single DTO for both the encoded/unencoded states by using custom getters/setters to control what value would be returned based on a readonly property isEncoded.
My problem with automapper and having multiple DTOs was that there was too much duplication and way too much code to write to add a new decodable DTO. Also, there were too many ways to break the relationship between encodedDTO and unencodedDTO, especially since there are other developers on the team (not to mention future hires) who could forget to create the encoded DTO or to create a mapping to properly encode or decode the ID values.
While I still have separate util methods to perform the encoding of a value, I moved all of the automapper "logic" into a base class EncodableDTO, which would allow a user to run Decode() or Encode() on a DTO to toggle its encoded state, including the encoded state for all of its encodable properties via reflection. Having a DTO inherit EncodableDTO also serves as a clear indicator to developers to what's going on, while custom getters/setters clearly indicate what I'm trying to do for specific fields.
Here's a sample:
public class EquipmentDTO: EncodableDTO
{
private int id;
public string Id {
get
{
return GetIdValue(id);
}
set
{
id = SetIdValue(value);
}
}
public List<PartDTO> Parts {get; set;}
public string Name {get; set;}
}
public class PartDTO: EncodableDTO
{
private int id;
public string Id {
get
{
return GetIdValue(id);
}
set
{
id = SetIdValue(value);
}
}
public string Name {get; set;}
}
public class EncodableDTO
{
public EncodableDTO()
{
// encode models by default
isEncoded = true;
}
public bool isEncoded { get; private set; }
public void Decode()
{
isEncoded = false;
RunEncodableMethodOnProperties(MethodBase.GetCurrentMethod().Name);
}
public void Encode()
{
isEncoded = true;
RunEncodableMethodOnProperties(MethodBase.GetCurrentMethod().Name);
}
protected string GetIdValue(int id)
{
return isEncoded ? Utils.EncodeParam(id) : id.ToString();
}
// TryParseInt() is a custom string extension method that does an int.TryParse and outputs the parameter if the string is not an int
protected int SetIdValue(string id)
{
// check to see if the input is an encoded value, otherwise try to parse it.
// the added logic to test if the 'id' is an encoded value allows the inheriting DTO to be received both in
// unencoded and encoded forms (unencoded/encoded http request) and still populate the correct numerical value for the ID
return id.TryParseInt(-1) == -1 ? Utils.DecodeParam(id) : id.TryParseInt(-1);
}
private void RunEncodableMethodOnProperties(string methodName)
{
var self = this;
var selfType = self.GetType();
// Loop through properties and check to see if any of them should be encoded/decoded
foreach (PropertyInfo property in selfType.GetProperties())
{
var test = property;
// if the property is a list, check the children to see if they are decodable
if (property is IList || (
property.PropertyType.IsGenericType
&& (property.PropertyType.GetGenericTypeDefinition() == typeof(List<>)
|| property.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
)
)
{
var propertyInstance = (IList)property.GetValue(self);
if (propertyInstance == null || propertyInstance.Count == 0)
{
continue;
}
foreach (object childInstance in propertyInstance)
{
CheckIfObjectEncodable(childInstance, methodName);
}
continue;
}
CheckIfObjectEncodable(property.GetValue(self), methodName);
}
}
private void CheckIfObjectEncodable(object instance, string methodName)
{
if (instance != null && instance.GetType().BaseType == typeof(EncodableDTO))
{
// child instance is encodable. Run the same decode/encode method we're running now on the child
var method = instance.GetType().GetMethod(methodName);
method.Invoke(instance, new object[] { });
}
}
}
An alternative to RunEncodableMethodOnProperties() was the explicitly decode/encode child properties in the inheriting class:
public class EquipmentDTO: EncodableDTO
{
private int id;
public string Id {
get
{
return GetIdValue(id);
}
set
{
id = SetIdValue(value);
}
}
public List<PartDTO> Parts {get; set;}
public string Name {get; set;}
public new void Decode() {
base.Decode();
// explicitly decode child properties
Parts.ForEach(p => p.Decode());
}
}
I chose not to do the above because it created more work for DTO creators to have to remember to explicitly add (1) the override method, and (2) any new decodable properties to the override method. That being said, I'm sure I'm taking some sort of a performance hit by looping through every class of my class' properties and its children, so in time I may have to migrate towards this solution instead.
Regardless of the method I chose to decode/encode properties, here was the end result in the controllers:
// Sample controller method that does not support encoded output
[HttpPost]
public async Task<IHttpActionResult> AddEquipment([FromBody] EquipmentDTO equipment)
{
// EquipmentDTO is 'isEncoded=true' by default
equipment.Decode();
// send automapper the interger IDs (stored in a string)
var serviceModel = Mapper.Map<EquipmentVO>(equipment);
var addedServiceModel = myService.AddEquipment(serviceModel);
var resultValue = Mapper.Map<EquipmentDTO>(addedServiceModel);
resultValue.Decode();
return Created("", resultValue);
}
// automapper
CreateMap<EquipmentVO, EquipmentDTO>().ReverseMap();
CreateMap<Equipment, EquipmentVO>();
While I don't think its the cleanest solution, it hides a lot of the necessary logic to make encoding/decoding work with the least amount of work for future developers

Entity Framework Core 2.2 use scalar DBFunction to get property on list of foreign keys

I have a model with a linked list of foreign keys i.e.
[Table("a"]
public class A {
[Key]
[Column("a_id")]
public int Id { get; set; }
public List<B> Bs { get; set; } = new List<B>();
}
[Table("b"]
public class B {
[Key]
[Column("b_id")]
public int Id { get; set; }
[NotMapped]
public string MyFunctionValue { get; set; }
[ForeignKey("a_id")]
public A A { get; set; }
}
I've then defined a function which links to a scalar sql function like so...
public static class MySqlFunctions {
[DbFunction("MyFunction", "dbo")]
public static string MyFunction(int bId) {
throw new NotImplementedException();
}
}
and registered in my context like so...
modelBuilder.HasDbFunction(() => MySqlFunctions.MyFunction(default));
What I want to be able to do in my repository class is to grab the A records with the linked B records in a List with their MyFunctionValue value set to the return value of the function when ran against the id of B. Something like...
myContext.A
.Include(a => a.Bs.Select(b => new B {
Id = b.Id,
MyFunctionValue = MySqlFunctions.MyFunction(b.Id)
});
However with all the options I've tried so far I'm getting either a InvalidOperationException or NotImplementedException I guess because it can't properly convert it to SQL?
Is there any way I can write a query like this or is it too complex for EF to generate SQL for? I know there's a possibility I could use .FromSql but I'd rather avoid it if possible as it's a bit messy.
EDIT:
So I've managed to get it working with the following code but it's obviously a bit messy, if anyone has a better solution I'd be grateful.
myContext.A
.Include(a => a.Bs)
.Select(a => new {
A = a,
MyFunctionValues = a.Bs.Select(b => MySqlFunctions.MyFunction(b.Id))
})
.AsEnumerable()
.Select(aWithMfvs => {
for (int i = 0; i < aWithMfvs.MyFunctionValues.Count(); i++) {
aWithMfvs.A.Bs[i].MyFunctionValue = aWithMfvs.MyFunctionValues[i];
}
return aWithMfvs.A;
})
.AsQueryable();
There are several things you should consider with db functions:
When you declare a DbFunction as static method, you don't have to register it with the modelBuilder
Registering is only needed, when you would use Fluent API (which IMHO I recommend anyway in order to have you entities free of any dependencies)
The return value, the method name and the count, type and order of the method parameters must match your code in the user defined function (UDF)
You named the method parameter as bId. Is it exactly the same in your UDF or rather as in the table like b_id?

Entity Framework core select causes too many queries

I have the following method which is meant to build me up a single object instance, where its properties are built via recursively calling the same method:
public ChannelObjectModel GetChannelObject(Guid id, Guid crmId)
{
var result = (from channelObject in _channelObjectRepository.Get(x => x.Id == id)
select new ChannelObjectModel
{
Id = channelObject.Id,
Name = channelObject.Name,
ChannelId = channelObject.ChannelId,
ParentObjectId = channelObject.ParentObjectId,
TypeId = channelObject.TypeId,
ChannelObjectType = channelObject.ChannelObjectTypeId.HasValue ? GetChannelObject(channelObject.ChannelObjectTypeId.Value, crmId) : null,
ChannelObjectSearchType = channelObject.ChannelObjectSearchTypeId.HasValue ? GetChannelObject(channelObject.ChannelObjectSearchTypeId.Value, crmId) : null,
ChannelObjectSupportingObject = channelObject.ChannelObjectSupportingObjectId.HasValue ? GetChannelObject(channelObject.ChannelObjectSupportingObjectId.Value, crmId) : null,
Mapping = _channelObjectMappingRepository.Get().Where(mapping => mapping.ChannelObjectId == channelObject.Id && mapping.CrmId == crmId).Select(mapping => new ChannelObjectMappingModel
{
CrmObjectId = mapping.CrmObjectId
}).ToList(),
Fields = _channelObjectRepository.Get().Where(x => x.ParentObjectId == id).Select(field => GetChannelObject(field.Id, crmId)).ToList()
}
);
return result.First();
}
public class ChannelObjectModel
{
public ChannelObjectModel()
{
Mapping = new List<ChannelObjectMappingModel>();
Fields = new List<ChannelObjectModel>();
}
public Guid Id { get; set; }
public Guid ChannelId { get; set; }
public string Name { get; set; }
public List<ChannelObjectMappingModel> Mapping { get; set; }
public int TypeId { get; set; }
public Guid? ParentObjectId { get; set; }
public ChannelObjectModel ParentObject { get; set; }
public List<ChannelObjectModel> Fields { get; set; }
public Guid? ChannelObjectTypeId { get; set; }
public ChannelObjectModel ChannelObjectType { get; set; }
public Guid? ChannelObjectSearchTypeId { get; set; }
public ChannelObjectModel ChannelObjectSearchType { get; set; }
public Guid? ChannelObjectSupportingObjectId { get; set; }
public ChannelObjectModel ChannelObjectSupportingObject { get; set; }
}
this is connecting to a SQL database using Entity Framework Core 2.1.1
Whilst it technically works, it causes loads of database queries to be made - I realise its because of the ToList() and First() etc. calls.
However because of the nature of the object, I can make one huge IQueryable<anonymous> object with a from.... select new {...} and call First on it, but the code was over 300 lines long going just 5 tiers deep in the hierarchy, so I am trying to replace it with something like the code above, which is much cleaner, albeit much slower..
ChannelObjectType, ChannelObjectSearchType, ChannelObjectSupportingObject
Are all ChannelObjectModel instances and Fields is a list of ChannelObjectModel instances.
The query takes about 30 seconds to execute currently, which is far too slow and it is on a small localhost database too, so it will only get worse with a larger number of db records, and generates a lot of database calls when I run it.
The 300+ lines code generates a lot less queries and is reasonably quick, but is obviously horrible, horrible code (which I didn't write!)
Can anyone suggest a way I can recursively build up an object in a similar way to the above method, but drastically cut the number of database calls so it's quicker?
I work with EF6, not Core, but as far as I know, same things apply here.
First of all, move this function to your repository, so that all calls share the DbContext instance.
Secondly, use Include on your DbSet on properties to eager load them:
ctx.DbSet<ChannelObjectModel>()
.Include(x => x.Fields)
.Include(x => x.Mapping)
.Include(x => x.ParentObject)
...
Good practice is to make this a function of context (or extension method) called for example BuildChannelObject() and it should return the IQueryable - just the includes.
Then you can start the recursive part:
public ChannelObjectModel GetChannelObjectModel(Guid id)
{
var set = ctx.BuildChannelObject(); // ctx is this
var channelModel = set.FirstOrDefault(x => x.Id == id); // this loads the first level
LoadRecursive(channelModel, set);
return channelModel;
}
private void LoadRecursive(ChannelObjectModel c, IQueryable<ChannelObjectModel> set)
{
if(c == null)
return; // recursion end condition
c.ParentObject = set.FirstOrDefault(x => x.Id == c?.ParentObject.Id);
// all other properties
LoadRecursive(c.ParentObject, set);
// all other properties
}
If all this code uses the same instance of DbContext, it should be quite fast. If not, you can use another trick:
ctx.DbSet<ChannelObjectModel>().BuildChannelObjectModel().Load();
This loads all objects to memory cache of your DbContext. Unfortunately, it dies with context instance, but it makes those recursive calls much faster, since no database trip is made.
If this is still to slow, you can add AsNoTracking() as last instruction of BuildChannelObjectModel().
If this is still to slow, just implement application wide memory cache of those objects and use that instead of querying database everytime - this works great if your app is a service that can have long startup, but then work fast.
Whole another approach is to enable lazy loading by marking navigation properties as virtual - but remember that returned type will be derived type anonymous proxy, not your original ChannelObjectModel! Also, properties will load only as long you don't dispose the context - after that you get an exception. To load all properties with the context and then return complete object is also a little bit tricky - easiest (but not the best!) way to do it to serialize the object to JSON (remember about circural references) before returning it.
If that does not satisfy you, switch to nHibernate which I hear has application wide cache by default.

Categories