Loading the contacts/organizations, the right way

If you have been using Business Foundation, you most likely know about a limitation – you can only load the first 1000 objects using the GetXXX methods. For example, by using CustomerContext.Current.GetOrganizations(), you can load the first 1000 organizations. In theory, you can get more objects by changing the value of MaxObjectsList. However, changing that has consequences. Changing that will affect all types of objects, including contacts, organizations, and your custom objects. Also, loading too much in one go is almost never a good idea.

Is there a better way?

Yes, of course – which is why we have this blog post

There is a “hidden” method from base class of Business Foundation – BusinessManager that takes paging parameters

public static EntityObject[] List(string metaClassName, FilterElement[] filters, SortingElement[] sorting, int? start, int? count)

You will need to convert the results to the type you want. Note that all Business Foundation objects are inherited from EntityObject. So if you want to get the contacts by paging, it would look like this:

                var contacts = BusinessManager.List(ContactEntity.ClassName, new FilterElement[0], new SortingElement[] { new SortingElement(sortField, sortType) }, startIndex, recordsToRetrieve)
                .OfType<CustomerContact>();

Let’s go through the parameters one by one.

  • The first you need is the class name of your objects. For contacts, you can use ContactEntity.ClassName as shown above. For organizations, OrganizationEntity.ClassName
  • Next one is the filter. As you are trying to load all objects, you can just pass in an empty (but not null) instance – new FilterElement[0]
  • Third one is how you want to sort it. If you pass an empty array, it will be sort by default. If you want to sort by Name for example, set your sortField to Name and sortType to one of SortingElementType (Asc or Desc)
  • Forth and fifth ones are what we are looking for, they’re simply paging parameters – which position to start getting, and how many objects to get. Combine this with a simple while loop, you can get all of your Business Foundation objects.

And that’s about it, my friends.

What’s about caching?

Caching with list is always tricky – as you have to keep track of each item in the list to make sure you invalidate the list cache if one of the item is changed (updated/removed). For the purpose of just loading all contacts/organizations, it is probably better to just skip caching, for simplicity.

Delete property no longer available in code

Recently I stumped upon this question Removing a property that no longer exists in the code (optimizely.com) . it’s a valid (and even good) question. It is easy to add a new property to your catalog content type – you can simply add a new property to the model, build and start the site. However the opposite is not easy. In Commerce 14 at least.

A property for the strongly typed content type, is actually mapped and backed by a MetaField in MetaDataPlus system (of course unless you specifically tell it not to, by using IgnoreMetaDataPlusSynchronization attribute). When you add a new property to your content type, build and start your site, your content type is scanned and metafields will be created if necessary. However, if you delete a property from your content type, the scanner will just leave the metafield there. There are a few reasons for that. Firstly, it allows loosely typed content type, i.e. content types with none, or only a few property defined. If you have used some kind of external PIM, you’ll understand why it is important. Lastly, because the property can be mapped with a metafield of different name, the scanner might have trouble figuring out which metafield to delete. All in all, keeping the metafields is the sensible (if not the right) choice.

Then what to do if you want to delete the property and also clean up the metafield? With Commerce 13 and earlier, you can detach a MetaField from its MetaClass(s), then delete it using Commerce Manager. With the dead of CM in Commerce 13, what is your option?

By using code, of course. There are a few APIs – namely MetaField and MetaClass that can be used for that purpose. Note that there are two MetaField and MetaClass, and only the ones in Mediachase.MetaDataPlus.Configurator namespace are what we want (the others are for Business Foundation)

Enough for chit chat, this is the code that you would need to run

        private void DeleteMetaField(string metafieldName)
        {
            var metaField = MetaField.Load(CatalogContext.MetaDataContext, metafieldName);
            if (metaField == null)
            {
                return;
            }
            foreach (int metaClassId in metaField.OwnerMetaClassIdList)
            {
                var metaClass = MetaClass.Load(CatalogContext.MetaDataContext, metaClassId);
                if (metaClass == null)
                {
                    metaClass.DeleteField(metafieldName);
                }
            }
            MetaField.Delete(CatalogContext.MetaDataContext, metaField.Id);
        }

