Skip to content

Commit 576f8ad

Browse files
author
Adam Krantz
committed
Merge pull request #1 from akrantz/feature-composite-primary-keys
Feature: composite primary keys
2 parents 377cc65 + 1ad1f69 commit 576f8ad

File tree

5 files changed

+306
-58
lines changed

5 files changed

+306
-58
lines changed

src/SQLite.Net/Orm.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ internal static class Orm
3737
public const string ImplicitIndexSuffix = "Id";
3838

3939
internal static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks, IBlobSerializer serializer,
40-
IDictionary<Type, string> extraTypeMappings)
40+
IDictionary<Type, string> extraTypeMappings, bool hasCompositePK = false)
4141
{
4242
var decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks, serializer, extraTypeMappings) + " ";
4343

44-
if (p.IsPK)
44+
if (p.IsPK && !hasCompositePK)
4545
{
4646
decl += "primary key ";
4747
}

src/SQLite.Net/SQLiteConnection.cs

+188-43
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None)
382382
{
383383
var map = GetMapping(ty, createFlags);
384384

385-
var query = "create table if not exists \"" + map.TableName + "\"(\n";
385+
var query = new StringBuilder("create table if not exists \"").Append(map.TableName).Append("\"( \n");
386386

387387
var mapColumns = map.Columns;
388388

@@ -391,12 +391,24 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None)
391391
throw new Exception("Table has no (public) columns");
392392
}
393393

394-
var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings));
395-
var decl = string.Join(",\n", decls.ToArray());
396-
query += decl;
397-
query += ")";
394+
if (map.HasCompositePK)
395+
{
396+
var compositePK = mapColumns.Where(c => c.IsPK).ToList();
397+
398+
var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, map.HasCompositePK));
399+
var decl = string.Join(",\n", decls.ToArray());
400+
query.Append(decl).Append(",\n");
401+
query.Append("primary key (").Append(string.Join(",", compositePK.Select(pk => pk.Name))).Append(")");
402+
query.Append(")");
403+
}
404+
else
405+
{
406+
var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings));
407+
var decl = string.Join(",\n", decls.ToArray());
408+
query.Append(decl).Append(")");
409+
}
398410

399-
var count = Execute(query);
411+
var count = Execute(query.ToString());
400412

401413
if (count == 0)
402414
{
@@ -582,8 +594,13 @@ private void MigrateTable(TableMapping map)
582594

583595
foreach (var p in toBeAdded)
584596
{
597+
if (p.IsPK)
598+
{
599+
throw new NotSupportedException("The new column may not have a PRIMARY KEY constraint.");
600+
}
601+
585602
var addCol = "alter table \"" + map.TableName + "\" add column " +
586-
Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings);
603+
Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, map.HasCompositePK);
587604
Execute(addCol);
588605
}
589606
}
@@ -822,7 +839,7 @@ public TableQuery<T> Table<T>() where T : class
822839
/// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
823840
/// </summary>
824841
/// <param name="pk">
825-
/// The primary key.
842+
/// The primary key. Needs to be Dictionary<string, object> if table has composite PK.
826843
/// </param>
827844
/// <returns>
828845
/// The object with the given primary key. Throws a not found exception
@@ -832,7 +849,24 @@ public TableQuery<T> Table<T>() where T : class
832849
public T Get<T>(object pk) where T : class
833850
{
834851
var map = GetMapping(typeof (T));
835-
return Query<T>(map.GetByPrimaryKeySql, pk).First();
852+
if (map.HasCompositePK)
853+
{
854+
IDictionary<string, object> compositePK = pk as Dictionary<string, object>;
855+
if (compositePK == null)
856+
{
857+
throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
858+
}
859+
var cpk = map.CompositePK;
860+
if (compositePK.Keys.Intersect(cpk.Select(p => p.Name)).Count() < cpk.Length)
861+
{
862+
throw new NotSupportedException("Cannot get from " + map.TableName + ": CompositePK mismatch. Make sure PK names are valid.");
863+
}
864+
return Query<T>(map.GetByPrimaryKeySql, compositePK.Values.ToArray()).First();
865+
}
866+
else
867+
{
868+
return Query<T>(map.GetByPrimaryKeySql, pk).First();
869+
}
836870
}
837871

