Code a SQL projection and mapping at the same time? - c#

This works to carve out a DDL object from an Address object from our database:
public class DDL {
public int? id { get; set; }
public string name { get; set; }
}
List<DDL> mylist = Addresses
.Select( q => new DDL { id = q.id, name = q.name })
.ToList();
However, we'd like to keep our POCO to ViewModel mappings in a single place outside of our MVC controller code. We'd like to do something like this:
List<DDL> mylist = Addresses
.Select( q => new DDL(q)) // <-- constructor maps POCO to VM
.ToList();
But SQL cannot use the constructor function. The object initializer above doesn't use functions to map fields. Of course you could do .AsEnumerable().Select( q => new DDL(q)), but this selects all the fields in SQL (including the data), sends it to C#, then C# carves out the fields we need (terribly inefficient to transfer data we don't need.)
Any suggestions? We happen to be using Entity Framework 6 to pull data.

All you need is to define the expression somewhere and use it. For example, in your ViewModel as a static read-only field.
public class SomeViewModel
{
public static readonly Expression<Func<SomeEntity, SomeViewModel>> Map = (o) => new SomeViewModel
{
id = o.id,
name = o.name
}
public int id { get; set; }
public string name { get; set; }
}
// Somewhere in your controller
var mappedAddresses = Addresses.Select(SomeViewModel.Map);
I personally made myself a little static Mapper that keeps all the maps and use them for me. The maps are declared in a static initializer in all my ViewModels. The result gives me something that feels like AutoMapper, yet doesn't require the lib or the complicated mapping code (but also won't do any magic for you).
I can write something like this:
MyCustomMapper.Map<Entity, ViewModel>(entity);
and it's overloaded to accept IEnumerables, IQueryables and a single ViewModel. I also added overloads with only 1 generic type (the entity) that accept a type parameter. This was a requirement for me.

