0

If I have an EF query like:

var model = testContext.PurchaseOrders
               .Include(order => order.CompanyAccountingCodeNumber)
               .Include(order => order.CompanyAccountingCodeNumber.AccountingCode);

Is there anyway to find out the types of the EF entities involved in the specific query. For example this one involves:

Test.Models.PurchaseOrder
Test.Models.CompanyAccountingCodeNumber
Test.Models.AccountingCode

So I would like to be able to get an IEnumerable of Types so I can get the namespace etc?

I am assuming that something like this is possible - Automapper supports Project().To<> which figures out which includes are required. So I guess using reflection of the include properties would give me their types.

GraemeMiller
  • 11,973
  • 8
  • 57
  • 111
  • This would be really complex. You would have to somehow infer the types from the IQueryable if you were to use `model` in your example after executing that line of code. Why is it not possible to know the namespace of these types beforehand? How would knowing them at runtime help? – Travis J Sep 08 '15 at 20:08
  • Looking at a generic cache wrapper and need the types involved to created Cache Key dependencies. I hoped I could write an extension method that would give me a list of the types so that I don't have to keep it up to date. If I manually specify a list it needs maintained. – GraemeMiller Sep 08 '15 at 20:11
  • To be honest, there is not going to be an easy way to do that with shortcuts. I would suggest just completely mirroring your database in the cache and then having the queries hit the memory as opposed to the database for the cache. – Travis J Sep 08 '15 at 20:14
  • 1
    Issue I am struggling with is cache invalidation (as well all know that and naming things is hard). I basically have a query layer and want the cached queries to depend on the underlying EF entities. If an Entity is modified etc I can raise an event (on save changes) that updates the cache key and cache monitoring will cause any queries depending on it to be invalidated. – GraemeMiller Sep 08 '15 at 20:19
  • 1
    Yeah, I think I see where the problem is, but here is what my suggestion was trying to get at. Storing your queries or objects as object graphs (i.e. just composed sets of data) is going to become unwieldy very quickly. For example, lets say you have 100 graphs stored which all reference the same description of something, and that description changes. Do you want to modify every composed object in your cache, or just that one string? Each individual object should be stored individually in order to avoid that, and that means not storing them in large graphs but as individual objects. – Travis J Sep 08 '15 at 20:32
  • What I am caching isn't the object graph. They are non-EF flattened DTOs. However in most cases they are project from EF. All updates to domain are done via EF so if something in EF changes then the cached DTO should be invalidated. Now it's a bit blunt to invalidate the cached DTOs based on a property updating that they may not use - but it's not critical and in most cases these items don't change often. Plenty of other ways to do this :). I am now largely just curious what the code looks like to get the types out of an EF query (if it is possible) – GraemeMiller Sep 08 '15 at 20:37
  • @GraemeMiller: If these are flattened DTOs, then why are you using `Include`? Wouldn't your `Select` statement determine, all by itself, what the flattened DTO has in it? – StriplingWarrior Sep 08 '15 at 20:41
  • 1
    Well [`Include` returns an `ObjectQuery`](https://msdn.microsoft.com/en-us/library/bb738708(v=vs.110).aspx), which [is an `IQueryable`](https://msdn.microsoft.com/en-us/library/bb345303(v=vs.110).aspx). Can you not look at the [`Expression` of the `IQueryable`](https://msdn.microsoft.com/en-us/library/vstudio/system.linq.iqueryable.expression(v=vs.100).aspx) and just walk the tree using a visitor-like pattern? – Jesus is Lord Sep 08 '15 at 20:41
  • @WordsLikeJared - Yes, that is what you would do. You would need to reflect the type inside of the return from the body. – Travis J Sep 08 '15 at 20:42
  • @WordsLikeJared - The problem with doing something like that is when there are nested projections inside of the Expression. This requires not just a visitor pattern but a recursive traversal because it may need to fully branch at points to a new tree. – Travis J Sep 08 '15 at 20:43
  • @StriplingWarrior The cache contains EF projections (usually), These projections are ultimately dependent on a number of related EF objects. Each cached DTO (or IEnumerable of DTO) is cached with a cache key and a list of dependent cache keys for each EF type involved. If EF updates any model it sets a new cachekey for each entity type. On changing these values any DTO dependent on those cache keys. Anything depending on any of those keys is itself invalidated. Rather than manually create the dependent cache keys I want to auto generate the cache keys. – GraemeMiller Sep 08 '15 at 20:54

2 Answers2

0

What about implementing these LINQ queries in a repository, and then storing involved entity types in some repository property in the last method call?

public interface IWithInvolvedDomainObjects 
{
     IEnumerable<Type> LatestInvolvedDomainObjectTypes { get; }
}

public interface IPurchaseRepository : IWithInvolvedDomainObjects 
{
     IEnumerable<PurchaseOrder> GetPurchaseOrders();
}

public class PurchaseRepository : IPurchaseRepository
{
      public IEnumerable<Type> LatestInvolvedDomainObjectTypes { get; private set; }

      public IEnumerable<PurchaseOrder> GetPurchaseOrders()
      {
           LatestInvolvedDomainObjectTypes = new[]
           {
                typeof(PurchaseOrder)
           };

           return testContext.PurchaseOrders
               .Include(order => order.CompanyAccountingCodeNumber)
               .Include(order => order.CompanyAccountingCodeNumber.AccountingCode);

      }
}
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • We only use repositores for writes. We have a query infrastructure that we use for all reads. I want to auto-generate what you are manually storing in LatestInvolvedDomainObjectTypes but actually include the types for CompanyAccountingCodeNumber and AccountingCode – GraemeMiller Sep 08 '15 at 20:56
  • @GraemeMiller This is a design flaw (the thing of using repos to just perform "writes". You're not implementing repository pattern at all. See this other Q&A where I provided some points about this topic: http://stackoverflow.com/questions/5166888/repository-design-pattern – Matías Fidemraizer Sep 09 '15 at 07:34
  • We will have to disagree over the repository pattern usage. It is a total code smell to have 30+ random query methods on repository. You could argue to add a generic specification filter method and use specification pattern however - how do you efficiently mange the ORM? IMHO far better to have a range of clearly defined queries classes with handlers responsible for getting the data they need allowing fine tuning the ORM -more SOLID due to the better SRP. – GraemeMiller Sep 09 '15 at 09:35
  • @GraemeMiller You can still implement repo pattern without designing 30+ methods, use expression trees and yet execute them inside the repository. I'm against the IQueryable thing outside the repository. What about a GetByCriteria where you give an expression which receives the IQueryable? This is the point. – Matías Fidemraizer Sep 09 '15 at 11:03
  • @GraemeMiller If you would do so, now you would be able to get involved entity types with ease. What's a design smell is designing a repository which loses the control of how it works. – Matías Fidemraizer Sep 09 '15 at 11:04
  • I don't see how doing this in the repository gets me the involved entity types any easier? I can hard code my list as easily in a query as you have in your example? I only want my repository to protect my aggregate roots and ensure they maintain their state correctly. We used to use specification pattern but it wasn't beneficial. In our current architecture querying is the responsibility of the read side. To get data I would rather have a highly optimised query layer that I can easily tune or change to use whatever storage meets the needs of that query. – GraemeMiller Sep 09 '15 at 11:23
0

I haven't got time to produce working code for this, but every IQueryable has an Expression property that contains an Expression Tree which represents the various chained calls that have been made on that IQueryable. You can theoretically dig into that Expression tree and reflect on the expressions therein to try to determine what gets "touched."

In order to get the most bang for your buck, I'd recommend extending the ExpressionVisitor class to give you easy deep traversal of the expression tree, and override specific methods for the types of expressions you think are likely to give you the information you're interested in (like Member-access expressions). In each of these overridden methods, capture the information you need in a private field and then return base.[SameMethod](expr);.

Still, while it's really neat that this is even possible, I'd question whether it's really a good idea. In my experience, it's better to have a single class responsible for caching a particular type of DTO, and inside that class I put a couple of event listeners that are responsible for invalidating specific cache entries when certain types of changes are made in the data.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Yeah - we currently manually define a list of dependencies which are used by CreateCacheEntryChangeMonitor. It just takes a lot of effort. I will see if I can get the visitor stuff working - even if I don't go that route a deep dive into EF will be fun – GraemeMiller Sep 08 '15 at 21:05