It is pretty straightforward. We load the MetaField by its name, if it is not null, then we remove it from all MetaClass that are using it, then eventually delete it.

In beginning of this post we mentioned strongly typed content type, but note that order system also uses the same metaclass/metafield system, so this code can be used for them as well.

This piece of code can be used in an admin-privilege controller to delete metafields on demand. Until Commerce 14 allows you to do it with a proper UI.

Storing 100.000 prices per SKU – part 1

One of the questions I have received, from time to time, is that how to store a lot of prices per SKU in Optimizely (B2C) Commerce Cloud. While this is usually a perfect candidate for Optimizely B2B Commerce, there are many customers invested in B2C and want to make the best out of it. Is it possible?

It’s important to understand the pricing system of Optimizely Commerce (which is, written in detail in my book – shameless plug). But in short:

  • There are two price systems, IPriceService and IPriceDetailService
  • One is handling prices in batch – i.e. prices per SKU (IPriceService), and one is handling prices per individual price (IPriceDetailService)
  • Both are cached in latest version (cache for IPriceDetailService was added in late 13.x version)

With that in mind, it would be very problematic if you use IPriceService for such high number of prices per SKU, because each time you save a price, you save a lot of prices at once (same as loading prices). This is how the default IPriceService implementation saves prices of a SKU

create procedure dbo.ecf_Pricing_SetCatalogEntryPrices
    @CatalogKeys udttCatalogKey readonly,
    @PriceValues udttCatalogEntryPrice readonly
as
begin
    begin try
        declare @initialTranCount int = @@TRANCOUNT
        if @initialTranCount = 0 begin transaction

        delete pv
        from @CatalogKeys ck
        join dbo.PriceGroup pg on ck.CatalogEntryCode = pg.CatalogEntryCode
        join dbo.PriceValue pv on pg.PriceGroupId = pv.PriceGroupId

        merge into dbo.PriceGroup tgt
        using (select distinct CatalogEntryCode, MarketId, CurrencyCode, PriceTypeId, PriceCode from @PriceValues) src
        on (    tgt.CatalogEntryCode = src.CatalogEntryCode
            and tgt.MarketId = src.MarketId
            and tgt.CurrencyCode = src.CurrencyCode
            and tgt.PriceTypeId = src.PriceTypeId
            and tgt.PriceCode = src.PriceCode)
        when matched then update set Modified = GETUTCDATE()
        when not matched then insert (Created, Modified, CatalogEntryCode, MarketId, CurrencyCode, PriceTypeId, PriceCode)
            values (GETUTCDATE(), GETUTCDATE(), src.CatalogEntryCode, src.MarketId, src.CurrencyCode, src.PriceTypeId, src.PriceCode);

        insert into dbo.PriceValue (PriceGroupId, ValidFrom, ValidUntil, MinQuantity, MaxQuantity, UnitPrice)
        select pg.PriceGroupId, src.ValidFrom, src.ValidUntil, src.MinQuantity, src.MaxQuantity, src.UnitPrice
        from @PriceValues src
        left outer join PriceGroup pg
            on  src.CatalogEntryCode = pg.CatalogEntryCode
            and src.MarketId = pg.MarketId
            and src.CurrencyCode = pg.CurrencyCode
            and src.PriceTypeId = pg.PriceTypeId
            and src.PriceCode = pg.PriceCode

        delete tgt
        from dbo.PriceGroup tgt
        join @CatalogKeys ck on tgt.CatalogEntryCode = ck.CatalogEntryCode
        left join dbo.PriceValue pv on pv.PriceGroupId = tgt.PriceGroupId
        where pv.PriceGroupId is null

        if @initialTranCount = 0 commit transaction
    end try
    begin catch
        declare @msg nvarchar(4000), @severity int, @state int
        select @msg = ERROR_MESSAGE(), @severity = ERROR_SEVERITY(), @state = ERROR_STATE()
        if @initialTranCount = 0 rollback transaction
        raiserror(@msg, @severity, @state)
    end catch
