Replaced the local cache with a new more robust one.
diff --git a/cache.go b/cache.go
index f0af1d0..2223740 100644
--- a/cache.go
+++ b/cache.go
@@ -49,12 +49,21 @@
 	limit    int                      // Maximum size allowed
 }
 
-func NewCache(limit int) *cache {
+const defaultCacheLimit = 16 << 20 // 16 MiB
+
+func newCache(limit int) *cache {
 	return &cache{elements: map[string]*list.Element{}, limit: limit}
 }
 
+func (c *cache) setLimit(limit int) {
+	c.lock.Lock()
+	c.limit = limit
+	c.meetLimitUnderLock()
+	c.lock.Unlock()
+}
+
 // meetLimit must be called under cache.lock
-func (c *cache) meetLimit() {
+func (c *cache) meetLimitUnderLock() {
 	for c.size > c.limit {
 		e := c.accessed.Back()
 		if e == nil {
@@ -89,7 +98,7 @@
 func (c *cache) Set(item *cacheItem) {
 	c.lock.Lock()
 	c.setUnderLock(item)
-	c.meetLimit()
+	c.meetLimitUnderLock()
 	c.lock.Unlock()
 }
 
@@ -99,7 +108,7 @@
 	for _, item := range items {
 		c.setUnderLock(item)
 	}
-	c.meetLimit()
+	c.meetLimitUnderLock()
 	c.lock.Unlock()
 }
 
diff --git a/cache_test.go b/cache_test.go
index fdf52ec..1024281 100644
--- a/cache_test.go
+++ b/cache_test.go
@@ -23,54 +23,6 @@
 	"unsafe"
 )
 
-func TestCacheKeyLeak(t *testing.T) {
-	ak, bk := string([]byte{'f', 'o', 'o'}), string([]byte{'f', 'o', 'o'})
-	av, bv := []byte{1, 2, 3}, []byte{4, 5, 6}
-
-	c := NewCache(16777216) // 16 MiB
-
-	// Set the original value
-	c.Set(&cacheItem{key: ak, value: av})
-	if v := c.Get(ak); !bytes.Equal(v, av) {
-		t.Fatalf("Invalid bytes! %v", v)
-	}
-
-	// Rewrite it with a different value, and also a different key but same key contents
-	c.Set(&cacheItem{key: bk, value: bv})
-	if v := c.Get(bk); !bytes.Equal(v, bv) {
-		t.Fatalf("Invalid bytes! %v", v)
-	}
-
-	// Modify the new key contents without changing the pointer
-	*(*byte)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&bk)))) = 'g'
-	if bk != "goo" {
-		t.Fatalf("Expected key to be 'goo' but it's %v", bk)
-	}
-
-	// Make sure that we can no longer retrieve the value with the new key,
-	// as that will only be possible via pointer equality, which means that
-	// the cache is still holding on to the new key, which doubles key storage
-	if v := c.Get(bk); v != nil {
-		t.Fatalf("Cache is leaking memory by keeping around the new key pointer! %v", v)
-	}
-	// Also make sure that we can retrieve the correct new value
-	// by using an unrelated key pointer that just matches the key contents
-	if v := c.Get("foo"); !bytes.Equal(v, bv) {
-		t.Fatalf("Invalid bytes! %v", v)
-	}
-
-	// Inspect the internals of the cache too, which contains only a single entry with ak as key
-	keyAddr := *(*uintptr)(unsafe.Pointer(&ak))
-	for key, elem := range c.elements {
-		if ka := *(*uintptr)(unsafe.Pointer(&key)); ka != keyAddr {
-			t.Fatalf("map key has wrong pointer! %x vs %x", ka, keyAddr)
-		}
-		if ka := *(*uintptr)(unsafe.Pointer(&(elem.Value.(*cacheItem)).key)); ka != keyAddr {
-			t.Fatalf("element key has wrong pointer! %x vs %x", ka, keyAddr)
-		}
-	}
-}
-
 func TestCacheBasics(t *testing.T) {
 	items := []*cacheItem{}
 	items = append(items, &cacheItem{key: "foo", value: []byte{1, 2, 3}})
@@ -81,7 +33,7 @@
 		keys = append(keys, item.key)
 	}
 
-	c := NewCache(16777216) // 16 MiB
+	c := newCache(defaultCacheLimit)
 
 	for i := range items {
 		if v := c.Get(items[i].key); v != nil {
@@ -137,3 +89,92 @@
 		t.Fatalf("Invalid bytes for value change! Got %x", v)
 	}
 }
+
+func TestCacheKeyLeak(t *testing.T) {
+	ak, bk := string([]byte{'f', 'o', 'o'}), string([]byte{'f', 'o', 'o'})
+	av, bv := []byte{1, 2, 3}, []byte{4, 5, 6}
+
+	c := newCache(defaultCacheLimit)
+
+	// Set the original value
+	c.Set(&cacheItem{key: ak, value: av})
+	if v := c.Get(ak); !bytes.Equal(v, av) {
+		t.Fatalf("Invalid bytes! %v", v)
+	}
+
+	// Rewrite it with a different value, and also a different key but same key contents
+	c.Set(&cacheItem{key: bk, value: bv})
+	if v := c.Get(bk); !bytes.Equal(v, bv) {
+		t.Fatalf("Invalid bytes! %v", v)
+	}
+
+	// Modify the new key contents without changing the pointer
+	*(*byte)(unsafe.Pointer(*(*uintptr)(unsafe.Pointer(&bk)))) = 'g'
+	if bk != "goo" {
+		t.Fatalf("Expected key to be 'goo' but it's %v", bk)
+	}
+
+	// Make sure that we can no longer retrieve the value with the new key,
+	// as that will only be possible via pointer equality, which means that
+	// the cache is still holding on to the new key, which doubles key storage
+	if v := c.Get(bk); v != nil {
+		t.Fatalf("Cache is leaking memory by keeping around the new key pointer! %v", v)
+	}
+	// Also make sure that we can retrieve the correct new value
+	// by using an unrelated key pointer that just matches the key contents
+	if v := c.Get("foo"); !bytes.Equal(v, bv) {
+		t.Fatalf("Invalid bytes! %v", v)
+	}
+
+	// Inspect the internals of the cache too, which contains only a single entry with ak as key
+	keyAddr := *(*uintptr)(unsafe.Pointer(&ak))
+	for key, elem := range c.elements {
+		if ka := *(*uintptr)(unsafe.Pointer(&key)); ka != keyAddr {
+			t.Fatalf("map key has wrong pointer! %x vs %x", ka, keyAddr)
+		}
+		if ka := *(*uintptr)(unsafe.Pointer(&(elem.Value.(*cacheItem)).key)); ka != keyAddr {
+			t.Fatalf("element key has wrong pointer! %x vs %x", ka, keyAddr)
+		}
+	}
+}
+
+func TestCacheLimit(t *testing.T) {
+	c := newCache(defaultCacheLimit)
+
+	items := []*cacheItem{}
+	items = append(items, &cacheItem{key: "foo", value: []byte{1, 2, 3}})
+	items = append(items, &cacheItem{key: "bar", value: []byte{4, 5, 6}})
+
+	keys := make([]string, 0, len(items))
+	for _, item := range items {
+		keys = append(keys, item.key)
+	}
+
+	if vs := c.GetMulti(keys); !reflect.DeepEqual(vs, [][]byte{nil, nil}) {
+		t.Fatalf("Expected nils but got %+v", vs)
+	}
+
+	c.SetMulti(items)
+
+	if vs := c.GetMulti(keys); !reflect.DeepEqual(vs, [][]byte{items[0].value, items[1].value}) {
+		t.Fatalf("Invalid bytes for items! %+v", vs)
+	}
+
+	c.setLimit(0)
+
+	if vs := c.GetMulti(keys); !reflect.DeepEqual(vs, [][]byte{nil, nil}) {
+		t.Fatalf("Expected nils but got %+v", vs)
+	}
+
+	if c.size != 0 {
+		t.Fatalf("Expected size to be zero, but got %v", c.size)
+	}
+
+	c.setLimit(cachedValueOverhead + len(items[1].key) + cap(items[1].value))
+
+	c.SetMulti(items)
+
+	if vs := c.GetMulti(keys); !reflect.DeepEqual(vs, [][]byte{nil, items[1].value}) {
+		t.Fatalf("Invalid bytes for items! %+v", vs)
+	}
+}
diff --git a/doc.go b/doc.go
index 6e30832..603345e 100644
--- a/doc.go
+++ b/doc.go
@@ -135,7 +135,6 @@
 
 See: http://talks.golang.org/2013/highperf.slide#23
 
-
 PropertyLoadSaver support
 
 Structs that implement the PropertyLoadSaver interface are guaranteed to call
@@ -143,73 +142,5 @@
 Similarly the Load() method is guaranteed to be called once and only once per
 Get/GetMulti/GetAll/Next call and never elsewhere.
 