You can use anonymous types to restrict what to select from the DB and then use those fields to construct your object :
List<DDL> mylist = Addresses
.Select( q => new { id, name })
.AsEnumerable()
.Select(i => new DDL(i.id, i.name) // <- On the Enumerable and not on the Queryable
.ToList();

Are you against using 3rd party libraries? Automapper's QueryableExtensions does exactly what you want.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.ToList();
It even has nice features like being able to filter on the transformed object and that filter being performed server side.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.Where(d => d.name = "Smith") //This gets translated to SQL even though it was performed on DDL.
.ToList();

Related

Single Element Using Lookup operator

I am stuck in an issue with MongoDB. I am using C# driver for MongoDB.
So basically I am trying to join Categories document with Users document. Each category document has one property:
public Guid UserId { get; set; }
This property is an ObjectId behind the scenes. I have another property:
public UserDoc User { get; set; }
Now I am trying to fill this User property with all the user details based on the UserId property. This is the code I am trying to achieve this:
categoriesCollection.Aggregate()
.Lookup<CategoryDoc, UserDoc, CategoryDoc>(
usersCollection,
x => x.UserId,
x => x.Id,
x => x.User)
As expected, 'Lookup' is expecting an array of User documents but I have a property referencing a single user object and thus, it throws an error:
An error occurred while deserializing the User property of class TatSat.API.Documents.CategoryDoc: Expected a nested document representing the serialized form of a TatSat.API.Documents.UserDoc value, but found a value of type Array instead.
Can someone help me with this? I am new to Mongo so this is a bit of a pain for me. Kindly note that I am looking for a strongly typed solution and don't want to mess with BsonDocuments if that can be avoided.
Thanks in advance.
So I finally figured out in some ways. Basically I decided to have another class:
public class CategoryDocWithTemporaryData : CategoryDoc
{
public UserDoc[] Users { get; set; }
public static Expression<Func<CategoryDocWithTemporaryData, CategoryDoc>> ToCategoryDoc => c =>
new CategoryDoc
{
Id = c.Id,
//other properties
User = c.Users.First()
};
}
Then I use the lookup as:
categoriesCollection.Aggregate()
.Lookup<CategoryDoc, UserDoc, CategoryDocWithTemporaryData>(
usersCollection,
c => c.UserId,
c => c.Id,
c => c.Users)
.Project(CategoryDocWithTemporaryData.ToCategoryDoc)
This, however needs me to use additional projection where I have to manually select all properties which is something I wanted to avoid. But I think I can live with it until I come across a better approach.
Lookup will always return an array, even if it only has 1 element. If you need just the first (or only) document in that array, use a project/set/addFields stage with arrayElemAt.

EF Core. How to load only necessary properties from deep nested entity

I develop a simple application, something like a chat where every message may contain text and files.
Entities are related like this:
Message group -> every message group has a collection of messages -> every message has a property 'FileCollection' -> 'File collection' has 4 collections: Images, Video, Audio, Files. All of them has the same relations in database. To show this logic here is my query to get all message groups with their entities:
var messageGroups = await _db.MessageGroups
.Where(mg => mg.UserId == id)
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Images)
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Video)
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Audio)
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Files)
.ToListAsync();
The problem is that every type of file (Image, Audio etc.) has a 'Data' column in Db (property in EF Core) which contains their blob data. I want to exclude all blob from query, because query becomes extremely heavy loading all user files from Db. Something like this (but exclude method does not exist):
.Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Video).exclude(video => video.Data);
Is there any way to use explicit loading at the end of the query? Or maybe there are attributes like [JsonIgnore] which excludes class property from Json serializing? Or any other method?
If it helps: ImageFile, AudioFile and others inherit from File super class:
public class File
{
[Column("id")]
public int Id { get; set; }
[Column("content_type")]
public string ContentType { get; set; }
[Column("file_name")]
public string FileName { get; set; }
[Column("length")]
public long Length { get; set; }
[Column("related_file_collection_id")]
public int FileCollectionId { get; set; }
public FileCollection FileCollection { get; set; }
}
public class ImageFile : File
{
[Column("data")]
public byte[] Data { get; set; }
}
I need all properties from 'File' class without 'Data' property from it's child classes.
I believe the best way would be to configure your DbContext for those entities containing Blob columns using Table Splitting.
Don't let the name confuse you. This technique is not to move the Blob to a different table. Instead, it will allow you to fit two "entities" on the same row.
In your case, you could split your File from your FileData, meaning that you will have a different entity for each of them, but both will be stored on the same row on the same table.
By using table splitting, you can .Include your File and it won't include the FileData unless you explicitly tell EF Core to do it.
If you don't wanna go down that road, I believe that you would either need to write some custom Selects or custom SQL.
You can use the [NotMapped] attribute but then you will not be able to retrive that column from the db from other queries.
You can also create a DTO and select only the required properties, but that would not be elegant considering all your includes.
As it was advised here, Table splitting can be the answer, but it is a little complicated. I've just modified my query using .Select(). Not very elegant, also I have a cycle inside cycle, but it works:
List<MessageGroup> messageGroups = await _db.MessageGroups.Where(mg => mg.UserId == id).AsNoTracking().AsSplitQuery().Include(m => m.Messages).ThenInclude(mes => mes.FileCollection.Images)
.Include(m => m.Messages).ThenInclude(mes => mes.UrlPreviews).ToListAsync();
foreach (var mg in messageGroups)
{
foreach (var m in mg.Messages)
{
m.FileCollection.Video = await _db.Video.Where(video => video.FileCollectionId == m.FileCollection.Id).Select(v => new VideoFile(v.ContentType, v.FileName, v.Length, v.FileCollectionId, null)).ToListAsync();
m.FileCollection.Audio = await _db.Audio.Where(audio => audio.FileCollectionId == m.FileCollection.Id).Select(a => new AudioFile(a.ContentType, a.FileName, a.Length, a.FileCollectionId, null)).ToListAsync();
m.FileCollection.Files = await _db.Files.Where(file => file.FileCollectionId == m.FileCollection.Id).Select(f => new OtherFile(f.ContentType, f.FileName, f.Length, f.FileCollectionId, null)).ToListAsync();
}
}
null in file constructors is where byte[] blob data should be.
One other solution is to (re-)construct your entity using Select.
_dbContext.MyEntity.Select(
myEntity => new MyEntity()
{
Property1 = myEntity.Property1,
Property2 = myEntity.Property2
// do not select / load Property3
});
EF Core will translate this query in such a way that in the SELECT SQL-statement it only accesses the fields used in your query. This way, the other large columns are not loaded from the database.
However, this solution only works if you have a suitable class constructor or you can set properties to empty or dummy values, and is therefore not generally applicable.

Error When Querying For A Substring Using Dynamic Linq

