I would like to create a more structured approach to loading the needed entity-tree:
I need a serious amount of data, so I'm doing this using type-safe Includes (just a normal Include but with Lambda's) as shown here.
As I said, I need a lot of data, basically a whole entity tree under 1 parent item.
Now, I could do this doing something like:
context.House
.Include(x => x.Doors)
.Include(x => x.Doors.FirstOrDefault().Joint)
.Include(x => x.Doors.FirstOrDefault().Joint.FirstOrDefault().JointCategory)
.Include(x => x.Doors.FirstOrDefault().Joint.FirstOrDefault().JointCategory.JointType)
.Include(x => x.Windows)
// ... same thing
.Include(x => x.Roof)
// ... same thing
As you can see, this line filled with includes can get quite huge. This is in fact a very simplified sample of the actual code (which doesn't include houses btw)
So what I would like to do is creating methods, responsible for its branch in the tree. Where the method can accept the object query and include the child, and in its turn, call the "child-loader methods". Also, the parent shouldn't matter, as long as it has a property with the type of the child.
This probably does not make much sense so:
public void LoadHouse(int id)
{
// ...
ObjectQuery<House> query = context.House;
// and now?
LoadDoors(query, x => x.Door);
}
public void LoadDoors<T>(ObjectQuery<T> query, ..?..)
{
// ... ?
LoadJoints(...)
}
And so on. But I can't really get my head around it... There's a missing link between the incoming query and calling the child methods.
Has anyone done something like this? Or could anyone give me some pointers?
Try something like this instead:
query = LoadDoors(query, x => x.Door);
Where LoadX returns the result of calling Include.
Related
I'm modelling a 3rd party database using Automapper 8 and EF 6.
One of my DTO classes needs to use a Where clause on an association to locate the correct record.
// Community table is mapped and working.
// Mapping breaks when mapping dest.Subjects
cfg.CreateMap<Person, PersonDto>()
// snip many mappings
.ForMember(dest => dest.Id, act => act.MapFrom(src => src.ID))
.ForMember(dest => dest.UserName, act => act.MapFrom(src => src.Community.NetworkLogin))
.ForMember(
dest => dest.Subjects,
act => act.MapFrom(
src => src.Community.StudentClasses.Where(
subject => subject.Year == CurrentSemester.Year && subject.Semester == CurrentSemester.Semester)))
.ForMember(
dest => dest.Contacts,
act => act.MapFrom(
src => src.Community.Contacts.Where(
contact => Contact.UseThis).Select(contact => contact.ContactDetails)));
This code works in production, but I'd really like to Unit Test this model. Running a simple test (get all records in the Mock) I'm hit with a NullReferenceException when it tries to access the Community.StudentClasses object.
I found this answer relating to NullReferenceExceptionexceptions and AutoMapper, which helped me fix the rest of the references in this config, but I'm still having issues with this one. The test works when I remove the Community.StudentClasses mapping.
I'm mocking objects using code similar to:
public static Person Person19788 =>
new SchoolContact
{
ID = 19788,
NetworkLogin = "username",
// Tried various creation methods
// StudentClasses = new List<StudentClass> {new StudentClass()},
// StudentClasses = new List<StudentClass> {new StudentClass {Year = 0, Semester = 0}},
StudentClasses = null,
StudentContacts = null,
Address = Address19788
};
CurrentSemester has been checked, and returns valid non-zero values for Year and Semester.
The strange thing is that the Contacts mapping works fine, even with null values. So I assume that I've broken my Subjects mapping somewhere along the line, but I'm unsure where else to look.
It turns out that the problem was with my Subjects mapping after all.
The top-level associations were mapped correctly, however the lower-level associations were not. (I didn't include these mappings because a) I didn't think about them, and b) there's only so much code that anyone would be willing to wade through).
After fixing up the w > x > y > z mappings everything is working as expected. I thought I'd exhausted all options before posting this question. Lesson learned: don't post to SO until you've slept on it and taken a fresh look at the code the next day.
I saw a lot of post showing how to perform a query within Entity Framework to retrieve master details data, like this:
IQueryable<myobj> foo = _context.Foos.Include(x => x.FooDetails).Where(x => x.Id == fooId);
But I have to manage an harder case. A master details where every detail has its own details.
Something like:
Foo --> FooDetails --> FooDetailsInfo
Is this possible? If yes, how? Of course the dumb solution exists and it is use a loop. Is there a smarter way to reach this goal?
I tried to edit the line code
IQueryable<myobj> foo = _context.Foos.Include(x => x.FooDetails).Where(x => x.Id == fooId);
but I didn't write anything useful.
Yes, it is possible. After Include you can call ThenInclude
var foo = _context.Foos
.Include(x => x.FooDetails)
.ThenInclude(fd => fd.FooDetailsInfo)
.Where(x => x.Id == fooId);
Mocked setup below (the GenerateTrades() and other methods in ContextFactory are just returning a List AsQueryable with sample data):
_trades = ContextFactory.GenerateTrades();
_hedges = ContextFactory.GenerateHedges();
_exposures = ContextFactory.GenerateExposures();
_ctx = new Mock<FxContext>();
var fakeTrades = new Mock<DbSet<Trade>>();
fakeTrades.As<IQueryable<Trade>>().Setup(m => m.Provider).Returns(_trades.Provider);
fakeTrades.As<IQueryable<Trade>>().Setup(m => m.Expression).Returns(_trades.Expression);
fakeTrades.As<IQueryable<Trade>>().Setup(m => m.ElementType).Returns(_trades.ElementType);
fakeTrades.As<IQueryable<Trade>>().Setup(m => m.GetEnumerator()).Returns(_trades.GetEnumerator());
var fakeHedges = new Mock<DbSet<Hedge>>();
fakeHedges.As<IQueryable<Hedge>>().Setup(m => m.Provider).Returns(_hedges.Provider);
fakeHedges.As<IQueryable<Hedge>>().Setup(m => m.Expression).Returns(_hedges.Expression);
fakeHedges.As<IQueryable<Hedge>>().Setup(m => m.ElementType).Returns(_hedges.ElementType);
fakeHedges.As<IQueryable<Hedge>>().Setup(m => m.GetEnumerator()).Returns(_hedges.GetEnumerator());
var fakeExposures = new Mock<DbSet<Exposure>>();
fakeExposures.As<IQueryable<Exposure>>().Setup(m => m.Provider).Returns(_exposures.Provider);
fakeExposures.As<IQueryable<Exposure>>().Setup(m => m.Expression).Returns(_exposures.Expression);
fakeExposures.As<IQueryable<Exposure>>().Setup(m => m.ElementType).Returns(_exposures.ElementType);
fakeExposures.As<IQueryable<Exposure>>().Setup(m => m.GetEnumerator()).Returns(_exposures.GetEnumerator());
_ctx.Setup(c => c.Trades).Returns(fakeTrades.Object);
_ctx.Setup(c => c.Hedges).Returns(fakeHedges.Object);
_ctx.Setup(c => c.Exposures).Returns(fakeExposures.Object);
Part of test code looks like:
_sut = (from x in _ctx.Object.Hedges
where x.Id == ContextFactory.s_hedge01Id
select x).FirstOrDefault();
_ctx.Object.Hedges.Attach(_sut);
_ctx.Object.Entry(_sut).Collection(x => x.HedgedTrades).Load();
On the last line I get an exception:
{"Member 'Load' cannot be called for property 'HedgedTrades' because the entity of type 'Hedge' does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet<Hedge>."}
I have verified that in fact the _sut Hedge is actually an instance of a Hedge (not null or a stub) and as the code seems to indicate, I have attached the Hedge to the context explicitly (though I would think the query should bring the object into the context automagically). Am I missing something?
It seems like here in your test you are mixing two separate approaches. Error you received states that you are trying to access DbContext inherited logic, but it is mocked. If you want to perform your unit tests against database with use of FxContext then do not mock it. If you want test other part of software then you should mock interface that FxContext should implement. This will enable you to fullfil dependency injection principle in your code which will help you then organize your test in more predictable way.
I had an Include method like so
public static IQueryable<SlideSet> IncludeParameters(this IDbSet<SlideSet> storage) {
return storage.Include(ss => ss.Params.Select(x => x.Parameter));
}
I am cleaning up my domain model and it no longer makes sense to have SlideSet.Params be public.
I know that there is a form of IDbSet<>.Include() that takes a string parameter. What is the syntax for using a string while descending into the child property like this?
btw, for those who are wondering, I'm pretty sure
return storage.Include(ss => ss.Params.Select(x => x.Parameter));
is identical to
return storage.Include(ss => ss.Params.Include(x => x.Parameter));
Just use .:
return storage.Include("Params.Parameter");
I have an entity A with a simple navigation property B. For any given instance of A, we expect several related thousand instances of B.
There is no case where I call something like:
foreach(var x in A.B) { ... }
Instead, I'm only interested in doing aggregate operations such as
var statY = A.B.Where(o => o.Property == "Y");
var statZ = A.B.Where(o => o.CreateDate > DateTime.Now.AddDays(-1));
As far as I can tell, EF instantiates thousands of references to B and does these operations in memory. This is because navigation properties use EntityCollection. Instead, I'd like it to perform these queries at the SQL level if possible.
My current hunch is that Navigation Properties may not be the right way to go. I'm not attached to EF, so I am open to other approaches. But I'd be very interested to know the right way to do this under EF if possible.
(I'm using EF4.)
CreateSourceQuery seems to do the trick.
So my examples would now be:
var statY = A.B.CreateSourceQuery().Where(o => o.Property == "Y");
var statZ = A.B.CreateSourceQuery().Where(o => o.CreateDate > DateTime.Now.AddDays(-1));
There's one thing you should know. Members that derives from IQueryable<> are executed on the server, not in memory. Members which are derived from IEnumerable<> is executed in memory.
for example
var someEntities = db.SomeEntities; <-- returns an IQueryable<> object. no data fetched. SomeEntities table may contain thousands of rows, but we are not fetching it yet, we are just building a query.
someEntities = someEntities.Where(s => s.Id > 100 && s.Id < 200); <-- creates expression tree with where statement. The query is not executed yet and data is not fetched on the client. We just tell EF to perform a where filter when query will execute. This statement too returns an IQueryable<> object.
var entities = someEntities.AsEnumerable(); <-- here we tell EF to execute query. now entities will be fetched and any additional linq query will be performed in memory.
you can also fetch the data using foreach, calling ToArray() or ToList<>.
Hope you understand what I mean, and sorry for my english :)