Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SQLite.CodeFirst.Utility;

namespace SQLite.CodeFirst.Test.UnitTests.Utility
{
[TestClass]
public class HistoryRecordSelectorTest
{
private static IHistory Record(string context)
{
return new History { Context = context, Hash = "hash-" + context };
}

[TestMethod]
public void SelectForContext_ReturnsRecordForGivenContext()
{
var records = new List<IHistory> { Record("ContextA"), Record("ContextB") };

IHistory result = HistoryRecordSelector.SelectForContext(records, "ContextB");

Assert.IsNotNull(result);
Assert.AreEqual("ContextB", result.Context);
}

[TestMethod]
public void SelectForContext_ReturnsNull_WhenNoRecordMatches()
{
var records = new List<IHistory> { Record("ContextA") };

IHistory result = HistoryRecordSelector.SelectForContext(records, "ContextB");

Assert.IsNull(result);
}

[TestMethod]
public void SelectForContext_ReturnsNull_WhenEmpty()
{
IHistory result = HistoryRecordSelector.SelectForContext(new List<IHistory>(), "ContextA");

Assert.IsNull(result);
}

[TestMethod]
public void SelectForContext_Throws_WhenMultipleRecordsShareContext()
{
// One record per context is an invariant maintained by SaveHistory. If it is ever
// violated, surfacing it is better than silently picking an arbitrary record.
var records = new List<IHistory> { Record("ContextA"), Record("ContextA") };

Assert.ThrowsExactly<InvalidOperationException>(
() => HistoryRecordSelector.SelectForContext(records, "ContextA"));
}
}
}
19 changes: 19 additions & 0 deletions SQLite.CodeFirst/Internal/Utility/HistoryRecordSelector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;

namespace SQLite.CodeFirst.Utility
{
/// <summary>
/// Selects the history record that belongs to a specific context from the history table.
/// A database can be shared by multiple contexts, so the lookup must be scoped by the context
/// key. Selecting without that scope would return more than one record on a shared database and
/// make the underlying <see cref="Enumerable.SingleOrDefault{TSource}(IEnumerable{TSource})"/> throw.
/// </summary>
internal static class HistoryRecordSelector
{
public static IHistory SelectForContext(IEnumerable<IHistory> records, string contextKey)
{
return records.SingleOrDefault(record => record.Context == contextKey);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ private IHistory GetHistoryRecord(TContext context)
// in order to be supported by .NET 4.0.
DbQuery dbQuery = context.Set(historyEntityType).AsNoTracking();
IEnumerable<IHistory> records = Enumerable.Cast<IHistory>(dbQuery);
return records.SingleOrDefault();

// A database can be shared by several contexts, so the record must be looked up by the
// context key that SaveHistory writes. An unscoped lookup would match every context's
// record and throw on a shared history table.
return HistoryRecordSelector.SelectForContext(records, context.GetType().FullName);
}

private string GetHashFromModel(DbConnection connection)
Expand Down