I'm trying to use dynamic linq to obtain a subset of people from a database using Entity
Framework (EF). I'm running into a problem when using the contains operation. Here is the entity
for the People table:
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
Here is a query that works successfully.
var people = personContext
.People
.OrderBy("id asc")
.Skip(0)
.Take(5)
.ToList();
Notice that I'm using dynamic linq in the OrderBy method. However, when I try to apply
filtering, I get an exception.
var people = personContext
.People
.Where("id.Contains(15)")
.OrderBy("id asc")
.Skip(0)
.Take(5)
.ToList();
What I'd like to get back is a subset of people with ids that contain the substring "15", such as:
"015", "115", "151", "152", etc.
When I execute the code, I get the following error.
System.Linq.Dynamic.ParseException was unhandled by user code
Message=No applicable method 'Contains' exists in type 'String'
What is the syntax for determining if the Id field contains the string "15"?
What is the syntax for determining if the Id field contains the string "15"?
Well, definitely not .Where("id.Contains(15)") which is trying to invoke the method Contains with numeric value 15.
According to the documentation, you can use either a string literal:
.Where("id.Contains(\"15\")")
or substitution values:
.Where("id.Contains(#0)", "15")
I feel misconception here... You are not supposed to use LINQ like this.
As a start you need to invoke the overloads that accept lambdas; then you specify the property in the lambda and if its a string you invoke Contains on it. Like so:
var people = personContext
.People
.Where(p => p.Id.Contains("15"))
.OrderByDescending(p => p.Id)
.Skip(0) // You don't need this line.
.Take(5)
.ToList();
The EF itself will do the heavy lifting and translate these pure C# codes into the correct SQL statements.
You can't use Contains in the LINQ query. Instead you can try this
var people = (from p in personContext.Set<People>()
where p.Id.Contains("15")
orderby p.Id
select p).Skip(0).Take(5).ToList();

"The entity or complex type cannot be constructed in a LINQ to Entities query" in Controler

i have this code in my controller
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new SellingItem
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});
and when i try to run it i get
an error
The entity or complex type cannot be constructed in a LINQ to Entities query
now it look to me that my linq query is too complex for entity framework to handle,
so i have 2 workarounds, but i don't like both of them.
1.
load the whole table in memory and make the query like this
2.
use a SQL statement, which will be faster but will not follow the entity framework rules
is there a better way?
-----UPDATE---------------------------------
as it turn out (thanks a lot guys)
i was wrong by stating
now it look to me that my linq query is too complex for entity framework to handle,
and it didn't work because of the fact that i used the same class for the result. i created a new class and now it works amazing!!
You don't need to resort to any of your workarounds to fix that exception per se. The problem is that SellingItem is a class that is part of your Entity Framework model. The reason for this is explained in the comments on this answer.
Either select an anonymous object like so:
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});
Or create an object specifically for the select that you are trying to do:
public class NewClass
{
public ItemClass Item { get;set; }
public ConditionClass Condition { get;set; }
public in ExpInDays { get;set; }
public int Qty { get;set; }
}
Naturally you will need to make sure that the types in this specific class match up to their respective types.
You can then use the new class to do the select:
// Use new class
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new NewClass
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});

Entity Framework 6 error - ICollection does not contain a definition for sub-entity

I'm using Entity Framework 6 with MVC and I'm using this query in the controller:
using (var context = new MyDbContext())
{
IQueryable<ContentArticle> query;
query = context.ContentArticle.Where(
c => c.ContentArticleSubdivisions.Subdivisions.Name == subdivision
The compiler is complaining that
'System.Collections.Generic.ICollection<MySite.DAL.Models.ContentArticleSubdivision>' does not contain a definition for 'Subdivisions'
However my content article model includes this property:
public virtual ICollection<ContentArticleSubdivision> ContentArticleSubdivisions { get; set; }
and my content article subdivision model includes this:
public partial class ContentArticleSubdivision
{
...
public virtual ICollection<Subdivision> Subdivisions { get; set; }
}
so what am I doing wrong? I think I need to modify the query so that it's looking up all possible Subdivisions that could be contained in the ContentArticleSubdivisions collection?
You need to use some sort of collection based method to do that. I'm not exactly sure what you are trying to query for here. I'm going to assume you want all articles where a subdivision exists matching that name.
query = context.ContentArticle.Where(
c => c.ContentArticleSubdivisions.Any(cs => cs.Subdivisions.Any(s => s.Name == subdivision))
);
You can also simplify this using the query comprehension syntax like so
query = from ca in context.ContentArticle
from cas in ca.ContentArticleSubdivisions
from s in cas.Subdivisions
where s.Name == subdivision
select ca;

Categories