-Keep in mind that the goon local cache is just a pointer to the previous result.
-This means that when you use Get to fetch something into a PropertyLoadSaver
-implementing struct, that struct's pointer is saved. Subsequent calls to Get
-will just return that pointer. This means that although Load() was called once
-during the initial Get, the subsequent calls to Get won't call Load() again.
-Generally this shouldn't be an issue, but e.g. if you generate some random
-data in the Load() call then sequential calls to Get that share the same goon
-local cache will always return the random data of the first call.
-
-A gotcha can be encountered with the local cache where Load() is never called.
-Specifically if you first call Get to load some entity into a struct S which
-does *not* implement PropertyLoadSaver. The local cache will now have a pointer
-to this struct S. When you now proceed to call Get again on the same id but into
-a struct PLS which implements PropertyLoadSaver but is also convertible from S.
-Then your struct PLS will be applied the value of S, however Load() will never
-be called, because it wasn't the first time and it's never called when loading
-from the local cache. This is a very specific edge case that won't affect 99.9%
-of developers using goon. This issue does not exist with memcache/datastore,
-so either flushing the local cache or doing the S->PLS migration in different
-requests will solve the issue.
-
-
-Local memory cache
-
-Entities retrieved via Get/GetMulti/GetAll/Next are stored in the local cache.
-The local cache is an in-memory per-goon-instance cache that keeps a pointer
-to the returned result. When attempting to retrieve the same entity again you
-will be returned a shallow copy of the pointer's value.
-
-Imagine a struct type Foo:
-
-	type Foo struct {
-		Id int64 `datastore:"-" goon:"id"`
-		X int
-		Y []byte
-	}
-
-Then behavior is as follows:
-
-	fA := &Foo{Id: 1}    // fA.X == 0 | fA.Y == []
-	g.Get(fA)            // fA.X == 1 | fA.Y == [1, 2]  Initial datastore values
-	fA.X = 2             // fA.X == 2 | fA.Y == [1, 2]  We change the int
-	fA.Y = []byte{3, 4}  // fA.X == 2 | fA.Y == [3, 4]  ..and create a new slice
-	fB := &Foo{Id: 1}    // fB.X == 0 | fB.Y == []
-	g.Get(fB)            // fB.X == 2 | fB.Y == [3, 4]  fB is now a copy of fA
-	fB.X = 3             // fB.X == 3 | fB.Y == [3, 4]  Changing fB.X is local
-	fB.Y[0] = 5          // fB.X == 3 | fB.Y == [5, 4]  ..but changing deep data
-	                     // fA.X == 2 | fA.Y == [5, 4]  will also appear in fA.Y
-	fA.Y = []byte{6, 7}  // fA.X == 2 | fA.Y == [6, 7]  However creating a new
-	                     // fB.X == 3 | fB.Y == [5, 4]  slice will not propagate
-
-The reason the cache works with pointers is because App Engine is very memory
-limited and storing just pointers takes up very little memory. However this
-means that any changes you perform on an entity that was cached will also be
-reflected in any future gets for that same entity with the same goon instance.
-What's more, the copy will be shallow. So any value-types likes primitive ints
-can be then changed and will only be changed for that specific struct instance.
-However deep modifications of slices will propagate to all copies of the struct.
-
-Thus for the most care-free life you should treat fetched entities as immutable,
-which means you never modify their data. That way the whole pointer/copy
-strategy won't have any effect on your app logic. For modifications, always do
-them inside a transaction because local cache is disabled there.
-
-This means that the local cache is **not** thread-safe for modifications. Make
-sure you either don't modify entities fetched outside of transactions or that
-you never fetch the same id from different goroutines using one goon instance.
-
 */
 package goon
diff --git a/goon.go b/goon.go
index ff29a79..ac6318e 100644
--- a/goon.go
+++ b/goon.go
@@ -59,12 +59,11 @@
 // Goon holds the app engine context and the request memory cache.
 type Goon struct {
 	Context       context.Context
-	cache         map[string]interface{}
-	cacheLock     sync.RWMutex // protect the cache from concurrent goroutines to speed up RPC access
+	cache         *cache
 	inTransaction bool
 	txnCacheLock  sync.Mutex // protects toDelete / toDeleteMC
-	toDelete      map[string]bool
-	toDeleteMC    map[string]bool
+	toDelete      map[string]struct{}
+	toDeleteMC    map[string]struct{}
 	// KindNameResolver is used to determine what Kind to give an Entity.
 	// Defaults to DefaultKindName
 	KindNameResolver KindNameResolver
@@ -88,7 +87,7 @@
 func FromContext(c context.Context) *Goon {
 	return &Goon{
 		Context:          c,
-		cache:            make(map[string]interface{}),
+		cache:            newCache(defaultCacheLimit),
 		KindNameResolver: DefaultKindName,
 	}
 }
@@ -170,8 +169,8 @@
 		ng = &Goon{
 			Context:          tc,
 			inTransaction:    true,
-			toDelete:         make(map[string]bool),
-			toDeleteMC:       make(map[string]bool),
+			toDelete:         make(map[string]struct{}),
+			toDeleteMC:       make(map[string]struct{}),
 			KindNameResolver: g.KindNameResolver,
 		}
 		return f(ng)
@@ -187,10 +186,8 @@
 			}
 			memcache.DeleteMulti(g.Context, memkeys)
 		}
-		g.cacheLock.Lock()
-		defer g.cacheLock.Unlock()
 		for k := range ng.toDelete {
-			delete(g.cache, k)
+			g.cache.Delete(k)
 		}
 	} else {
 		g.error(err)
@@ -203,6 +200,10 @@
 // is an incomplete key, the returned key will be a unique key generated by
 // the datastore.
 func (g *Goon) Put(src interface{}) (*datastore.Key, error) {
+	v := reflect.ValueOf(src)
+	if v.Kind() != reflect.Ptr {
+		return nil, fmt.Errorf("goon: expected pointer to a struct, got %#v", src)
+	}
 	ks, err := g.PutMulti([]interface{}{src})
 	if err != nil {
 		if me, ok := err.(appengine.MultiError); ok {
@@ -264,28 +265,30 @@
 					g.setStructKey(vi, rkeys[i])
 					keys[lo+i] = rkeys[i]
 				}
-				if g.inTransaction {
-					mk := MemcacheKey(rkeys[i])
-					g.txnCacheLock.Lock()
-					g.toDeleteMC[mk] = true
-					g.txnCacheLock.Unlock()
-				}
 			}
 		}(i)
 	}
 	wg.Wait()
 
-	// Memcache needs to be updated after the datastore to prevent a common race condition,
+	// Caches need to be updated after the datastore to prevent a common race condition,
 	// where a concurrent request will fetch the not-yet-updated data from the datastore
