Unit of Work, LazyLoading Disabled, Generic Repository, IncludeMultiple<T>, Http 500 error - entity-framework

I have a Vehicle with an association to Model, Model has an association to Make.
Here is my Generic Repository as pertaining to associations as LazyLoadingEnabled = false in my project:
public IQueryable<T> IncludeMultiple<T1>(params Expression<Func<T, object>>[] associations) where T1 : class
{
var source = (IQueryable<T>)DbContext.Set<T>();
if (associations != null)
{
foreach (Expression<Func<T, object>> path in associations)
source = DbExtensions.Include<T, object>(source, path);
}
return source;
}
In my api controller, I am using Unit of work pattern. Here is my GetAll method:
public IEnumerable<Vehicle> GetAll()
{
var vehicles = Uow.VehicleRepository.IncludeMultiple<Vehicle>(c => c.VehicleModel).ToList();
return vehicles;
}
Everything works fine and Json retrieves the Vehicle class data as well as the related VehicleModel class data.
However, Vehicle has no direct association to VehicleMake, only VehicleModel does. Now, if my GetAll method has this:
public IEnumerable<Vehicle> GetAll()
{
var vehicles = Uow.VehicleRepository.IncludeMultiple<Vehicle>(c => c.VehicleModel, c => c.VehicleModel.VehicleMake).ToList();
return vehicles;
}
while I see in debug that vehicles does indeed have the vehicles and their relevant VehicleModel and VehicleMake data, it returns a Http 500 error in Fiddler.
Update:
Added another association in Vehicle called "Test", with the GetAll method being:
(c => c.VehicleModel, c => c.Test)
No error, all data was returned via fiddler. So, it appears that a "Non-direct association" (ie Vehicle -> VehicleMake) is the cause of the error.
Question:
What would be the correct way to retrieving the relevant Vehicle data and its associated classes' data and return it to Json while not getting a Http 500 error?

*SOLVED *
This works:
public HttpResponseMessage GetAll()
{
var vehicles = from data in Uow.VehicleRepository.IncludeMultiple<Vehicle>(c => c.VehicleModel,c => c.VehicleModel.VehicleMake)
select new
{
VehDesc = data.Description,
VehVIN = data.VIN,
VehTransmissionType = data.TransmissionType,
VehFuelType = data.FuelType,
VehYear = data.Year,
VehMileage = data.Mileage,
VehCylinderSize = data.CylinderSize,
VehEngineSize = data.EngineSize,
VehVehicleModel = data.VehicleModel.Name,
VehMakeName = data.VehicleModel.VehicleMake.Name
};
return Request.CreateResponse(HttpStatusCode.OK, vehicles);
}
Basically,
1. I used an HttpResponseMessage as my return type;
2. I used projection to create an anonymous type;
Why did I have to do this?
As near as I can tell, the issue centered on JSON receiving a "circular" return with VehicleModel and VehicleMake. That is, VehicleModel had a association to VehicleMake and VehicleMake has a collection of VehicleModels. When I looked in my debug code I could see a cascade of VehicleModel to VehicleMake to VehicleModel, etc, etc, etc, so to me that meant it was circular.
If anyone knows a better way w/o using anonymous type nor removing the virtual keyword from my navigation properties, I would certainly like to know it. But this does truly work.
FinalNote: Be sure NOT to use the model's property names in anonymous type, ie replace property "TransmissionType" with something like "VehTransmissionType".

Related

Non-recognized member in lambda expression of EF Core ThenInclude in Extension method