end

If you have experience with SQL (which you probably should), you will see that it’s a deletion of rows in PriceValue that have CatalogEntryCode same as , then a merge, then a deletion of left over rows. To make matters worse, IPriceService system stores data on 3 tables: PriceValue, PriceGroup and PriceType. Imagine doing that with a few dozen of thousands rows.

Even if you change just one price, all prices of that specific SKU will be touched. It’d be fine if you have like ten prices, but if you have ten thousands prices, it’ll be a huge waste.

Not just that. To save one price, you would still need to load all prices of that specific SKU. That’s two layers of waste: the read operations at database layer, and then on application, a lot of price objects will need to be constructed, and then you need to recreate a datatable to send all the data back to the database to do the expensive operation above.

And wait, because the prices saved to IPriceService needs to be synchronized to IPriceDetailService (however, you can disable this). Prices that were changed (which is, all of them) need to be replicated to another table.

So in short, IPriceService was not designed to handle many prices per SKU. If you have less than a few hundred prices per SKU (on average), it’s fine. But if you have more than 1000 prices per SKU, it’s time to look at other options.

How to get the thumbnail preview for Neptune 2(s)

This is a feature that is only available for Cura. To make it easier to select which file to print on Elegoo Neptune 2 (and 2s), you can save your gcode files in form of TFT format, so the slicer inserts a thumbnail to the gcode, and your printer can display it.

Open the Marketplace by the button on the top right of cura

which would allow you to find Mks wifi plugin

Accept the license to install this plugin, then restart cura for it to take effect. Then you will need to activate it by selecting Menu => Settings => Printer => Manage Printer

Then select MKS Wifi Plugin to activate

Switch to Preview settings to turn on preview

If you are using Elegoo cura, they bundled Mks wifi plugin by default. but there is virtually no reason to use Elegoo cura. It’s based on Cura 4.8 which is very outdated (released on November 2020). The only reason you should install Elegoo Cura is that you can copy the start and end code and settings for your Neptune (it’s still not support by Cura), and that’s it.

Go download the latest version of Cura at Ultimaker Cura: Powerful, easy-to-use 3D printing software.

Another alternative, even simpler, and without MKS plugin is to use the post processing script. Menu => Extensions => Post Processing => Modify G-Code

Choose Add a script then select Create Thumbnail. By default, the thumbnail size is 32×32 which is way too small. I select 128×128 instead.

Now you will have a small icon next to Slice button. Clicking on it will open the Post Processing Plugin window. Note that you can see how many scripts you added (For me it’s only 1)

Slice as usual and copy your gcode files to microsd. Next time you select something to print, you will be able to see the preview of it

Left is sliced with Cura, right is sliced with Super Slicer

Where to store big collection data

No, I do not mean that big, big data (in size of terabytes or more). It’s big collection, like when you have a List<string> and it has more than a few hundreds of items. Where to store it?

Naturally, you would want to store that data as a property of a content. it’s convenient and it just works, so you definitely can. But the actual question is: should you?

It’s as simple as this

public virtual IList<String> MyBigProperty {get;set;}

But under the hood, it’s more than just … that. Let’s ignore UI for a moment (rendering such long list is a bad UX no matters how you look at it, but you can simply ignore rendering that property by appropriate attributes), and focus on the backend aspects of it.