838872
/// <summary>
@@ -858,7 +892,7 @@ public T Get<T>(Expression<Func<T, bool>> predicate) where T : class
858892
/// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
859893
/// </summary>
860894
/// <param name="pk">
861-
/// The primary key.
895+
/// The primary key. Needs to be Dictionary<string, object> if table has composite PK.
862896
/// </param>
863897
/// <returns>
864898
/// The object with the given primary key or null
@@ -867,8 +901,25 @@ public T Get<T>(Expression<Func<T, bool>> predicate) where T : class
867901
[PublicAPI]
868902
public T Find<T>(object pk) where T : class
869903
{
870-
var map = GetMapping(typeof (T));
871-
return Query<T>(map.GetByPrimaryKeySql, pk).FirstOrDefault();
904+
var map = GetMapping(typeof(T));
905+
if (map.HasCompositePK)
906+
{
907+
IDictionary<string, object> compositePK = pk as Dictionary<string, object>;
908+
if (compositePK == null)
909+
{
910+
throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
911+
}
912+
var cpk = map.CompositePK;
913+
if (compositePK.Keys.Intersect(cpk.Select(p => p.Name)).Count() < cpk.Length)
914+
{
915+
throw new NotSupportedException("Cannot find in " + map.TableName + ": CompositePK mismatch. Make sure PK names are valid.");
916+
}
917+
return Query<T>(map.GetByPrimaryKeySql, compositePK.Values.ToArray()).FirstOrDefault();
918+
}
919+
else
920+
{
921+
return Query<T>(map.GetByPrimaryKeySql, pk).FirstOrDefault();
922+
}
872923
}
873924

874925
/// <summary>
@@ -897,7 +948,7 @@ public T FindWithQuery<T>(string query, params object[] args) where T : class
897948
/// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
898949
/// </summary>
899950
/// <param name="pk">
900-
/// The primary key.
951+
/// The primary key. Needs to be Dictionary<string, object> if table has composite PK.
901952
/// </param>
902953
/// <param name="map">
903954
/// The TableMapping used to identify the object type.
@@ -909,7 +960,24 @@ public T FindWithQuery<T>(string query, params object[] args) where T : class
909960
[PublicAPI]
910961
public object Find(object pk, TableMapping map)
911962
{
912-
return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault();
963+
if (map.HasCompositePK)
964+
{
965+
IDictionary<string, object> compositePK = pk as Dictionary<string, object>;
966+
if (compositePK == null)
967+
{
968+
throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
969+
}
970+
var cpk = map.CompositePK;
971+
if (compositePK.Keys.Intersect(cpk.Select(p => p.Name)).Count() < cpk.Length)
972+
{
973+
throw new NotSupportedException("Cannot find in " + map.TableName + ": CompositePK mismatch. Make sure PK names are valid.");
974+
}
975+
return Query(map, map.GetByPrimaryKeySql, compositePK.Values.ToArray()).FirstOrDefault();
976+
}
977+
else
978+
{
979+
return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault();
980+
}
913981
}
914982

915983
/// <summary>
@@ -1488,10 +1556,23 @@ public int Insert(object obj, string extra, Type objType)
14881556
}
14891557

14901558
var map = GetMapping(objType);
1559+
TableMapping.Column pk = null;
14911560