I'm trying to write an extension method to include a certain property (text element, themselves containing a collection of translations) that are present in many of my entity models.
I had no problem with the .Include function:
public static IIncludableQueryable<T, IEnumerable<Translation>> IncludeTextBitWithTranslations<T>(this IQueryable<T> source, Expression<Func<T, TextBit>> predicate) where T: class
{
var result = source.Include(predicate).ThenInclude(t => t.Translations);
return result;
}
And tests proved successful.
Now, in some cases, I have entities that have all their texts in a child - for example Article entity has an ArticleInfo property that contains a few text elements. So I figure I just needed to do another extension that was a ThenInclude instead. With a few differences I finally get this :
public static IIncludableQueryable<TEntity, ICollection<Translation>> ThenIncludeTextBitWithTranslations<TEntity, TPreviousProperty, TextBit>(this IIncludableQueryable<TEntity, TPreviousProperty> source, Expression<Func<TPreviousProperty, TextBit>> predicate) where TEntity: class
{
var result = source.ThenInclude(predicate)
.ThenInclude(t => t.Translations);
return result;
}
And now I get this error:
'TextBit' does not contain a definition for 'Translations' and no extension method 'Translations' accepting an argument of 'TextBit' type was found
This error appears on the last lambda expression t => t.Translations.
This error is extremely weird for me, I've been looking all over the internet for some help on the matter but I was unsuccessful.
I tried forcing the type to the ThenInclude by adding them manually :
var result = source.ThenInclude(predicate)
.ThenInclude<TEntity, TextBit, ICollection<Translation>>(t => t.Translations);
but without success.
Does anyone have some clues as to why?
I'm very much at a loss here
You have extra type parameter TextBit in second one (ThenIncludeTextBitWithTranslations<TEntity, TPreviousProperty, TextBit>), so it is considered as a generic type, not an actual one, remove it:
public static IIncludableQueryable<TEntity, ICollection<Translation>> ThenIncludeTextBitWithTranslations<TEntity, TPreviousProperty>(this IIncludableQueryable<TEntity, TPreviousProperty> source, Expression<Func<TPreviousProperty, TextBit>> predicate) where TEntity: class
{
var result = source.ThenInclude(predicate).ThenInclude(t => t.Translations);
return result;
}

Assign Mapped Object to Expression Result in LINQ to Entities

I have the following child object that we use an expression to map our 'entity' to our 'domain' model. We use this when specifically calling our ChildRecordService method GetChild or GetChildren:
public static Expression<Func<global::Database.Models.ChildRecord, ChildRecord>> MapChildRecordToCommon = entity => new ChildRecord
{
DateTime = entity.DateTime,
Type = entity.Type,
};
public static async Task<List<ChildRecord>> ToCommonListAsync(this IQueryable<global::Database.Models.ChildRecord> childRecords)
{
var items = await
childRecords.Select(MapChildRecordToCommon).ToListAsync().EscapeContext();
return items;
}
public async Task<List<ChildRecord>> GetChildRecords()
{
using (var uow = this.UnitOfWorkFactory.CreateReadOnly())
{
var childRecords= await uow.GetRepository<IChildRecordRepository>().GetChildRecords().ToCommonListAsync().EscapeContext();
return childRecords;
}
}
So that all works just fine. However we have another object that is a parent to that child, that in SOME cases, we also wish to get the child during the materialisation and mapping process.
In other words the standard object looks as such:
private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommonBasic = (entity) => new Plot
{
Id = entity.Id,
Direction = entity.Direction,
Utc = entity.Utc,
Velocity = entity.Velocity,
};
However what I also want to map is the Plot.ChildRecord property, using the expression MapChildRecordToCommon I have already created. I made a second expression just to test this:
private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommonAdvanced = (entity) => new Plot
{
ChildRecord = MapChildRecordToCommon.Compile() (entity.ChildRecord)
};
This fails:
System.NotSupportedException
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
Is there a way to reuse my existing expression for ChildRecord, to materialise the object of ChildRecord (ie. one to one/singular not multiple) on the Plot object? I think my trouble is caused by there being just one object and being unable to use the .Select(Map) method. I am not too great at expressions and have hit a wall with this.
For reference, there are actually up to 5 or 6 other child objects on the "Plot" object that I also want to make expressions for.
I resolved this by using the third party library LinqKit.
The library allowed the use of 2 methods, .AsExpandable() (which allows for the expressions to properly compile and be invoked as I understand), and .Invoke() as an extension method to an expression, rather than calling Expression.Invoke(yourexpression). I included a null check just in case.
My code now looks as follows:
public static async Task<List<Plot>> ToCommonListAsync(this IQueryable<global::Database.Models.Plot> plots)
{
var items = await
plots.AsExpandable().Select(MapPlotToCommon).ToListAsync().EscapeContext();
return items;
}
private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommon = (entity) => new Plot
{
Id = entity.Id,
Direction = entity.Direction,
Utc = entity.Utc,
Velocity = entity.Velocity,
ChildRecord = entity.ChildRecord != null ? MapChildRecordToCommon.Invoke(entity.ChildRecord) : default
};
public static Expression<Func<global::Database.Models.ChildRecord, ChildRecord>> MapChildRecordToCommon = entity => new ChildRecord
{
DateTime = entity.DateTime,
Type = entity.Type,
};

Filehelpers and Entity Framework