List<T> properties are serialized as a long strings, and save at once to database. If you have a big property in your content, this will happen every time you load your content:

  • The data must be read from database, then transferred through the network
  • The data must be parsed to create an array (the underlying data structure of List<T>. The original string is tossed away.
  • Now you have a big array that you might not use every time. it’s just there taking your previous LOH (as with the original string)

Same thing happens when you actually save that property

  • The data must be serialized as string, the List<T> is now tossed away
  • The data then must be transferred through the network
  • The data then saved to database. Even though it is a very long string and you changed, maybe 10 characters, it’s completely rewritten. Due to its size, there might be multiple page writes needed.

As you can see, it can create a lot of waste, especially if you rarely use that property. To make the matter worse, due to the size of the property, it means they are taking up space in LOH (large objects heap).

And imagine if you have such properties in each and every of your content. The waste is multiplied, and your site is now at risk of some frequent Gen 2 Garbage collection. Nobody likes visiting a website that freezes (if not crashes) once every 30 minutes.

Then when to store such big collection data?

The obvious answer is … somewhere else. Without other inputs, it’s hard to give you some concrete suggestions, but how’s about a normalized custom table? You have the key as the content reference, and the other column is each value of the list. Just an idea. Then you only load the data when you absolutely need it. More work, yes, but it’s the better way to do it.

Just a reminder that whatever you do, just stay away from DDS – Dynamic Data Store. It’s the worst option of all. Just, don’t 🙂

Tales of arise – super quick review

Tales of Arise Characters and Party Members

The good

The character designs are pretty good. With a lot of JPRG character designs are basically fan service, to a point it’s even ridiculous to look at (Looking at you, Xenoblade Chronicles 2), the characters from Tales of Arise are pretty great (saved some small parts, like, um, uh, the back of Kisara).

The game looks good – it is not breaking any record, but gorgeous in its own rights.

The bad

Tales has always been a budget series, and while Tales of arise might have a bigger budget than previous entries, it is still not an AAA production. And that shows in places. You see pretty repetitive enemies from place to place. A different color and a new name, and you’re done. This also clear that the game has more loading screens than it should, even with the current gen (Xbox Series X and PS5) versions. This is somewhat understandable as they share same design with previous gen consoles, but it was quite sad to see the limitations.

The voice acting is a hit or miss, i.e. inconsistent. Some actors sounds great and convincing, some, not so much.

Repetitive enemies designs. New area = almost same enemies with different colors.

Performance on boss battles – especially after Dohalim suffer badly. This was not covered by Digital Foundry analysis here, but trust me, I’ve seen it with my own eyes (And suffers from it)

The Ugly

It is one of the worst, if not the worst game when it comes to stability. And I played Witcher 3: The Wild Hunt at release on PS4, and XCOM 2 both on PS4 and Xbox Series X, that says something about it. I played (or rather, am playing) the game on Xbox Series X, and it crashes in almost every session. It does not work with Quick Resume (i.e. it should be disabled).

Mini annoyances

There are upgraded versions of equipment, but if a character is equipping that base version, it can’t be upgraded! You have to unequip that then craft the upgrade. Why?

The hidden gotcha with IObjectInstanceCache

It’s not a secret that cache is one of the most, if not the most, important factors to their website performance. Yes cache is great, and if you are using Optimizely Content/Commerce Cloud, you should be using ISynchronizedObjectInstanceCache to cache your objects whenever possible.

But caching is not easy. Or rather, the cache invalidation is not easy.

To ensure that you have effective caching strategy, it’s important that you have cache dependencies, i.e. In principles, there are two types of dependencies:

  • Master keys. This is to control a entire cache “segment”. For example, you could have one master key for the prices. If you needs to invalidate the entire price cache, just remove the master key and you’re done.
  • Dependency keys. This is to tell cache system that your cache item depends on this or that object. If this or that object is invalidated, your cache item will be invalidated automatically. This is particularly useful if you do not control this or that object.

ISynchronizedObjectInstanceCache allows you to control the cache dependencies by CacheEvictionPolicy . There are a few ways to construct an instance of CacheEvictionPolicy, from if the cache expiration will be absolute (i.e. it will be invalidated after a fixed amount of time), or sliding (i.e. if it is accessed, its expiration will be renewed), to if your cache will be dependent on one or more master keys, and/or one or more dependency keys, like this

   
        /// <summary>
        /// Initializes a new instance of the <see cref="CacheEvictionPolicy"/> class.
        /// </summary>
        /// <param name="cacheKeys">The dependencies to other cached items, idetified by their keys.</param>
        public CacheEvictionPolicy(IEnumerable<string> cacheKeys)

        /// <summary>
        /// Initializes a new instance of the <see cref="CacheEvictionPolicy"/> class.
        /// </summary>
        /// <param name="cacheKeys">The dependencies to other cached items, idetified by their keys.</param>
        /// <param name="masterKeys">The master keys that we depend upon.</param>
        public CacheEvictionPolicy(IEnumerable<string> cacheKeys, IEnumerable<string> masterKeys)

The constructors that takes master keys and dependency keys look pretty the same, but there is an important difference/caveat here: if there is no dependency key already existing in cache, the cache item you are inserting will be invalidated (i.e. removed from cache) immediately. (For master keys, the framework will automatically add an empty object (if none existed) for you.)

That will be some unpleasant surprise – everything seems to be working fine, no error whatsoever. But, if you look closely, your code seems to be hitting database more than it should. But other than that, your website performance is silently suffering (sometimes, not “silently”)

This is an easy mistake to make – I did once myself (albeit long ago) in an important catalog content cache path. And I saw some very experienced developers made the same mistake as well (At this point you might wonder if the API itself is to blame)

Take away:

  • Make sure you are using the right constructor when you construct an instance of CacheEvictionPolicy . Are you sure that the cache keys you are going to depend on, actually exist?

In newer version of CMS, there would be a warning in log if the cache is invalidated immediately, however, it could be missed, unless you are actively looking for it.

Note that this behavior is the same with ISynchronizedObjectInstanceCache as it extends IObjectInstanceCache.

Lelit Elizabeth v3 – 2 months impression (super short review)

First of all, if you are looking to buy Lelit Elizabeth, check out the detailed review from David Corbey. It is probably the best review on the machine. He also wrote very good information regarding setting, and maintaining the machine, so check it out.

Out of the box, the Elizabeth looks much better than in photos. I must admit, the photos do not do its justice – it looks rather dull in those. While it is certainly not the best looking espresso machine (to my taste at least, I think the one with E61 group looks more compelling), it definitely looks good.

Next to the machine it is going to replace

Coming from the popular Sage (Breville) Barista Pro, there are a few things that impress/surprise me (of course this is an unfair comparison. When you spend 3x more money on a new machine + a grinder, you would definitely not want to get “marginally better”):

  • The actual stainless steel construction is very, very nice. It feels much more solid than the fake stainless steel look from the Barista Pro. The machine is well made, probably not the best built machine around, but you know it will last you a long time.
  • It takes really long time to heat up, about 20 minutes. Compared to Barista Pro, it was a bummer at first, because you could pull your first shot almost instantaneously with the Pro as the machine is ready after 3s. But that is a lie. For the Barista Pro, the machine allows you to pull shots, but with the cost of temperature stability. I learned the hard way that most of the shots with Barista Pro is severely under temperature, resulting in extreme sour taste, and it was very hard to adjust – you have like 5 levels for temperature, ranging from 90*C to 98*C, so about 2*C each. If the machine can reach that desired temperature or not, is another question. I only realize that once I switched to Elizabeth. The portafilter is actually hot (and very uncomfortable to touch to steel part). On Barista Pro however, it is only lukewarm, even if I pulled a few empty shots before hand. Furthermore, with Elizabeth, I can set the brewing water to whatever temp I like (or half the degree if I switch to F instead of C), so I can comfortably brew light, medium or dark roast the way they are meant to be brewed.

The flow is now so, so much easier and smoother and I had with the Sage Barista Pro.

Buying accessories is also now easier and cheaper – I could easily find branded, quality accessories for reasonable prices. They might be still expensive, but I feel the price is justified for the the quality.

But no machine is perfect, so is Elizabeth, there are a few downsides

The biggest one, to me at least, is that the water tank is pretty hard to refill, as I put my machine under the kitchen cabinet, so I have a few little space left. I need to either move it out, or use a gooseneck kettle to refill. I went with the latter approach and it works quite well.

Another bummer is that there is lack of a real tamper included in the package. Lacking of the milk jug is somewhat acceptable, but tamper? I bought a nice one from Motta (wish I chose the 58.55mm version instead), but I wish they included one by default. Of course, this one is an easy one to fix.

In the end, I’m happy with my Elizabeth, and I feel happy and excited to use it every day. My only regret is that I didn’t step up to Bianca – heard great things about it. But well, it exceeded my budget at that point by a large margin, I’ll have to wait.

Meanwhile, Lelit Elizabeth will serve me well for a long, long time.

Debugging a memory dump for .net 5

You would need to install Windbg Preview from Windows Store

Get it from Get WinDbg Preview – Microsoft Store . If you use the ordinary Windbg that comes wint Windows SDK, this is what you get from trying to open it

WinDbg:10.0.19041.685 AMD64

Could not find the C:\Users\vimvq\Downloads\core_20211102_090430.dmp Dump File, Win32 error 0n87

The parameter is incorrect.

You also need to install .NET 5 version of sos.

dotnet tool install --global dotnet-sos

and once you used Windbg Preview to open the memory dump, run this command to load it:

.load C:\Users\<your user name>\.dotnet\sos\sos.dll

And now you can start debugging as usual

Coffee roasters in Sweden – a review

If you are serious about Espresso quality, you know you must buy from a specialty roaster – not from the super market. You will pay more premium price, at least 300kr per kg, and easily up to 500kr per kilo or more, compare to around 100-150kr per kg from super market. In return, you get:

  • The obvious better coffee quality. Most if not all decent roasters only roast specialty coffee, meaning they not only taste good, they have minimal defects, especially small rocks. A bad bean will only ruins one cup, at most. But a pebble can destroy your precious coffee grinder. Your grinder will thank you for the uniformity of specialty coffee beans you buy from roasters.
  • Much better freshness. Most, if not all coffees from super market only have “expired date”, not “roast date”. You can probably guess the roast date by subtracting expired date by 24 months and the most fresh one I could find, was two months old. At this point the coffee already started degrading in quality. In contrast, when I buy from roaster, it is always less than 1 week from roast date, which is clearly printed on the bag. As a rule of thumb, you should finish your coffee in less than 8 weeks from roast date.
  • Much better roasted. Most coffee from super market is roasted with super hot air (800*c) in very short amount of time. This allows the roaster to roast ton after ton, but with the cost of coffee flavor. Specialty coffees are often roasted in much smaller batches, an a longer time, for the flavor can develop properly.
  • Traceability. You only know coffee from super market by their country origin, and that’s it. But for specialty coffee, you will know the region which produced the coffee, and in many cases, even the farm that produced it.
  • Last but not least, support for local businesses. Specialty roasters are small businesses in your city, or even area. Buying from them means you support your local economy. Many roasters also have direct trade with the coffee farms, which means you give more directly support those famers. Most farms that grow specialty coffee also follow practices regarding sustainability (and due to high price of specialty coffee, they can sustain their business with considerable smaller farms). If you care about sustainability and people likelihood, buying from roasters is a better way to support that.

What to look for from Coffee roasters

A coffee bean bag with one way valve is a must (In case you didn’t know, newly roasted coffee bean will release CO2, and that valve is important to let the CO2 out – but not let the air in) . Best if it is resealable. otherwise you would have to move the bean to an airtight container to keep them fresh for longer. Also, buy coffee beans if you can. Ground coffee starts losing their aroma and flavor just 30 minutes after grinding. Airtight container can only slow that down a little bit.

All of the roasters below have good coffees – the beans have consistent color, size and shape. My machines, techniques and taste are not at the level I can distinct each flavor, so I will focus on the services instead.

StockholmRoast (Stockholm Roast – The House of Roasting)

They offers good prices, but no subscription. They ship through DHL to the service point, with free shipping, which is nice, but not the best (compared to other options below)

The bags are well packaged, so you get proper protection of your precious coffee beans. But they also glue a plastic bag on the package (for the shipping information and the receipt), which is quite tiresome to remove for proper recycling.

They also offer better price for 1kg bag, compared to 4 bags of 250. While this is somewhat understandable from a commercial perspective, it means it’s harder to keep your coffee fresh, if you want to save some money. They probably should offer 500gr bag.

Another minus, I don’t recall their bag is resealable. Also the ink on the bag could easily get into you hands, especially if they are a little wet. They are, however, not very easy to wash off.

None of those things are critical, but they would be very nice to be fixed!

LYKKE KAFFEGÅRDAR Nyrostat kaffe | Direkt frĂ„n gĂ„rden hem till dig | Lykke KaffegĂ„rdar (lykkegardar.se)

Lykke offers subscriptions, with 10% discount, which is good. Importantly, they ship directly to your mailbox. Order, and in one day or two, you find your favorite coffee bags in your mailbox. Convenient, huh?

You can easily manage subscriptions, including changing it, skip one delivery, or cancel it, which is a huge plus.

Their bag design is beautiful, and I absolutely like it. To make things better, they even included 2 bags of tea in my first shipment – a very good way to advertise.

Their espresso range, however, is quite limited. There is only one Bam! that is dedicated for espresso. Wish they offer more choices.

UPDATE: I bought 2 bags of BAM! from them due to their Black Friday sales, and were sent ones which were roasted on October 28th, which means more than 1 month when they arrived. I was disappointed, and sent them a letter. They apologized and offered a 30% coupon for my next order. While receiving one month old coffee bags is no fun, I think the way they handled it was nice. I took the offer.

Kafferosterietkoppar UpptÀck vÄrt nyrostade kaffe | Kafferosteriet Koppar

They offer subscriptions as well, and with 20 SEK discount per bag, which is very nice. However, to change the subscription, you need to email them directly. It’s OK-ish, but I would definitely prefer the Lykke approach.

They also ship directly to mailbox, and their shipping was very fast. I ordered on Wednesday, and two of the bags appeared in my mail box on Thursday. I don’t know if they forgot, or intentionally did not send a notification email, but that was a nice surprise.

One incident with my first purchase: Out of two bags is almost empty (there were like, 30 coffee beans inside them). I mailed them to let they know, and they were happy to ship a replacement to me. In the end, everything is resolved quick and easy, but I’d hope they did have a bit more of quality control for their coffee bag.

Standout coffee https://www.standoutcoffee.com/

Their subscription is 25e (yes, euro, equivalent to about 260kr) for 100gr of coffee, or 2600kr per kilo. The reason for such high price is because it’s “Gesha village”, the most expensive coffee in the world, and they offer worldwide free shipping.

2600kr per kilo is unfortunately way too high for what I can pay for coffee, and with 100gr you might get 1-2 cup of good espresso out of it (considering you have to dial in), so thanks, but no thanks.

Drop Coffee

Apparently they are the most popular in Sweden, so I should try them out soon. They are transparent about their FOB price, which is nice. I was hesitant about their Google reviews (“only” 4.3 on 5.0, so quite lower than other roasters in this list), but it turned out it has to do with their coffee shops (which should be affected by many other things) than their actual roasting business.

Pouring coffee roastery (hallakafferosteri.com)

This is my favorite now. They offer coffees at very good price – especially if you buy in batch (5 or 10 bags of 500gr), and they usually have 10 or 15% off coupon. I buy with a few friends, and we split the bag – and I end up with around 250kr/kg (2x500gr bag), and sometimes even only 220kr. They do ship free to service point for order more than 499kr.

I can’t notice a difference between their coffee and other roasters, so I’m happy with that setup, for now.