Home > Enterprise >  C# Linq - how to create collection of two types using one collection of one type
C# Linq - how to create collection of two types using one collection of one type

Time:01-21

I have a List<Category>

public partial class Category
{
    public int CategoryId { get; set; }

    public string? Name { get; set; }

    public int? ImageId { get; set; }

    public virtual ProductImage? Image { get;
        set; }

    public virtual ICollection<Product> Products { get; } = new List<Product>();
}

and I want to create from this a List<(string, Image)> by using a single linq expression. How can I do this?

Doing it with multiple instructions would look like this:

List<Category> CategoriesWithImages = Context.Categories.Include(c => c.Image).ToList();
List<(string, Image)> values = new List<(string, Image)>();
CategoriesWithImages.ForEach(c => values.Add((c.Name, Image.FromStream(new MemoryStream(c.Image.Image)))));

Is there a better way to do this?

Edit: Unfortunately I have to use Include() method for this to load images from another table

CodePudding user response:

You could do it like this:

List<(string, Image)> values = CategoriesWithImages
    .Select(c => new ValueTuple<string, Image>(c.Name, Image.FromStream(new MemoryStream(c.Image.Image))))
    .ToList();

Using records is another option (requires C# 9 ):

List<ImageData> values = CategoriesWithImages
    .Select(c => new ImageData(c.Name, Image.FromStream(new MemoryStream(c.Image.Image))))
    .ToList();

record struct ImageData(string Name, Image Image);

Note: structy records require C# 10

CodePudding user response:

Right now Context.Categories.Include(c => c.Image) loads the entire Categories and probably Images tables in memory. That's wasteful

A better way would be to retrieve only the necessary fields from the database and not use Image. This class, like all types in the System.Drawing namespace, is only meant for drawing on a desktop screen using GDI and uses limited OS-wide resources. That's why there's no Image.FromBytes - the image is expected to be stored on the local disk or get loaded from the application's resources.

From the comments, it seems there are ~20 thumbnails, 15KB each, displayed in a Windows Forms application.

A LINQ query can load just the name and images as byte[] arrays, in a dictionary or List, depending on usage:

record MyImage(string Name,byte[] Content);

...

var categoryThumbnails=await Context.Categories
    .ToListAsync(new MyImage(c.Name,c.Image.Image));

or

Dictionary<string,byte[]> categoryThumbnails=await Context.Categories
        .ToDictionaryAsync(c=>c.Name,c=>c.Image.Image));

EF Core will generate the necessary JOINs between Categories and the Image table and load only the Name and Image properties.

At this point, a good idea would be to store the images locally, eg in a temporary folder, and load them from disk with the Image-derived Bitmap class and the Bitmap(string) constructor.

  • Related