I'm using Filehelpers to parse a very wide, fixed format file and want to be able to take the resulting object and load it into a DB using EF. I'm getting a missing key error when I try to load the object into the DB and when I try and add an Id I get a Filehelpers error. So it seems like either fix breaks the other. I know I can map a Filehelpers object to a POCO object and load that but I'm dealing with dozens (sometimes hundreds of columns) so I would rather not have to go through that hassle.
I'm also open to other suggestions for parsing a fixed width file and loading the results into a DB. One option of course is to use an ETL tool but I'd rather do this in code.
Thanks!
This is the FileHelpers class:
public class AccountBalanceDetail
{
[FieldHidden]
public int Id; // Added to try and get EF to work
[FieldFixedLength(1)]
public string RecordNuber;
[FieldFixedLength(3)]
public string Branch;
// Additional fields below
}
And this is the method that's processing the file:
public static bool ProcessFile()
{
var dir = Properties.Settings.Default.DataDirectory;
var engine = new MultiRecordEngine(typeof(AccountBalanceHeader), typeof(AccountBalanceDetail), typeof(AccountBalanceTrailer));
engine.RecordSelector = new RecordTypeSelector(CustomSelector);
var fileName = dir + "\\MOCK_ACCTBAL_L1500.txt";
var res = engine.ReadFile(fileName);
foreach (var rec in res)
{
var type = rec.GetType();
if (type.Name == "AccountBalanceHeader") continue;
if (type.Name == "AccountBalanceTrailer") continue;
var data = rec as AccountBalanceDetail; // Throws an error if AccountBalanceDetail.Id has a getter and setter
using (var ctx = new ApplicationDbContext())
{
// Throws an error if there is no valid Id on AccountBalanceDetail
// EntityType 'AccountBalanceDetail' has no key defined. Define the key for this EntityType.
ctx.AccountBalanceDetails.Add(data);
ctx.SaveChanges();
}
//Console.WriteLine(rec.ToString());
}
return true;
}
Entity Framework needs the key to be a property, not a field, so you could try declaring it instead as:
public int Id {get; set;}
I suspect FileHelpers might well be confused by the autogenerated backing field, so you might need to do it long form in order to be able to mark the backing field with the [FieldHidden] attribute, i.e.,
[FieldHidden]
private int _Id;
public int Id
{
get { return _Id; }
set { _Id = value; }
}
However, you are trying to use the same class for two unrelated purposes and this is generally bad design. On the one hand AccountBalanceDetail is the spec for the import format. On the other you are also trying to use it to describe the Entity. Instead you should create separate classes and map between the two with a LINQ function or a library like AutoMapper.

Leaking Quriable objects to upper layers

i have an application that is flexible, that the user can:
filter by any field
sort by any multiple of fields.
and because it will run in ASP.Net Site + some Xamarin C# Apps, i will also have paging in it.
For network performance, it will send projection on the required fields that will be shown.
So if i include in each "Service" method, a parameter "UQueryConstraints", that can send filter expression + oderBy expression + page numbers + Projection of the fields, to be used by the Repository, which will apply it to the DBContext, is this is going to be considered a Data leak to the domain services or not?
as seen in this Pic:
http://1drv.ms/1Ngi3Kn
e.g.:
notice:
"UQueryConstraints", it will not leak any "IQueryable".
The "AmbientDbContextLocator", from:
<http://mehdi.me/ambient-dbcontext-in-ef6/>
<https://github.com/mehdime/DbContextScope>
public class UIView
{
public static void Display()
{
object constraintsB = new UQueryConstraints<Car>().Filter(x => x.carNo <= 6).SortBy(x => x.eName).Page(1, 5);
//.Projection( field1, field2, field3)
Debug.WriteLine("---------------test CarModel -------------------");
CarModel carModel1 = new CarModel();
carModel1.printCars(constraintsB);
}
}
public class CarModel
{
private CarService _carService = new CarService();
void printCars(UQueryConstraints<Car> constraints)
{
foreach ( c in _carService.getCarsList("", constraints)) {
Debug.WriteLine("Reading from converted back: aName =" + c.aName + ", eName = " + c.eName);
}
}
}
public class CarService
{
public IList<Car> getCarsList(string Text, UQueryConstraints<Car> constraints)
{
object dbContextScopeFactory = new DbContextScopeFactory();
object ambientDbContextLocator = new AmbientDbContextLocator();
using (dbContextScope == dbContextScopeFactory.Create()) {
//after creating the Scope:
//1. create the repository
//2. call repository functions
object carRep = new CarRepository(ambientDbContextLocator);
return carRep.getCarsList("", constraints);
}
}
}
public class CarRepository : URepositoryFramwork.URepository
{
public CarRepository(IAmbientDbContextLocator contextLocator)
{
base.New(contextLocator);
}
public IList<Car> getCarsList(string Text, UQueryConstraints<Car> constraints)
{
object query = this.DataSet.Where(constraints.FilterExpression);
//.Select(constraints._projection2)
IList<Car> items;
if (constraints == null) {
items = query.ToList();
} else {
items = constraints.ApplyTo(query).ToList();
}
return items;
}
}
Regards.
Here are few points.
You don't need UQueryConstraints at all and you don't need to do any filtering in the UI at all.
I'd ague that the model is something that needs to be returned from the service so I wouldn't create CarModel in the UI layer and then pushed values to it, it doesn't make sense to me.
I'd have a method on the service that request some data and then returns it in some shape or form to the UI.
I'd inject the service to UIView.
I don't understand why there's so much noise around the context and why do you create it in getCarsList it seems like getCarList should be a class called RequestCars and both the repository and the service should be removed in favor of something like depicted in the command pattern.
I don't like the whole abstraction here at all, seems like over engineering to me and who says that IQueryable should be abstracted? it's like abstracting language/framework features whereas you should abstract domain features and only when necessary.
Abstracting 3rd-party frameworks can be fine to some extent but this isn't one of these cases.