-	// and populate memcache with it.
-	if !g.inTransaction {
-		var memkeys []string
-		for _, key := range keys {
-			if !key.Incomplete() {
-				memkeys = append(memkeys, MemcacheKey(key))
-			}
+	// and populate the caches with it.
+	cachekeys := make([]string, 0, len(keys))
+	for _, key := range keys {
+		if !key.Incomplete() {
+			cachekeys = append(cachekeys, MemcacheKey(key))
 		}
-		memcache.DeleteMulti(g.Context, memkeys)
+	}
+	if g.inTransaction {
+		g.txnCacheLock.Lock()
+		for _, ck := range cachekeys {
+			g.toDelete[ck] = struct{}{}
+			g.toDeleteMC[ck] = struct{}{}
+		}
+		g.txnCacheLock.Unlock()
+	} else {
+		g.cache.DeleteMulti(cachekeys)
+		memcache.DeleteMulti(g.Context, cachekeys)
 	}
 
 	if any {
@@ -294,49 +297,20 @@
 	return keys, nil
 }
 
-func (g *Goon) putMemoryMulti(src interface{}, exists []byte) {
-	v := reflect.Indirect(reflect.ValueOf(src))
-	for i := 0; i < v.Len(); i++ {
-		if exists[i] == 0 {
-			continue
-		}
-		g.putMemory(v.Index(i).Interface())
-	}
-}
-
-func (g *Goon) putMemory(src interface{}) {
-	key, _, _ := g.getStructKey(src)
-	g.cacheLock.Lock()
-	defer g.cacheLock.Unlock()
-	g.cache[MemcacheKey(key)] = src
-}
-
 // FlushLocalCache clears the local memory cache.
 func (g *Goon) FlushLocalCache() {
-	g.cacheLock.Lock()
-	g.cache = make(map[string]interface{})
-	g.cacheLock.Unlock()
+	g.cache.Flush()
 }
 
-type cacheEntry struct {
-	key   *datastore.Key
-	props datastore.PropertyList
-}
-
-func (g *Goon) putMemcache(entries []cacheEntry) error {
-	items := make([]*memcache.Item, len(entries))
+func (g *Goon) putMemcache(citems []*cacheItem) error {
+	items := make([]*memcache.Item, len(citems))
 	payloadSize := 0
-	for i, entry := range entries {
-		data, err := serializeProperties(entry.props, entry.props != nil)
-		if err != nil {
-			g.error(err)
-			return err
-		}
+	for i, citem := range citems {
 		// payloadSize will overflow if we push 2+ gigs on a 32bit machine
-		payloadSize += len(data)
+		payloadSize += len(citem.value)
 		items[i] = &memcache.Item{
-			Key:   MemcacheKey(entry.key),
-			Value: data,
+			Key:   citem.key,
+			Value: citem.value,
 		}
 	}
 	memcacheTimeout := MemcachePutTimeoutSmall
@@ -357,12 +331,12 @@
 // If there is no such entity for the key, Get returns
 // datastore.ErrNoSuchEntity.
 func (g *Goon) Get(dst interface{}) error {
-	set := reflect.ValueOf(dst)
-	if set.Kind() != reflect.Ptr {
+	v := reflect.ValueOf(dst)
+	if v.Kind() != reflect.Ptr {
 		return fmt.Errorf("goon: expected pointer to a struct, got %#v", dst)
 	}
-	if !set.CanSet() {
-		set = set.Elem()
+	if !v.CanSet() {
+		v = v.Elem()
 	}
 	dsts := []interface{}{dst}
 	if err := g.GetMulti(dsts); err != nil {
@@ -373,7 +347,7 @@
 		// Not multi, normal error
 		return err
 	}
-	set.Set(reflect.Indirect(reflect.ValueOf(dsts[0])))
+	v.Set(reflect.Indirect(reflect.ValueOf(dsts[0])))
 	return nil
 }
 
@@ -419,47 +393,57 @@
 
 	var dskeys []*datastore.Key
 	var dsdst []interface{}
-	var dixs []int
+	var dixs []int // dskeys[5] === keys[dixs[5]]
 
-	var memkeys []string
-	var mixs []int
+	var mckeys []string
+	var mixs []int // mckeys[3] =~= keys[mixs[3]]
 
-	g.cacheLock.RLock()
+	lckeys := make([]string, 0, len(keys))
+	for _, key := range keys {
+		// NB! Current implementation has optimizations in place
+		// that expect memcache & local cache keys to match.
+		lckeys = append(lckeys, MemcacheKey(key))
+	}
+
+	lcvalues := g.cache.GetMulti(lckeys)
+
 	for i, key := range keys {
-		m := MemcacheKey(key)
 		vi := v.Index(i)
-
 		if vi.Kind() == reflect.Struct {
 			vi = vi.Addr()
 		}
+		d := vi.Interface()
 
-		if s, present := g.cache[m]; present {
-			if vi.Kind() == reflect.Interface {
-				vi = vi.Elem()
+		if data := lcvalues[i]; data != nil {
+			// Attempt to deserialize the cached value into the struct
+			err := deserializeStruct(d, data)
+			if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) {
+				if err == datastore.ErrNoSuchEntity || errFieldMismatch(err) {
+					anyErr = true // this flag tells GetMulti to return multiErr later
+					multiErr[i] = err
+				} else {
+					g.error(err)
+					return err
+				}
 			}
-			vi = reflect.Indirect(vi)
-			cached := reflect.Indirect(reflect.ValueOf(s))
-			viType, cType := vi.Type(), cached.Type()
-			if viType != cType && cType.ConvertibleTo(viType) {
-				cached = cached.Convert(viType)
-			}
-			vi.Set(cached)
 		} else {
-			memkeys = append(memkeys, m)
+			mckeys = append(mckeys, lckeys[i])
 			mixs = append(mixs, i)
 			dskeys = append(dskeys, key)
-			dsdst = append(dsdst, vi.Interface())
+			dsdst = append(dsdst, d)
 			dixs = append(dixs, i)
 		}
 	}
-	g.cacheLock.RUnlock()
 
-	if len(memkeys) == 0 {
+	if len(mckeys) == 0 {
+		if anyErr {
+			return realError(multiErr)
+		}
 		return nil
 	}
 
 	tc, cf := context.WithTimeout(g.Context, MemcacheGetTimeout)
-	memvalues, err := memcache.GetMulti(tc, memkeys)
+	memvalues, err := memcache.GetMulti(tc, mckeys)
 	cf()
 	if appengine.IsTimeoutError(err) {
 		g.timeoutError(err)
@@ -474,21 +458,24 @@
 		// we only want to check the returned map if there weren't any errors
 		// unlike the datastore, memcache will return a smaller map with no error if some of the keys were missed
 
-		for i, m := range memkeys {
+		for i, m := range mckeys {
 			d := v.Index(mixs[i]).Interface()
 			if v.Index(mixs[i]).Kind() == reflect.Struct {
 				d = v.Index(mixs[i]).Addr().Interface()
 			}
 			if s, present := memvalues[m]; present {
+				// Mirror any memcache entries in local cache
+				g.cache.Set(&cacheItem{key: m, value: s.Value})
+				// Attempt to deserialize the cached value into the struct
 				err := deserializeStruct(d, s.Value)
-				if err == nil || (IgnoreFieldMismatch && errFieldMismatch(err)) {
-					g.putMemory(d)
-				} else if err == datastore.ErrNoSuchEntity || errFieldMismatch(err) {
-					anyErr = true // this flag tells GetMulti to return multiErr later
-					multiErr[mixs[i]] = err
-				} else {
-					g.error(err)
-					return err
+				if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) {
+					if err == datastore.ErrNoSuchEntity || errFieldMismatch(err) {
+						anyErr = true // this flag tells GetMulti to return multiErr later
+						multiErr[mixs[i]] = err
+					} else {
+						g.error(err)
+						return err
+					}
 				}
 			} else {
 				dskeys = append(dskeys, keys[mixs[i]])
@@ -516,15 +503,24 @@
 			if hi > len(dskeys) {
 				hi = len(dskeys)
 			}
-			toCache := make([]cacheEntry, 0, hi-lo)
+			toCache := make([]*cacheItem, 0, hi-lo)
 			propLists := make([]datastore.PropertyList, hi-lo)
-			handleProp := func(i, idx int) {
-				err := deserializeProperties(dsdst[lo+i], propLists[i])
-				if err == nil || (IgnoreFieldMismatch && errFieldMismatch(err)) {
-					toCache = append(toCache, cacheEntry{key: dskeys[lo+i], props: propLists[i]})
-					g.putMemory(dsdst[lo+i])
-				} else {
+			handleProp := func(i, idx int, exists bool) {
+				// Serialize the properties
+				data, err := serializeProperties(propLists[i], exists)
+				if err != nil {
+					g.error(err)
 					multiErr[idx] = err
+					return
+				}
+				// Prepare the properties for caching
+				toCache = append(toCache, &cacheItem{key: lckeys[idx], value: data})
+				// Deserialize the properties into a struct
+				if exists {
+					err = deserializeProperties(dsdst[lo+i], propLists[i])
+					if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) {
+						multiErr[idx] = err
+					}
 				}
 			}
 			gmerr := datastore.GetMulti(g.Context, dskeys[lo:hi], propLists)
@@ -542,20 +538,21 @@
 				}
 				for i, idx := range dixs[lo:hi] {
 					if merr[i] == nil {
-						handleProp(i, idx)
+						handleProp(i, idx, true)
 					} else {
 						if merr[i] == datastore.ErrNoSuchEntity {
-							toCache = append(toCache, cacheEntry{key: dskeys[lo+i], props: nil})
+							handleProp(i, idx, false)
 						}
 						multiErr[idx] = merr[i]
 					}
 				}
 			} else {
 				for i, idx := range dixs[lo:hi] {
-					handleProp(i, idx)
+					handleProp(i, idx, true)
 				}
 			}
 			if len(toCache) > 0 {
+				// Populate memcache
 				if err := g.putMemcache(toCache); err != nil {
 					// since putMemcache() gives no guarantee it will actually store the data in memcache
 					// we log and swallow this error
@@ -565,7 +562,8 @@
 						g.error(err)
 					}
 				}
-
+				// Populate local cache
+				g.cache.SetMulti(toCache)
 			}
 		}(i)
 	}
@@ -588,72 +586,12 @@
 
 const deleteMultiLimit = 500
 
-// Returns a single error if each error in MultiError is the same
-// otherwise, returns multiError or nil (if multiError is empty)
-func realError(multiError appengine.MultiError) error {
-	if len(multiError) == 0 {
-		return nil
-	}
-	init := multiError[0]
-	// some errors are *always* returned in MultiError form from the datastore
-	if _, ok := init.(*datastore.ErrFieldMismatch); ok { // returned in GetMulti
-		return multiError
-	}
-	if init == datastore.ErrInvalidEntityType || // returned in GetMulti
-		init == datastore.ErrNoSuchEntity { // returned in GetMulti
-		return multiError
-	}
-	// check if all errors are the same
-	for i := 1; i < len(multiError); i++ {
-		// since type error could hold structs, pointers, etc,
-		// the only way to compare non-nil errors is by their string output
-		if init == nil || multiError[i] == nil {
-			if init != multiError[i] {
-				return multiError
-			}
-		} else if init.Error() != multiError[i].Error() {
-			return multiError
-		}
-	}
-	// datastore.ErrInvalidKey is returned as a single error in PutMulti
-	return init
-}
-
 // DeleteMulti is a batch version of Delete.
 func (g *Goon) DeleteMulti(keys []*datastore.Key) error {
 	if len(keys) == 0 {
 		return nil
 		// not an error, and it was "successful", so return nil
 	}
-	memkeys := make([]string, len(keys))
-
-	g.cacheLock.Lock()
-	for i, k := range keys {
-		mk := MemcacheKey(k)
-		memkeys[i] = mk
-
-		if g.inTransaction {
-			g.txnCacheLock.Lock()
-			g.toDelete[mk] = true
-			g.txnCacheLock.Unlock()
-		} else {
-			delete(g.cache, mk)
-		}
-	}
-	g.cacheLock.Unlock()
-
-	// Memcache needs to be updated after the datastore to prevent a common race condition,
-	// where a concurrent request will fetch the not-yet-updated data from the datastore
-	// and populate memcache with it.
-	if g.inTransaction {
-		g.txnCacheLock.Lock()
-		for _, mk := range memkeys {
-			g.toDeleteMC[mk] = true
-		}
-		g.txnCacheLock.Unlock()
-	} else {
-		defer memcache.DeleteMulti(g.Context, memkeys)
-	}
 
 	mu := new(sync.Mutex)
 	multiErr, any := make(appengine.MultiError, len(keys)), false
@@ -686,6 +624,26 @@
 		}(i)
 	}
 	wg.Wait()
+
+	// Caches need to be updated after the datastore to prevent a common race condition,
+	// where a concurrent request will fetch the not-yet-updated data from the datastore
+	// and populate the caches with it.
+	cachekeys := make([]string, 0, len(keys))
+	for _, key := range keys {
+		cachekeys = append(cachekeys, MemcacheKey(key))
+	}
+	if g.inTransaction {
+		g.txnCacheLock.Lock()
+		for _, ck := range cachekeys {
+			g.toDelete[ck] = struct{}{}
+			g.toDeleteMC[ck] = struct{}{}
+		}
+		g.txnCacheLock.Unlock()
+	} else {
+		g.cache.DeleteMulti(cachekeys)
+		memcache.DeleteMulti(g.Context, cachekeys)
+	}
+
 	if any {
 		return realError(multiErr)
 	}
@@ -705,3 +663,34 @@
 	_, ok := err.(*datastore.ErrFieldMismatch)
 	return ok
 }
+
+// Returns a single error if each error in MultiError is the same
+// otherwise, returns multiError or nil (if multiError is empty)
+func realError(multiError appengine.MultiError) error {
+	if len(multiError) == 0 {
+		return nil
+	}
+	init := multiError[0]
+	// some errors are *always* returned in MultiError form from the datastore
+	if _, ok := init.(*datastore.ErrFieldMismatch); ok { // returned in GetMulti
+		return multiError
+	}
+	if init == datastore.ErrInvalidEntityType || // returned in GetMulti
+		init == datastore.ErrNoSuchEntity { // returned in GetMulti
+		return multiError
+	}
+	// check if all errors are the same
+	for i := 1; i < len(multiError); i++ {
+		// since type error could hold structs, pointers, etc,
+		// the only way to compare non-nil errors is by their string output
+		if init == nil || multiError[i] == nil {
+			if init != multiError[i] {
+				return multiError
+			}
+		} else if init.Error() != multiError[i].Error() {
+			return multiError
+		}
+	}
+	// datastore.ErrInvalidKey is returned as a single error in PutMulti
+	return init
+}
diff --git a/goon_test.go b/goon_test.go
index e484332..440da44 100644
--- a/goon_test.go
+++ b/goon_test.go
@@ -21,6 +21,7 @@
 	"context"
 	"errors"
 	"fmt"
+	"math/rand"
 	"reflect"
 	"strings"
 	"sync"
@@ -469,6 +470,7 @@
 }
 
 var ivItems []ivItem
+var ivItemKeys []*datastore.Key
 
 func initializeIvItems(c context.Context) {
 	// We force UTC, because the datastore API will always return UTC
@@ -593,6 +595,11 @@
 	ivItems = append(ivItems, *ivi1)
 	ivItems = append(ivItems, *ivi2)
 	ivItems = append(ivItems, *ivi3)
+
+	g := FromContext(c)
+	for i := range ivItems {
+		ivItemKeys = append(ivItemKeys, g.Key(&ivItems[i]))
+	}
 }
 
 func getInputVarietyItem(t *testing.T, g *Goon, ivType int, empty bool, indices ...int) interface{} {
@@ -755,20 +762,6 @@
 	return false
 }
 
-func ivWipe(t *testing.T, g *Goon, prettyInfo string) {
-	// Make sure the datastore is clear of any previous tests
-	// TODO: Batch this once goon gets more convenient batch delete support
-	for _, ivi := range ivItems {
-		if err := g.Delete(g.Key(ivi)); err != nil {
-			t.Fatalf("%s > Unexpected error on delete - %v", prettyInfo, err)
-		}
-	}
-
-	// Make sure the caches are clear, so any caching is done by our specific test
-	g.FlushLocalCache()
-	memcache.Flush(g.Context)
-}
-
 // getDiff is a helper function that returns string lines describing the differences between a & b
 func getDiff(a, b interface{}, aName, bName string) string {
 	var buf bytes.Buffer
@@ -819,13 +812,23 @@
 	return buf.String()
 }
 
-func ivGetMulti(t *testing.T, g *Goon, ref, dst interface{}, dirtyFunc func(), prettyInfo string) error {
-	// Dirty up the local cache entries before Get
-	// NOTE: It would be awesome if we could dirty them up after Get and have it match,
-	//       however the current goon implementation does a value copy of the cached entity.
-	if dirtyFunc != nil {
-		dirtyFunc()
+func onlyErrNoSuchEntity(err error) bool {
+	if err == nil {
+		return false
 	}
+	merr, ok := err.(appengine.MultiError)
+	if !ok || len(merr) == 0 {
+		return false
+	}
+	for i := 0; i < len(merr); i++ {
+		if merr[i] != datastore.ErrNoSuchEntity {
+			return false
+		}
+	}
+	return true
+}
+
+func ivGetMulti(t *testing.T, g *Goon, ref, dst interface{}, prettyInfo string) error {
 	// Get our data back and make sure it's correct
 	if err := g.GetMulti(dst); err != nil {
 		t.Fatalf("%s > Unexpected error on GetMulti - %v", prettyInfo, err)
@@ -887,8 +890,7 @@
 	}
 }
 
-// This function tests standard Put/Get functions and their relation to the caches
-func validateInputVariety(t *testing.T, g *Goon, srcType, dstType, mode int) {
+func validateInputVariety(t *testing.T, g *Goon, srcType, dstType, mode int, txn bool) {
 	if mode >= ivModeTotal {
 		t.Fatalf("Invalid input variety mode! %v >= %v", mode, ivModeTotal)
 		return
@@ -896,40 +898,49 @@
 
 	// Generate a nice debug info string for clear logging
 	prettyInfo := getPrettyIVType(srcType) + " " + getPrettyIVType(dstType) + " " + getPrettyIVMode(mode)
-
-	// This function just populates the cache via GetMulti
-	loadIVItem := func(indices ...int) interface{} {
-		dst := getInputVarietyItem(t, g, dstType, true, indices...)
-		if err := g.GetMulti(dst); err != nil {
-			t.Fatalf("%s > Unexpected error on GetMulti - %v", prettyInfo, err)
-		}
-		return dst
+	if txn {
+		prettyInfo += " TXN"
 	}
 
-	// Start with a clean slate
-	ivWipe(t, g, prettyInfo)
-
 	// Generate test data with the specified types
 	src := getInputVarietyItem(t, g, srcType, false, 0, 1, 2)
 	ref := getInputVarietyItem(t, g, dstType, false, 0, 1, 2)
 	dstA := getInputVarietyItem(t, g, dstType, true, 0, 1, 2)
 	dstB := getInputVarietyItem(t, g, dstType, true, 0, 1, 2)
+	dstC := getInputVarietyItem(t, g, dstType, true, 0, 1, 2)
 
 	setPLSCounts(ref, isIVTypePLS(srcType), isIVTypePLS(dstType))
 
 	// Save our test data
-	if _, err := g.PutMulti(src); err != nil {
-		t.Fatalf("%s > Unexpected error on PutMulti - %v", prettyInfo, err)
+	if txn {
+		if err := g.RunInTransaction(func(tg *Goon) error {
+			_, err := tg.PutMulti(src)
+			return err
+		}, &datastore.TransactionOptions{XG: true}); err != nil {
+			t.Fatalf("%s > Unexpected error on PutMulti - %v", prettyInfo, err)
+		}
+	} else {
+		if _, err := g.PutMulti(src); err != nil {
+			t.Fatalf("%s > Unexpected error on PutMulti - %v", prettyInfo, err)
+		}
 	}
 
 	// Attempt an immediate get, which should catch any faulty Put-based caching
-	ivGetMulti(t, g, ref, dstA, nil, prettyInfo+" PC")
+	ivGetMulti(t, g, ref, dstA, prettyInfo+" PC")
 
 	// Clear the caches, as we're going to precisely set the caches via loadIVItem
+	// TODO: Instead of clear, fill the caches with invalid data
 	g.FlushLocalCache()
 	memcache.Flush(g.Context)
 
-	var locallyCached interface{}
+	// This function just populates the cache via GetMulti
+	loadIVItem := func(indices ...int) {
+		dst := getInputVarietyItem(t, g, dstType, true, indices...)
+		if err := g.GetMulti(dst); err != nil {
+			t.Fatalf("%s > Unexpected error on GetMulti - %v", prettyInfo, err)
+		}
+		makeDirty(dst) // Make these dirty to confirm the cache doesn't reflect it
+	}
 
 	// Set the caches into proper state based on given mode
 	switch mode {
@@ -942,141 +953,51 @@
 		loadIVItem(0, 1) // Left in memcache
 		g.FlushLocalCache()
 	case ivModeLocalcache:
-		locallyCached = loadIVItem(0, 1, 2) // Left in local cache
-		makeDirty(ref, 0, 1, 2)             // Dirty up the local cache ones
+		loadIVItem(0, 1, 2) // Left in local cache
 	case ivModeLocalcacheAndMemcache:
 		loadIVItem(0) // Left in memcache
 		g.FlushLocalCache()
-		locallyCached = loadIVItem(1, 2) // Left in local cache
-		makeDirty(ref, 1, 2)             // Dirty up the local cache ones
+		loadIVItem(1, 2) // Left in local cache
 	case ivModeLocalcacheAndDatastore:
-		locallyCached = loadIVItem(0, 1) // Left in local cache
-		makeDirty(ref, 0, 1)             // Dirty up the local cache ones
+		loadIVItem(0, 1) // Left in local cache
 	case ivModeLocalcacheAndMemcacheAndDatastore:
 		loadIVItem(0) // Left in memcache
 		g.FlushLocalCache()
-		locallyCached = loadIVItem(1) // Left in local cache
-		makeDirty(ref, 1)             // Dirty up the local cache ones
+		loadIVItem(1) // Left in local cache
 	}
 
 	// Get our data back and make sure it's correct
-	ivGetMulti(t, g, ref, dstB, func() { makeDirty(locallyCached) }, prettyInfo+" GC")
-}
-
-// This function tests Put inside a transaction and its relation to the caches
-func validateInputVarietyTXNPut(t *testing.T, g *Goon, srcType, dstType, mode int) {
-	if mode >= ivModeTotal {
-		t.Fatalf("Invalid input variety mode! %v >= %v", mode, ivModeTotal)
-		return
+	if txn {
+		if err := g.RunInTransaction(func(tg *Goon) error {
+			return ivGetMulti(t, tg, ref, dstB, prettyInfo+" GC")
+		}, &datastore.TransactionOptions{XG: true}); err != nil {
+			t.Fatalf("%s > Unexpected error on transaction - %v", prettyInfo, err)
+		}
+	} else {
+		ivGetMulti(t, g, ref, dstB, prettyInfo+" GC")
 	}
 
-	// The following modes are redundant with the current goon transaction implementation
-	switch mode {
-	case ivModeMemcache:
-		return
-	case ivModeMemcacheAndDatastore:
-		return
-	case ivModeLocalcache:
-		return
-	case ivModeLocalcacheAndMemcache:
-		return
-	case ivModeLocalcacheAndDatastore:
-		return
-	case ivModeLocalcacheAndMemcacheAndDatastore:
-		return
+	// Delete our data
+	if txn {
+		if err := g.RunInTransaction(func(tg *Goon) error {
+			return tg.DeleteMulti(ivItemKeys)
+		}, &datastore.TransactionOptions{XG: true}); err != nil {
+			t.Fatalf("%s > Unexpected error on DeleteMulti - %v", prettyInfo, err)
+		}
+	} else {
+		if err := g.DeleteMulti(ivItemKeys); err != nil {
+			t.Fatalf("%s > Unexpected error on DeleteMulti - %v", prettyInfo, err)
+		}
 	}
 
-	// Generate a nice debug info string for clear logging
-	prettyInfo := getPrettyIVType(srcType) + " " + getPrettyIVType(dstType) + " " + getPrettyIVMode(mode) + " TXNPut"
-
-	// Start with a clean slate
-	ivWipe(t, g, prettyInfo)
-
-	// Generate test data with the specified types
-	src := getInputVarietyItem(t, g, srcType, false, 0, 1, 2)
-	ref := getInputVarietyItem(t, g, dstType, false, 0, 1, 2)
-	dstA := getInputVarietyItem(t, g, dstType, true, 0, 1, 2)
-	dstB := getInputVarietyItem(t, g, dstType, true, 0, 1, 2)
-
-	setPLSCounts(ref, isIVTypePLS(srcType), isIVTypePLS(dstType))
-
-	// Save our test data
-	if err := g.RunInTransaction(func(tg *Goon) error {
-		_, err := tg.PutMulti(src)
-		return err
-	}, &datastore.TransactionOptions{XG: true}); err != nil {
-		t.Fatalf("%s > Unexpected error on PutMulti - %v", prettyInfo, err)
+	// Make sure our data isn't retrievable from any layer
+	if err := g.GetMulti(dstC); !onlyErrNoSuchEntity(err) {
+		t.Fatalf("%s > Expected ErrNoSuchEntity but got %v", prettyInfo, err)
 	}
 
-	// Attempt an immediate get, which should catch any faulty Put-based caching
-	ivGetMulti(t, g, ref, dstA, nil, prettyInfo+" PC")
-
-	// Set the caches into proper state based on given mode
-	switch mode {
-	case ivModeDatastore:
-		g.FlushLocalCache()
-		memcache.Flush(g.Context)
-	}
-
-	// Get our data back and make sure it's correct
-	ivGetMulti(t, g, ref, dstB, nil, prettyInfo+" GC")
-}
-
-// This function tests Get inside a transaction and its relation to the caches
-func validateInputVarietyTXNGet(t *testing.T, g *Goon, srcType, dstType, mode int) {
-	if mode >= ivModeTotal {
-		t.Fatalf("Invalid input variety mode! %v >= %v", mode, ivModeTotal)
-		return
-	}
-
-	// The following modes are redundant with the current goon transaction implementation
-	switch mode {
-	case ivModeMemcache:
-		return
-	case ivModeMemcacheAndDatastore:
-		return
-	case ivModeLocalcache:
-		return
-	case ivModeLocalcacheAndMemcache:
-		return
-	case ivModeLocalcacheAndDatastore:
-		return
-	case ivModeLocalcacheAndMemcacheAndDatastore:
-		return
-	}
-
-	// Generate a nice debug info string for clear logging
-	prettyInfo := getPrettyIVType(srcType) + " " + getPrettyIVType(dstType) + " " + getPrettyIVMode(mode) + " TXNGet"
-
-	// Start with a clean slate
-	ivWipe(t, g, prettyInfo)
-
-	// Generate test data with the specified types
-	src := getInputVarietyItem(t, g, srcType, false, 0, 1, 2)
-	ref := getInputVarietyItem(t, g, dstType, false, 0, 1, 2)
-	dst := getInputVarietyItem(t, g, dstType, true, 0, 1, 2)
-
-	setPLSCounts(ref, isIVTypePLS(srcType), isIVTypePLS(dstType))
-
-	// Save our test data
-	if _, err := g.PutMulti(src); err != nil {
-		t.Fatalf("%s > Unexpected error on PutMulti - %v", prettyInfo, err)
-	}
-
-	// Set the caches into proper state based on given mode
-	// TODO: Instead of clear, fill the caches with invalid data, because we're supposed to always fetch from the datastore
-	switch mode {
-	case ivModeDatastore:
-		g.FlushLocalCache()
-		memcache.Flush(g.Context)
-	}
-
-	// Get our data back and make sure it's correct
-	if err := g.RunInTransaction(func(tg *Goon) error {
-		return ivGetMulti(t, tg, ref, dst, nil, prettyInfo+" GC")
-	}, &datastore.TransactionOptions{XG: true}); err != nil {
-		t.Fatalf("%s > Unexpected error on transaction - %v", prettyInfo, err)
-	}
+	// Do final clean-up of any negative cache
+	g.FlushLocalCache()
+	memcache.Flush(g.Context)
 }
 
 func TestInputVariety(t *testing.T) {
@@ -1092,9 +1013,9 @@
 	for srcType := 0; srcType < ivTypeTotal; srcType++ {
 		for dstType := 0; dstType < ivTypeTotal; dstType++ {
 			for mode := 0; mode < ivModeTotal; mode++ {
-				validateInputVariety(t, g, srcType, dstType, mode)
-				validateInputVarietyTXNPut(t, g, srcType, dstType, mode)
-				validateInputVarietyTXNGet(t, g, srcType, dstType, mode)
+				for txn := 0; txn < 2; txn++ {
+					validateInputVariety(t, g, srcType, dstType, mode, txn == 1)
+				}
 			}
 		}
 	}
@@ -1217,20 +1138,32 @@
 	if _, err := g.Put(dA); err != nil {
 		t.Fatalf("Unexpected error on Put: %v", err)
 	}
-	// Clear the local cache and get it to fill the memcache
-	g.FlushLocalCache()
-	dX := &dummyPLS{Id: 1}
-	if err := g.Get(dX); err != nil {
-		t.Fatalf("Unexpected error on Get: %v", err)
-	}
-	// Clear the local cache and get it from the memcache
-	g.FlushLocalCache()
-	dB := &dummyPLS{Id: 1}
-	if err := g.Get(dB); err != nil {
-		t.Fatalf("Unexpected error on Get: %v", err)
-	}
-	if !reflect.DeepEqual(dA, dB) {
-		t.Errorf("dA & dB don't match!\n%s", getDiff(dA, dB, "dA", "dB"))
+
+	for i := 0; i < 5; i++ {
+		switch i {
+		case 0:
+			// Test immediately after Put, leave caches as is
+		case 1:
+			// Clear the local cache to test memcache
+			g.FlushLocalCache()
+		case 2:
+			// Clear both caches to test datastore
+			g.FlushLocalCache()
+			memcache.Flush(g.Context)
+		case 3:
+			// Test local cache from the Get
+		case 4:
+			// Clear the local cache to test memcache from the Get
+			g.FlushLocalCache()
+		}
+
+		dB := &dummyPLS{Id: dA.Id}
+		if err := g.Get(dB); err != nil {
+			t.Fatalf("Unexpected error on Get #%d: %v", i, err)
+		}
+		if !reflect.DeepEqual(dA, dB) {
+			t.Errorf("dA & dB don't match #%d!\n%s", i, getDiff(dA, dB, "dA", "dB"))
+		}
 	}
 }
 
@@ -1259,6 +1192,7 @@
 	ZZs              []ZigZag          `datastore:"zigzag,noindex"`
 	ZeroKey          *datastore.Key    `datastore:",noindex"`
 	File             []byte
+	LoadCount        int
 	DeprecatedField  string       `datastore:"depf,noindex"`
 	DeprecatedStruct MigrationSub `datastore:"deps,noindex"`
 	FinalField       string       `datastore:"final,noindex"` // This should always be last, to test deprecating middle properties
@@ -1340,6 +1274,7 @@
 	ZZs            ZigZags           `datastore:"zigzag,noindex"`
 	Keys           []*datastore.Key  `datastore:"ZeroKey,noindex"`
 	Files          [][]byte          `datastore:"File,noindex"`
+	LoadCount      int               `datastore:"LoadCount,noindex"`
 	FinalField     string            `datastore:"final,noindex"`
 }
 
@@ -1367,7 +1302,9 @@
 	return datastore.SaveStruct(m)
 }
 func (m *MigrationPlsA) Load(props []datastore.Property) error {
-	return datastore.LoadStruct(m, props)
+	err := datastore.LoadStruct(m, props)
+	m.LoadCount++
+	return err
 }
 
 func (m MigrationPlsB) parent() *datastore.Key {
@@ -1380,7 +1317,9 @@
 	return datastore.SaveStruct(m)
 }
 func (m *MigrationPlsB) Load(props []datastore.Property) error {
-	return datastore.LoadStruct(m, props)
+	err := datastore.LoadStruct(m, props)
+	m.LoadCount++
+	return err
 }
 
 // Make sure these implement datastore.PropertyLoadSaver
@@ -1466,12 +1405,17 @@
 				t.Fatalf("Unexpected error on Get: %v", err)
 			}
 
-			// Clear the local cache, because it doesn't need to support migration
+			// Test whether local cache supports migration
+			var fetched MigrationEntity
+			debugInfo := fmt.Sprintf("%s LC-%v", tt.name, IgnoreFieldMismatch)
+			fetched = verifyMigration(t, g, tt.src, tt.dst, migrationMethodGet, debugInfo)
+			checkMigrationResult(t, g, tt.src, fetched, debugInfo)
+
+			// Clear the local cache, to test memcache
 			g.FlushLocalCache()
 
 			// Test whether memcache supports migration
-			var fetched MigrationEntity
-			debugInfo := fmt.Sprintf("%s - field mismatch: %v - MC", tt.name, IgnoreFieldMismatch)
+			debugInfo = fmt.Sprintf("%s MC-%v", tt.name, IgnoreFieldMismatch)
 			fetched = verifyMigration(t, g, tt.src, tt.dst, migrationMethodGet, debugInfo)
 			checkMigrationResult(t, g, tt.src, fetched, debugInfo)
 
@@ -1563,6 +1507,10 @@
 		migB = v
 	case *MigrationPlsB:
 		migB = (*MigrationB)(v)
+
+		if migB.LoadCount != 1 {
+			t.Errorf("%v > Expected LoadCount 1 but got %d", debugInfo, migB.LoadCount)
+		}
 	}
 
 	if migA.Id != migB.Identification {
@@ -1748,22 +1696,31 @@
 	defer done()
 	g := FromContext(c)
 
-	hid := &HasId{Id: 1}
-	if err := g.Get(hid); err != datastore.ErrNoSuchEntity {
-		t.Fatalf("Expected ErrNoSuchEntity, got %v", err)
-	}
+	// Run twice to test local cache & memcache
+	for _, mode := range []string{"MC", "LC"} {
+		debugInfo := fmt.Sprintf("%s", mode)
 
-	// Do a sneaky save straight to the datastore
-	if _, err := datastore.Put(c, datastore.NewKey(c, "HasId", "", 1, nil), &HasId{Id: 1, Name: "one"}); err != nil {
-		t.Fatalf("Unexpected error on datastore.Put: %v", err)
-	}
+		hid := &HasId{Id: rand.Int63()}
+		if err := g.Get(hid); err != datastore.ErrNoSuchEntity {
+			t.Fatalf("%s > Expected ErrNoSuchEntity, got %v", debugInfo, err)
+		}
 
-	// Clear local cache to test memcache
-	g.FlushLocalCache()
+		// Do a sneaky save straight to the datastore
+		if _, err := datastore.Put(c, datastore.NewKey(c, "HasId", "", hid.Id, nil), &HasId{Id: hid.Id, Name: "one"}); err != nil {
+			t.Fatalf("%s > Unexpected error on datastore.Put: %v", debugInfo, err)
+		}
 
-	// Get the entity again via goon, to make sure we cached the non-existance
-	if err := g.Get(hid); err != datastore.ErrNoSuchEntity {
-		t.Errorf("Expected ErrNoSuchEntity, got %v", err)
+		switch mode {
+		case "MC":
+			g.FlushLocalCache()
+		case "LC":
+			memcache.Flush(c)
+		}
+
+		// Get the entity again via goon, to make sure we cached the non-existance
+		if err := g.Get(hid); err != datastore.ErrNoSuchEntity {
+			t.Errorf("%s > Expected ErrNoSuchEntity, got %v", debugInfo, err)
+		}
 	}
 }
 
@@ -1775,47 +1732,65 @@
 	defer done()
 	g := FromContext(c)
 
-	hid := &HasId{Name: "one"}
-	var id int64
+	// Run twice to test local cache & memcache
+	for _, mode := range []string{"MC", "LC"} {
+		for tx := 0; tx < 2; tx++ {
+			debugInfo := fmt.Sprintf("%s-%v", mode, tx == 1)
 
-	puted := make(chan bool)
-	cached := make(chan bool)
-	ended := make(chan bool)
+			hid := &HasId{Name: "one"}
+			var id int64
 
-	go func() {
-		err := g.RunInTransaction(func(tg *Goon) error {
-			tg.Put(hid)
-			id = hid.Id
-			puted <- true
-			<-cached
-			return nil
-		}, nil)
-		if err != nil {
-			t.Errorf("Unexpected error on RunInTransaction: %v", err)
-		}
-		ended <- true
-	}()
+			ided := make(chan bool)
+			cached := make(chan bool)
+			ended := make(chan bool)
 
-	// simulate negative cache (yet commit)
-	{
-		<-puted
-		negative := &HasId{Id: id}
-		g.FlushLocalCache()
-		if err := g.Get(negative); err != datastore.ErrNoSuchEntity {
-			t.Fatalf("Expected ErrNoSuchEntity, got %v", err)
-		}
-		cached <- true
-	}
+			go func() {
+				var err error
+				if tx == 0 {
+					id = rand.Int63()
+					hid.Id = id
+					ided <- true
+					<-cached
+					_, err = g.Put(hid)
+				} else {
+					err = g.RunInTransaction(func(tg *Goon) error {
+						_, err := tg.Put(hid)
+						id = hid.Id
+						ided <- true
+						<-cached
+						return err
+					}, nil)
+				}
+				if err != nil {
+					t.Errorf("%s > Unexpected error on RunInTransaction: %v", debugInfo, err)
+				}
+				ended <- true
+			}()
 
-	{
-		<-ended
-		want := &HasId{Id: id}
-		g.FlushLocalCache()
-		if err := g.Get(want); err != nil {
-			t.Fatalf("Unexpected error on get: %v", err)
-		}
-		if want.Name != hid.Name {
-			t.Fatalf("Expected %v but got %v", hid.Name, want.Name)
+			// Populate the cache with a negative hit
+			{
+				<-ided
+				negative := &HasId{Id: id}
+				if err := g.Get(negative); err != datastore.ErrNoSuchEntity {
+					t.Fatalf("%s > Expected ErrNoSuchEntity, got %v", debugInfo, err)
+				}
+				cached <- true
+			}
+
+			// Make sure the negative cache no longer exists
+			{
+				<-ended
+				want := &HasId{Id: id}
+				if mode == "MC" {
+					g.FlushLocalCache()
+				}
+				if err := g.Get(want); err != nil {
+					t.Fatalf("%s > Unexpected error on get: %v", debugInfo, err)
+				}
+				if want.Name != hid.Name {
+					t.Fatalf("%s > Expected '%v' but got '%v'", debugInfo, hid.Name, want.Name)
+				}
+			}
 		}
 	}
 }
@@ -1991,8 +1966,8 @@
 	}
 	if err := n.GetMulti(nes); err != nil {
 		t.Fatalf("put: unexpected error")
-	} else if *es[0] != *nes[0] || *es[1] != *nes[1] {
-		t.Fatalf("put: bad results")
+	} else if !reflect.DeepEqual(es, nes) {
+		t.Fatalf("put: bad results\n%s", getDiff(es, nes, "es", "nes"))
 	} else {
 		nesk0 := n.Key(nes[0])
 		if !nesk0.Equal(datastore.NewKey(c, "HasId", "", 1, nil)) {
@@ -2031,16 +2006,11 @@
 		t.Fatalf("get: unexpected error - %v", err)
 	}
 	if hi2.Name != hi.Name {
-		n.cacheLock.Lock()
-		cv := n.cache[MemcacheKey(n.Key(hi2))]
-		n.cacheLock.Unlock()
-		t.Fatalf("Could not fetch HasId object from memory - %#v != %#v, memory=%#v", hi, hi2, cv)
+		t.Fatalf("Could not fetch HasId object from memory - %#v != %#v", hi, hi2)
 	}
 
 	hi3 := &HasId{Id: hi.Id}
-	n.cacheLock.Lock()
-	delete(n.cache, MemcacheKey(n.Key(hi)))
-	n.cacheLock.Unlock()
+	n.cache.Delete(MemcacheKey(n.Key(hi)))
 	if err := n.Get(hi3); err != nil {
 		t.Fatalf("get: unexpected error - %v", err)
 	}
@@ -2049,9 +2019,7 @@
 	}
 
 	hi4 := &HasId{Id: hi.Id}
-	n.cacheLock.Lock()
-	delete(n.cache, MemcacheKey(n.Key(hi4)))
-	n.cacheLock.Unlock()
+	n.cache.Delete(MemcacheKey(n.Key(hi4)))
 	if memcache.Flush(n.Context) != nil {
 		t.Fatalf("Unable to flush memcache")
 	}
@@ -2068,25 +2036,31 @@
 	//   but this tests that goon isn't still pulling from the datastore (or memcache) unnecessarily
 	// hi in datastore Name = hasid
 	hiPull := &HasId{Id: hi.Id}
-	n.cacheLock.Lock()
-	n.cache[MemcacheKey(n.Key(hi))].(*HasId).Name = "changedincache"
-	n.cacheLock.Unlock()
+	{
+		cachekey := MemcacheKey(n.Key(hi))
+		hiTamper := &HasId{Id: hi.Id, Name: "changedincache"}
+		data, err := serializeStruct(hiTamper)
+		if err != nil {
+			t.Fatalf("Unexpected error serializing: %v", err)
+		}
+		n.cache.Set(&cacheItem{key: cachekey, value: data})
+	}
 	if err := n.Get(hiPull); err != nil {
 		t.Fatalf("get: unexpected error - %v", err)
 	}
 	if hiPull.Name != "changedincache" {
 		t.Fatalf("hiPull.Name should be 'changedincache' but got %s", hiPull.Name)
 	}
-
-	hiPush := &HasId{Id: hi.Id, Name: "changedinmemcache"}
-	props, err := datastore.SaveStruct(hiPush)
-	if err != nil {
-		t.Fatalf("datastore.SaveStruct failed: %v", err)
+	{
+		cachekey := MemcacheKey(n.Key(hi))
+		hiPush := &HasId{Id: hi.Id, Name: "changedinmemcache"}
+		data, err := serializeStruct(hiPush)
+		if err != nil {
+			t.Fatalf("Unexpected error serializing: %v", err)
+		}
+		n.putMemcache([]*cacheItem{{key: cachekey, value: data}})
+		n.cache.Delete(cachekey)
 	}
-	n.putMemcache([]cacheEntry{{key: n.Key(hiPush), props: props}})
-	n.cacheLock.Lock()
-	delete(n.cache, MemcacheKey(n.Key(hi)))
-	n.cacheLock.Unlock()
 
 	hiPull = &HasId{Id: hi.Id}
 	if err := n.Get(hiPull); err != nil {
@@ -2118,17 +2092,52 @@
 		t.Fatalf("GetAll Zero: expected 0 keys, got %v", len(dskeys))
 	}
 
-	// Create some entities that we will query for
-	if getKeys, err := n.PutMulti([]*QueryItem{{Id: 1, Data: "one"}, {Id: 2, Data: "two"}}); err != nil {
-		t.Fatalf("PutMulti: unexpected error: %v", err)
-	} else {
-		// do a datastore Get by *Key so that data is written to the datstore and indexes generated before subsequent query
-		if err := datastore.GetMulti(c, getKeys, make([]QueryItem, 2)); err != nil {
+	createEntities := func() {
+		// Create some entities that we will query for
+		if getKeys, err := n.PutMulti([]*QueryItem{{Id: 1, Data: "one"}, {Id: 2, Data: "two"}}); err != nil {
+			t.Fatalf("PutMulti: unexpected error: %v", err)
+		} else {
+			// do a datastore Get by *Key so that data is written to the datstore and indexes generated before subsequent query
+			if err := datastore.GetMulti(c, getKeys, make([]QueryItem, 2)); err != nil {
+				t.Error(err)
+			}
+		}
+		// Make sure we clear the cache, as we will fill it later with a query
+		n.FlushLocalCache()
+	}
+	deleteEntities := func(ids ...int64) {
+		if len(ids) == 0 {
+			ids = []int64{1, 2}
+		}
+		var keys []*datastore.Key
+		for _, id := range ids {
+			keys = append(keys, datastore.NewKey(c, "QueryItem", "", id, nil))
+		}
+		if err := datastore.DeleteMulti(c, keys); err != nil {
 			t.Error(err)
 		}
 	}
 
-	// Clear the local memory cache, because we want to test it being filled correctly by GetAll
+	createEntities()
+
+	// Get the entity using a slice of structs that needs to be appended but has garbage data
+	qiSGRes := []QueryItem{{Id: 1, Data: "invalid cache"}, {Garbage: "uninitialized memory"}}[:1]
+	if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "two"), &qiSGRes); err != nil {
+		t.Fatalf("GetAll SoS: unexpected error: %v", err)
+	} else if len(dskeys) != 1 {
+		t.Fatalf("GetAll SoS: expected 1 key, got %v", len(dskeys))
+	} else if dskeys[0].IntID() != 2 {
+		t.Fatalf("GetAll SoS: expected key IntID to be 2, got %v", dskeys[0].IntID())
+	} else if len(qiSGRes) != 2 {
+		t.Fatalf("GetAll SoS: expected 2 results, got %v", len(qiSGRes))
+	} else if qiSGRes[1].Id != 2 {
+		t.Fatalf("GetAll SoS: expected entity id to be 2, got %v", qiSGRes[1].Id)
+	} else if qiSGRes[1].Data != "two" {
+		t.Fatalf("GetAll SoS: expected entity data to be 'two', got '%v'", qiSGRes[1].Data)
+	} else if qiSGRes[1].Garbage != "" {
+		t.Fatalf("GetAll SoS: expected no garbage data, but got '%v'", qiSGRes[1].Garbage)
+	}
+
 	n.FlushLocalCache()
 
 	// Get the entity using a slice of structs
@@ -2147,7 +2156,9 @@
 		t.Fatalf("GetAll SoS: expected entity data to be 'one', got '%v'", qiSRes[0].Data)
 	}
 
-	// Get the entity using normal Get to test local cache (provided the local cache actually got saved)
+	deleteEntities()
+
+	// Get the entity using normal Get to test local cache
 	qiS := &QueryItem{Id: 1}
 	if err := n.Get(qiS); err != nil {
 		t.Fatalf("Get SoS: unexpected error: %v", err)
@@ -2157,8 +2168,7 @@
 		t.Fatalf("Get SoS: expected entity data to be 'one', got '%v'", qiS.Data)
 	}
 
-	// Clear the local memory cache, because we want to test it being filled correctly by GetAll
-	n.FlushLocalCache()
+	createEntities()
 
 	// Get the entity using a slice of pointers to struct
 	qiPRes := []*QueryItem{}
@@ -2176,7 +2186,9 @@
 		t.Fatalf("GetAll SoPtS: expected entity data to be 'one', got '%v'", qiPRes[0].Data)
 	}
 
-	// Get the entity using normal Get to test local cache (provided the local cache actually got saved)
+	deleteEntities()
+
+	// Get the entity using normal Get to test local cache
 	qiP := &QueryItem{Id: 1}
 	if err := n.Get(qiP); err != nil {
 		t.Fatalf("Get SoPtS: unexpected error: %v", err)
@@ -2186,8 +2198,7 @@
 		t.Fatalf("Get SoPtS: expected entity data to be 'one', got '%v'", qiP.Data)
 	}
 
-	// Clear the local memory cache, because we want to test it being filled correctly by Next
-	n.FlushLocalCache()
+	createEntities()
 
 	// Get the entity using an iterator
 	qiIt := n.Run(datastore.NewQuery("QueryItem").Filter("data=", "one"))
@@ -2208,7 +2219,9 @@
 		t.Fatalf("Next: expected iterator to end with the error datastore.Done, got %v", err)
 	}
 
-	// Get the entity using normal Get to test local cache (provided the local cache actually got saved)
+	deleteEntities()
+
+	// Get the entity using normal Get to test local cache
 	qiI := &QueryItem{Id: 1}
 	if err := n.Get(qiI); err != nil {
 		t.Fatalf("Get Iterator: unexpected error: %v", err)
@@ -2218,10 +2231,9 @@
 		t.Fatalf("Get Iterator: expected entity data to be 'one', got '%v'", qiI.Data)
 	}
 
-	// Clear the local memory cache, because we want to test it not being filled incorrectly when supplying a non-zero slice
-	n.FlushLocalCache()
+	createEntities()
 
-	// Get the entity using a non-zero slice of structs
+	// Get the entity using a non-zero slice of structs, to also test the cache being filled incorrectly
 	qiNZSRes := []QueryItem{{Id: 1, Data: "invalid cache"}}
 	if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "two"), &qiNZSRes); err != nil {
 		t.Fatalf("GetAll NZSoS: unexpected error: %v", err)
@@ -2241,6 +2253,8 @@
 		t.Fatalf("GetAll NZSoS: expected entity data to be 'two', got '%v'", qiNZSRes[1].Data)
 	}
 
+	deleteEntities(2)
+
 	// Get the entities using normal GetMulti to test local cache
 	qiNZSs := []QueryItem{{Id: 1}, {Id: 2}}
 	if err := n.GetMulti(qiNZSs); err != nil {
@@ -2257,10 +2271,9 @@
 		t.Fatalf("GetMulti NZSoS: expected entity data to be 'two', got '%v'", qiNZSs[1].Data)
 	}
 
-	// Clear the local memory cache, because we want to test it not being filled incorrectly when supplying a non-zero slice
-	n.FlushLocalCache()
+	createEntities()
 
-	// Get the entity using a non-zero slice of pointers to struct
+	// Get the entity using a non-zero slice of pointers to struct, to also test the cache being filled incorrectly
 	qiNZPRes := []*QueryItem{{Id: 1, Data: "invalid cache"}}
 	if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "two"), &qiNZPRes); err != nil {
 		t.Fatalf("GetAll NZSoPtS: unexpected error: %v", err)
@@ -2280,6 +2293,8 @@
 		t.Fatalf("GetAll NZSoPtS: expected entity data to be 'two', got '%v'", qiNZPRes[1].Data)
 	}
 
+	deleteEntities(2)
+
 	// Get the entities using normal GetMulti to test local cache
 	qiNZPs := []*QueryItem{{Id: 1}, {Id: 2}}
 	if err := n.GetMulti(qiNZPs); err != nil {
@@ -2296,10 +2311,40 @@
 		t.Fatalf("GetMulti NZSoPtS: expected entity data to be 'two', got '%v'", qiNZPs[1].Data)
 	}
 
-	// Clear the local memory cache, because we want to test it not being filled incorrectly by a keys-only query
+	createEntities()
+
+	// Get the entity using a keys-only iterator
+	qiItKO := n.Run(datastore.NewQuery("QueryItem").Filter("data=", "one").KeysOnly())
+
+	qiItKORes := &QueryItem{}
+	if dskey, err := qiItKO.Next(qiItKORes); err != nil {
+		t.Fatalf("Next: unexpected error: %v", err)
+	} else if dskey.IntID() != 1 {
+		t.Fatalf("Next: expected key IntID to be 1, got %v", dskey.IntID())
+	} else if qiItKORes.Id != 1 {
+		t.Fatalf("Next: expected entity id to be 1, got %v", qiItKORes.Id)
+	} else if qiItKORes.Data != "" {
+		t.Fatalf("Next: expected entity data to be empty, got '%v'", qiItKORes.Data)
+	}
+
+	// Make sure the iterator ends correctly
+	if _, err := qiItKO.Next(&QueryItem{}); err != datastore.Done {
+		t.Fatalf("Next: expected iterator to end with the error datastore.Done, got %v", err)
+	}
+
+	// Get the entity using normal Get to test local cache
+	qiIKO := &QueryItem{Id: 1}
+	if err := n.Get(qiIKO); err != nil {
+		t.Fatalf("Get Iterator: unexpected error: %v", err)
+	} else if qiIKO.Id != 1 {
+		t.Fatalf("Get Iterator: expected entity id to be 1, got %v", qiIKO.Id)
+	} else if qiIKO.Data != "one" {
+		t.Fatalf("Get Iterator: expected entity data to be 'one', got '%v'", qiIKO.Data)
+	}
+
 	n.FlushLocalCache()
 
-	// Test the simplest keys-only query
+	// Test the simplest keys-only query, also test the cache not being filled incorrectly by a keys-only query
 	if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "one").KeysOnly(), nil); err != nil {
 		t.Fatalf("GetAll KeysOnly: unexpected error: %v", err)
 	} else if len(dskeys) != 1 {
@@ -2318,10 +2363,9 @@
 		t.Fatalf("Get KeysOnly: expected entity data to be 'one', got '%v'", qiKO.Data)
 	}
 
-	// Clear the local memory cache, because we want to test it not being filled incorrectly by a keys-only query
 	n.FlushLocalCache()
 
-	// Test the keys-only query with slice of structs
+	// Test the keys-only query with slice of structs, also test the cache not being filled incorrectly by a keys-only query
 	qiKOSRes := []QueryItem{}
 	if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "one").KeysOnly(), &qiKOSRes); err != nil {
 		t.Fatalf("GetAll KeysOnly SoS: unexpected error: %v", err)
@@ -2348,10 +2392,9 @@
 		t.Fatalf("Get KeysOnly SoS: expected entity data to be 'one', got '%v'", qiKOSRes[0].Data)
 	}
 
-	// Clear the local memory cache, because we want to test it not being filled incorrectly by a keys-only query
 	n.FlushLocalCache()
 
-	// Test the keys-only query with slice of pointers to struct
+	// Test the keys-only query with slice of pointers to struct, also test the cache not being filled incorrectly by a keys-only query
 	qiKOPRes := []*QueryItem{}
 	if dskeys, err := n.GetAll(datastore.NewQuery("QueryItem").Filter("data=", "one").KeysOnly(), &qiKOPRes); err != nil {
 		t.Fatalf("GetAll KeysOnly SoPtS: unexpected error: %v", err)
@@ -2378,7 +2421,6 @@
 		t.Fatalf("Get KeysOnly SoPtS: expected entity data to be 'one', got '%v'", qiKOPRes[0].Data)
 	}
 
-	// Clear the local memory cache, because we want to test it not being filled incorrectly when supplying a non-zero slice
 	n.FlushLocalCache()
 
 	// Test the keys-only query with non-zero slice of structs
@@ -2418,7 +2460,6 @@
 		t.Fatalf("GetMulti NZSoS: expected entity data to be 'two', got '%v'", qiKONZSRes[1].Data)
 	}
 
-	// Clear the local memory cache, because we want to test it not being filled incorrectly when supplying a non-zero slice
 	n.FlushLocalCache()
 
 	// Test the keys-only query with non-zero slice of pointers to struct
@@ -2485,8 +2526,9 @@
 }
 
 type QueryItem struct {
-	Id   int64  `datastore:"-" goon:"id"`
-	Data string `datastore:"data"`
+	Id      int64  `datastore:"-" goon:"id"`
+	Data    string `datastore:"data"`
+	Garbage string `datastore:"-"`
 }
 
 type HasString struct {
@@ -2942,29 +2984,29 @@
 	}
 
 	// Generate the cache entry
-	props, err := datastore.SaveStruct(hi)
+	data, err := serializeStruct(hi)
 	if err != nil {
-		t.Fatalf("Unexpected error on SaveStruct: %v", err)
+		t.Fatalf("Unexpected error on serialize: %v", err)
 	}
-	ce := cacheEntry{
-		key:   g.Key(hi),
-		props: props,
+	ci := &cacheItem{
+		key:   MemcacheKey(g.Key(hi)),
+		value: data,
 	}
-	ces := []cacheEntry{ce}
+	cis := []*cacheItem{ci}
 
 	MemcachePutTimeoutSmall = 0
-	if err := g.putMemcache(ces); !appengine.IsTimeoutError(err) {
+	if err := g.putMemcache(cis); !appengine.IsTimeoutError(err) {
 		t.Fatalf("Request should timeout - err = %v", err)
 	}
 	MemcachePutTimeoutSmall = time.Second
 	MemcachePutTimeoutThreshold = 0
 	MemcachePutTimeoutLarge = 0
-	if err := g.putMemcache(ces); !appengine.IsTimeoutError(err) {
+	if err := g.putMemcache(cis); !appengine.IsTimeoutError(err) {
 		t.Fatalf("Request should timeout - err = %v", err)
 	}
 
 	MemcachePutTimeoutLarge = time.Second
-	if err := g.putMemcache(ces); err != nil {
+	if err := g.putMemcache(cis); err != nil {
 		t.Fatalf("putMemcache: unexpected error - %v", err)
 	}
 
diff --git a/query.go b/query.go
index 2277d3e..ee89b20 100644
--- a/query.go
+++ b/query.go
@@ -39,87 +39,126 @@
 // See: https://developers.google.com/appengine/docs/go/datastore/reference#Query.GetAll
 func (g *Goon) GetAll(q *datastore.Query, dst interface{}) ([]*datastore.Key, error) {
 	v := reflect.ValueOf(dst)
+	dstV := reflect.Indirect(v)
 	vLenBefore := 0
-
 	if dst != nil {
 		if v.Kind() != reflect.Ptr {
 			return nil, fmt.Errorf("goon: Expected dst to be a pointer to a slice or nil, got instead: %v", v.Kind())
 		}
-
 		v = v.Elem()
 		if v.Kind() != reflect.Slice {
 			return nil, fmt.Errorf("goon: Expected dst to be a pointer to a slice or nil, got instead: %v", v.Kind())
 		}
-
 		vLenBefore = v.Len()
 	}
 
-	keys, err := q.GetAll(g.Context, dst)
+	var propLists []datastore.PropertyList
+	keys, err := q.GetAll(g.Context, &propLists)
 	if err != nil {
-		if errFieldMismatch(err) {
-			if IgnoreFieldMismatch {
-				err = nil
-			}
-		} else {
-			g.error(err)
-			return keys, err
-		}
+		g.error(err)
+		return keys, err
 	}
 	if dst == nil || len(keys) == 0 {
 		return keys, err
 	}
 
-	keysOnly := ((v.Len() - vLenBefore) != len(keys))
+	keysOnly := (len(propLists) != len(keys))
 	updateCache := !g.inTransaction && !keysOnly
 
-	// If this is a keys-only query, we need to fill the slice with zero value elements
-	if keysOnly {
-		elemType := v.Type().Elem()
-		ptr := false
-		if elemType.Kind() == reflect.Ptr {
-			elemType = elemType.Elem()
-			ptr = true
+	elemType := v.Type().Elem()
+	elemTypeIsPtr := false
+	if elemType.Kind() == reflect.Ptr {
+		elemType = elemType.Elem()
+		elemTypeIsPtr = true
+	}
+	if elemType.Kind() != reflect.Struct {
+		return keys, fmt.Errorf("goon: Expected struct, got instead: %v", elemType.Kind())
+	}
+
+	initMem := false
+	finalLen := vLenBefore + len(keys)
+	if v.Cap() < finalLen {
+		// If the slice doesn't have enough capacity for the final length,
+		// then create a new slice with the exact capacity needed,
+		// with all elements zero-value initialized already.
+		newSlice := reflect.MakeSlice(v.Type(), finalLen, finalLen)
+		if copied := reflect.Copy(newSlice, v); copied != vLenBefore {
+			return keys, fmt.Errorf("goon: Wanted to copy %v elements to dst but managed %v", vLenBefore, copied)
 		}
-
-		if elemType.Kind() != reflect.Struct {
-			return keys, fmt.Errorf("goon: Expected struct, got instead: %v", elemType.Kind())
-		}
-
-		for i := 0; i < len(keys); i++ {
-			ev := reflect.New(elemType)
-			if !ptr {
-				ev = ev.Elem()
-			}
-
-			v.Set(reflect.Append(v, ev))
+		v = newSlice
+	} else {
+		// If the slice already has enough capacity ..
+		if elemTypeIsPtr {
+			// .. then just change the length if it's a slice of pointers,
+			// because we will overwrite all the pointers anyway.
+			v.SetLen(finalLen)
+		} else {
+			// .. we need to initialize the memory of every non-pointer element.
+			initMem = true
 		}
 	}
 
+	var toCache []*cacheItem
 	if updateCache {
-		g.cacheLock.Lock()
-		defer g.cacheLock.Unlock()
+		toCache = make([]*cacheItem, 0, len(keys))
 	}
+	var rerr error
 
 	for i, k := range keys {
-		var e interface{}
+		if elemTypeIsPtr {
+			ev := reflect.New(elemType)
+			v.Index(vLenBefore + i).Set(ev)
+		} else if initMem {
+			ev := reflect.New(elemType).Elem()
+			v = reflect.Append(v, ev)
+		}
 		vi := v.Index(vLenBefore + i)
+
+		var e interface{}
 		if vi.Kind() == reflect.Ptr {
 			e = vi.Interface()
 		} else {
 			e = vi.Addr().Interface()
 		}
 
+		if !keysOnly {
+			if err := deserializeProperties(e, propLists[i]); err != nil {
+				if errFieldMismatch(err) {
+					// If we're not configured to ignore, set rerr to err,
+					// but proceed with deserializing other entities
+					if !IgnoreFieldMismatch {
+						rerr = err
+					}
+				} else {
+					return nil, err
+				}
+			}
+		}
+
 		if err := g.setStructKey(e, k); err != nil {
 			return nil, err
 		}
 
 		if updateCache {
-			// Cache lock is handled before the for loop
-			g.cache[MemcacheKey(k)] = e
+			// Serialize the properties
+			data, err := serializeProperties(propLists[i], true)
+			if err != nil {
+				g.error(err)
+				return nil, err
+			}
+			// Prepare the properties for caching
+			toCache = append(toCache, &cacheItem{key: MemcacheKey(k), value: data})
 		}
 	}
 
-	return keys, err
+	if len(toCache) > 0 {
+		g.cache.SetMulti(toCache)
+	}
+
+	// Set dst to the slice we created
+	dstV.Set(v)
+
+	return keys, rerr
 }
 
 // Run runs the query.
@@ -142,35 +181,49 @@
 }
 
 // Next returns the entity of the next result. When there are no more results,
-// datastore.Done is returned as the error. If dst is null (for a keys-only
-// query), nil is returned as the entity.
+// datastore.Done is returned as the error.
 //
 // If the query is not keys only and dst is non-nil, it also loads the entity
 // stored for that key into the struct pointer dst, with the same semantics
 // and possible errors as for the Get function. This result is cached in memory.
 //
-// If the query is keys only, dst must be passed as nil. Otherwise the cache
-// will be populated with empty entities since there is no way to detect the
-// case of a keys-only query.
+// If the query is keys only and dst is non-nil, dst will be given the right id.
 //
 // Refer to appengine/datastore.Iterator.Next:
 // https://developers.google.com/appengine/docs/go/datastore/reference#Iterator.Next
 func (t *Iterator) Next(dst interface{}) (*datastore.Key, error) {
-	k, err := t.i.Next(dst)
-	if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) {
+	var props datastore.PropertyList
+	k, err := t.i.Next(&props)
+	if err != nil {
 		return k, err
 	}
-
+	var rerr error
 	if dst != nil {
-		// Update the struct to have correct key info
-		t.g.setStructKey(dst, k)
-
-		if !t.g.inTransaction {
-			t.g.cacheLock.Lock()
-			t.g.cache[MemcacheKey(k)] = dst
-			t.g.cacheLock.Unlock()
+		keysOnly := (props == nil)
+		updateCache := !t.g.inTransaction && !keysOnly
+		if !keysOnly {
+			if err := deserializeProperties(dst, props); err != nil {
+				if errFieldMismatch(err) {
+					// If we're not configured to ignore, set rerr to err,
+					// but proceed with work
+					if !IgnoreFieldMismatch {
+						rerr = err
+					}
+				} else {
+					return k, err
+				}
+			}
+		}
+		if err := t.g.setStructKey(dst, k); err != nil {
+			return k, err
+		}
+		if updateCache {
+			data, err := serializeProperties(props, true)
+			if err != nil {
+				return k, err
+			}
+			t.g.cache.Set(&cacheItem{key: MemcacheKey(k), value: data})
 		}
 	}
-
-	return k, nil
+	return k, rerr
 }