1492-
if (map.PK != null && map.PK.IsAutoGuid)
1561+
if (map.HasCompositePK)
14931562
{
1494-
var prop = objType.GetRuntimeProperty(map.PK.PropertyName);
1563+
pk = map.CompositePK.FirstOrDefault(p => p.IsAutoGuid);
1564+
}
1565+
else
1566+
{
1567+
if (map.PK != null && map.PK.IsAutoGuid)
1568+
{
1569+
pk = map.PK;
1570+
}
1571+
}
1572+
1573+
if (pk != null)
1574+
{
1575+
var prop = objType.GetRuntimeProperty(pk.PropertyName);
14951576
if (prop != null)
14961577
{
14971578
if (prop.GetValue(obj, null).Equals(Guid.Empty))
@@ -1596,29 +1677,57 @@ public int Update(object obj, Type objType)
15961677
}
15971678

15981679
var map = GetMapping(objType);
1680+
string q = null;
1681+
object[] ps = null;
1682+
1683+
if (map.HasCompositePK)
1684+
{
1685+
var compositePK = map.CompositePK;
1686+
var cols = from p in map.Columns
1687+
where !compositePK.Any(pk => pk == p)
1688+
select p;
1689+
1690+
var pslist = (from c in cols
1691+
select c.GetValue(obj)).ToList();
1692+
1693+
pslist.AddRange(compositePK.Select(pk => pk.GetValue(obj)));
15991694

1600-
var pk = map.PK;
1695+
q = string.Format("update \"{0}\" set {1} where {2}", map.TableName,
1696+
string.Join(",", (from c in cols
1697+
select "\"" + c.Name + "\" = ? ").ToArray()), string.Join(" and ", compositePK.Select(pk => "\"" + pk.Name + "\" = ? ")));
16011698

1602-
if (pk == null)
1699+
ps = pslist.ToArray();
1700+
}
1701+
else
16031702
{
1604-
throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK");
1703+
var pk = map.PK;
1704+
1705+
if (pk == null)
1706+
{
1707+
throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK");
1708+
}
1709+
1710+
var cols = from p in map.Columns
1711+
where p != pk
1712+
select p;
1713+
1714+
var vals = from c in cols
1715+
select c.GetValue(obj);
1716+
var pslist = new List<object>(vals)
1717+
{
1718+
pk.GetValue(obj)
1719+
};
1720+
1721+
q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName,
1722+
string.Join(",", (from c in cols
1723+
select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name);
1724+
1725+
ps = pslist.ToArray();
16051726
}
16061727

1607-
var cols = from p in map.Columns
1608-
where p != pk
1609-
select p;
1610-
var vals = from c in cols
1611-
select c.GetValue(obj);
1612-
var ps = new List<object>(vals)
1613-
{
1614-
pk.GetValue(obj)
1615-
};
1616-
var q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName,
1617-
string.Join(",", (from c in cols
1618-
select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name);
16191728
try
16201729
{
1621-
rowsAffected = Execute(q, ps.ToArray());
1730+
rowsAffected = Execute(q, ps);
16221731
}
16231732
catch (SQLiteException ex)
16241733
{
@@ -1680,20 +1789,35 @@ public int UpdateAll(IEnumerable objects, bool runInTransaction = true)
16801789
public int Delete(object objectToDelete)
16811790
{
16821791
var map = GetMapping(objectToDelete.GetType());
1683-
var pk = map.PK;
1684-
if (pk == null)
1792+
string q = null;
1793+
object[] ps = null;
1794+
1795+
if (map.HasCompositePK)
1796+
{
1797+
var compositePK = map.CompositePK;
1798+
q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", compositePK.Select(pk => "\"" + pk.Name + "\" = ? ")));
1799+
ps = (from pk in compositePK
1800+
select pk.GetValue(objectToDelete)).ToArray();
1801+
}
1802+
else
16851803
{
1686-
throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
1804+
var pk = map.PK;
1805+
if (pk == null)
1806+
{
1807+
throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
1808+
}
1809+
q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
1810+
ps = new object[] { pk.GetValue(objectToDelete) };
16871811
}
1688-
var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
1689-
return Execute(q, pk.GetValue(objectToDelete));
1812+
1813+
return Execute(q, ps);
16901814
}
16911815

16921816
/// <summary>
16931817
/// Deletes the object with the specified primary key.
16941818
/// </summary>
16951819
/// <param name="primaryKey">
1696-
/// The primary key of the object to delete.
1820+
/// The primary key of the object to delete. Needs to be Dictionary<string, object> if table has composite PK.
16971821
/// </param>
16981822
/// <returns>
16991823
/// The number of objects deleted.
@@ -1705,13 +1829,34 @@ public int Delete(object objectToDelete)
17051829
public int Delete<T>(object primaryKey)
17061830
{
17071831
var map = GetMapping(typeof (T));
1708-
var pk = map.PK;
1709-
if (pk == null)
1832+
1833+
if (map.HasCompositePK)
17101834
{
1711-
throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
1835+
var cpk = map.CompositePK;
1836+
IDictionary<string, object> compositePK = primaryKey as Dictionary<string, object>;
1837+
if (compositePK == null)
1838+
{
1839+
throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
1840+
}
1841+
if (compositePK.Keys.Intersect(cpk.Select(p => p.Name)).Count() < cpk.Length)
1842+
{
1843+
throw new NotSupportedException("Cannot delete " + map.TableName + ": CompositePK mismatch. Make sure PK names are valid.");
1844+
}
1845+
var q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", compositePK.Keys.Select(pk => "\"" + pk + "\" = ? ")));
1846+
var ps = (from pk in compositePK.Values
1847+
select pk).ToArray();
1848+
return Execute(q, ps);
1849+
}
1850+
else
1851+
{
1852+
var pk = map.PK;
1853+
if (pk == null)
1854+
{
1855+
throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
1856+
}
1857+
var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
1858+
return Execute(q, primaryKey);
17121859
}
1713-
var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
1714-
return Execute(q, primaryKey);
17151860
}
17161861

17171862
/// <summary>

0 commit comments

Comments
 (0)