Combining Includes with Entity Framework

I generally use a generic repository to boilerplate my EF queries so I have to write limited code and also use caching. The source code for the repository can be found here.
The backbone query within the code is this one below. FromCache<T>() is an IEnumerable<T> extension method that utilizes the HttpContext.Cache to store the query using a stringified representation of the lambda expression as a key.
public IQueryable<T> Any<T>(Expression<Func<T, bool>> expression = null)
where T : class, new()
{
// Check for a filtering expression and pull all if not.
if (expression == null)
{
return this.context.Set<T>()
.AsNoTracking()
.FromCache<T>(null)
.AsQueryable();
}
return this.context.Set<T>()
.AsNoTracking<T>()
.Where<T>(expression)
.FromCache<T>(expression)
.AsQueryable<T>();
}
Whilst this all works it is subject to the N+1 problem for related tables since If I were to write a query like so:
var posts = this.ReadOnlySession.Any<Post>(p => p.IsDeleted == false)
.Include(p => p.Author);
The Include() will have no effect on my query since it has already been run in order to be cached.
Now I know that I can force Entity Framework to use eager loading within my model by removing the virtual prefix on my navigation properties but that to me feels like the wrong place to do it as you cannot predict the types of queries you will be making. To me it feels like something I would be doing in a controller class. What I am wondering is whether I can pass a list of includes into my Any<T>() method that I could then iterate though when I make the call?
ofDid you mean something like...
IQueryable<T> AnyWithInclude<T,I>(Expression<Func<T,bool>> predicate,
Expression<Func<T,I>> includeInfo)
{
return DbSet<T>.where(predicate).include(includeInfo);
}
the call
Context.YourDbSetReference.AnyWithInclude(t => t.Id==someId, i => i.someNavProp);
In response to extra question on as collection.
I realised late, there was an overload on Property. You can just pass a string
This might work but call is not easy. Well I find it hard.
IQueryable<T> GetListWithInclude<I>(Expression<Func<T, bool>> predicate,
params Expression<Func<T, I>>[] IncludeCollection);
so i tried
public virtual IQueryable<T> GetListWithInclude(Expression<Func<T, bool>> predicate,
List<string> includeCollection)
{ var result = EntityDbSet.Where(predicate);
foreach (var incl in includeCollection)
{
result = result.Include(incl);
}
return result;
}
and called with
var ic = new List<string>();
ic.Add("Membership");
var res = context.DbSte<T>.GetListWithInclude( t=>t.UserName =="fred", ic);
worked as before.
In the interest of clarity I'm adding the solution I came up with based upon #soadyp's answer.
public IQueryable<T> Any<T>(Expression<Func<T, bool>> expression = null,
params Expression<Func<T, object>>[] includeCollection)
where T : class, new()
{
IQueryable<T> query = this.context.Set<T>().AsNoTracking().AsQueryable<T>();
if (includeCollection.Any())
{
query = includeCollection.Aggregate(query,
(current, include) => current.Include(include));
}
// Check for a filtering expression and pull all if not.
if (expression != null)
{
query = query.Where<T>(expression);
}
return query.FromCache<T>(expression, includeCollection)
.AsQueryable<T>();
}
Usage:
// The second, third, fourth etc parameters are the strongly typed includes.
var posts = this.ReadOnlySession.Any<Post>(p => p.IsDeleted == false,
p => p.Author);