I am using sqlite-net for a Unity project. My current settings include relying on IL2CPP compiler instead of Mono when building the binary. This is something I cannot change. Apparently IL2CPP has a problem with ConstructorArguments (for iOS/Android builds, not in Unity Editor):
NotSupportedException: ./External/il2cpp/il2cpp/libil2cpp/icalls/mscorlib/System.Reflection/CustomAttributeData.cpp(17) : Unsupported internal call for IL2CPP:CustomAttributeData::ResolveArgumentsInternal - "IL2CPP does not support inspection of attribute constructor arguments at run time."
at System.Reflection.CustomAttributeData.ResolveArguments () [0x00000] in <00000000000000000000000000000000>:0
at System.Reflection.CustomAttributeData.get_ConstructorArguments () [0x00000] in <00000000000000000000000000000000>:0
at SQLite.Orm.InflateAttribute (System.Reflection.CustomAttributeData x) [0x00000] in <00000000000000000000000000000000>:0
at SQLite.TableMapping+<>c.<.ctor>b__31_1 (System.Reflection.CustomAttributeData x) [0x00000] in <00000000000000000000000000000000>:0
at System.Func`2[T,TResult].Invoke (T arg) [0x00000] in <00000000000000000000000000000000>:0
at System.Linq.Enumerable+WhereSelectEnumerableIterator`2[TSource,TResult].MoveNext () [0x00000] in <00000000000000000000000000000000>:0
at System.Linq.Enumerable.FirstOrDefault[TSource] (System.Collections.Generic.IEnumerable`1[T] source) [0x00000] in <00000000000000000000000000000000>:0
at SQLite.TableMapping..ctor (System.Type type, SQLite.CreateFlags createFlags) [0x00000] in <00000000000000000000000000000000>:0
at SQLite.SQLiteConnection.GetMapping (System.Type type, SQLite.CreateFlags createFlags) [0x00000] in <00000000000000000000000000000000>:0
The problem is anywhere ConstructorArguments property is used.
For now, I have fixed this problem by adding a #define and using different code (older code taken from this repo, not really sure which version tbh) whenever ConstructorArguments is used. e.g.:
#if UNITY_EDITOR
var typeInfo = type.GetTypeInfo();
var tableAttr =
typeInfo.CustomAttributes
.Where(x => x.AttributeType == typeof(TableAttribute))
.Select(x => (TableAttribute)Orm.InflateAttribute(x))
.FirstOrDefault();
#elif NETFX_CORE
var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions
.GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true);
#else
var tableAttr = (TableAttribute)type.GetCustomAttributes(typeof(TableAttribute), true).FirstOrDefault();
#endif
or
#if UNITY_EDITOR
Name = (colAttr != null && colAttr.ConstructorArguments.Count > 0) ?
colAttr.ConstructorArguments[0].Value?.ToString() :
prop.Name;
#else
Name = colAttr == null ? prop.Name : colAttr.Name;
#endif
Maybe there's a better way to fix this problem than that, but I wanted to know if this issue could be addressed officially?
I have the same problem when testing in IOS and unfortunately your fix does not work. Especially because the field colAttr.Name does not exsist.
Are there any other approaches or ideas for a fix? I really need it urgently and I'm having a hard time debugging it.
For me the error occurs when I want to create tables. This happens first at startup and I don't see what could happen next.
Edit (08.06.2020):
Ok, I found now a way to solve my problem.
colAttrelse you will a recieve an error like I said. It should look like this ```
Name = (colAttr != null && colAttr.ConstructorArguments.Count > 0) ?colAttr.ConstructorArguments[0].Value?.ToString() : prop.Name;
var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault();
_prop = prop;
Name = colAttr == null ? prop.Name : colAttr.Name;
````
@time2smile sorry I just noticed your reply, yeah you are right I do have that line you added, but for the sake of asking I forgot to post the complete solution. Thanks for pointing out for others that might stumble upon this problem!
Is there a related PR for this fix? I too an unable to create tables on IOS (everything works great in editor on Mac). Or at least could @time2smile please post the entire solution in context here? Thank you.
I just ran into this issue.
A bit confusing, because the IL2CPP docs say that the System.Reflection namespace is supported, only System.Reflection.Emit is not. CustomAttributeData is in System.Reflection. But perhaps the ResolveArguments() method (which is internal), called by get_ConstructorArguments(), relies on Emit? I'm not sure how to tell.
I'm still trying to apply the suggested changes above. A Pull Request or a fork or even a gist with a patch would be really helpful.
--
diff --git a/Assets/Packages/SQLite4Unity3d/SQLite.cs b/Assets/Packages/SQLite4Unity3d/SQLite.cs
old mode 100755
new mode 100644
index cbc8d22..b92f4da
--- a/Assets/Packages/SQLite4Unity3d/SQLite.cs
+++ b/Assets/Packages/SQLite4Unity3d/SQLite.cs
@@ -2394,11 +2394,15 @@ namespace SQLite
CreateFlags = createFlags;
var typeInfo = type.GetTypeInfo ();
+#if UNITY_EDITOR
var tableAttr =
typeInfo.CustomAttributes
.Where (x => x.AttributeType == typeof (TableAttribute))
.Select (x => (TableAttribute)Orm.InflateAttribute (x))
.FirstOrDefault ();
+#else
+ var tableAttr = (TableAttribute)type.GetCustomAttributes(typeof(TableAttribute), true).FirstOrDefault();
+#endif
TableName = (tableAttr != null && !string.IsNullOrEmpty (tableAttr.Name)) ? tableAttr.Name : MappedType.Name;
WithoutRowId = tableAttr != null ? tableAttr.WithoutRowId : false;
@@ -2517,12 +2521,17 @@ namespace SQLite
public Column (PropertyInfo prop, CreateFlags createFlags = CreateFlags.None)
{
- var colAttr = prop.CustomAttributes.FirstOrDefault (x => x.AttributeType == typeof (ColumnAttribute));
_prop = prop;
+#if UNITY_EDITOR
+ var colAttr = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(ColumnAttribute));
Name = (colAttr != null && colAttr.ConstructorArguments.Count > 0) ?
colAttr.ConstructorArguments[0].Value?.ToString () :
prop.Name;
+#else
+ var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault();
+ Name = colAttr == null ? prop.Name : colAttr.Name;
+#endif
//If this type is Nullable<T> then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead
ColumnType = Nullable.GetUnderlyingType (prop.PropertyType) ?? prop.PropertyType;
Collation = Orm.Collation (prop);
@@ -2733,6 +2742,7 @@ namespace SQLite
return GetProperty (t.BaseType.GetTypeInfo (), name);
}
+#if UNITY_EDITOR
public static object InflateAttribute (CustomAttributeData x)
{
var atype = x.AttributeType;
@@ -2768,6 +2778,27 @@ namespace SQLite
}
return null;
}
+#else
+ public static IEnumerable<IndexedAttribute> GetIndices(MemberInfo p)
+ {
+ var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true);
+ return attrs.Cast<IndexedAttribute>();
+ }
+
+ public static int? MaxStringLength(PropertyInfo p)
+ {
+ var attrs = p.GetCustomAttributes (typeof(MaxLengthAttribute), true);
+#if !NETFX_CORE
+ if (attrs.Length > 0)
+ return ((MaxLengthAttribute)attrs [0]).Value;
+#else
+ if (attrs.Count() > 0)
+ return ((MaxLengthAttribute)attrs.First()).Value;
+#endif
+
+ return null;
+ }
+#endif
public static bool IsMarkedNotNull (MemberInfo p)
{
Is there a related PR for this fix? I too an unable to create tables on IOS (everything works great in editor on Mac). Or at least could @time2smile please post the entire solution in context here? Thank you.
I implemented what gtino offered plus the small addition from my post.
But what helped me most was the omission of the attributes in my mappings.
In the following I have attached your example data type and in addition I have attached my SQliteDBManager, with which I implement all my communication to the database.
The IMapping and IFileMapping are the interfaces of my data objects.
The important thing is that the primary key is not set by the attribute but by the parameter when creating the table.
The [SerielizedField] attribute does not cause any problems.
I hope that this helps.
[Serializable]
public class ColorData : IFileMapping, IEquatable<ColorData>
{
#region Properties
public int Id
{
get => id;
set => id = value;
}
public string Type
{
get => type;
set => type = value;
}
public string Name {
get => name;
set => name = value;
}
public string GetFolderPath()
{
return DataPath.ColorsDir;
}
#endregion
#region Serializeable Fields
[SerializeField]
private int id;
[SerializeField]
private string name;
[SerializeField]
private string type;
#endregion
#region Equality
public bool Equals(ColorData other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Id == other.Id && Name == other.Name && Type == other.Typel;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((ColorData) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (Id != null ? Id.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (Type != null ? Type.GetHashCode() : 0);
return hashCode;
}
}
#endregion
public override string ToString()
{
return
$"{nameof(Id)}: {Id} " +
$"{nameof(Name)}: {Name} " +
$"{nameof(Type)}: {Type} " ;
}
}
public class SQLiteDBManager
{
#region Open and Close Connection
/// <summary>
/// Returns an active <see cref="SQLiteConnection"/> to local SQLDataBase.
/// </summary>
/// <returns></returns>
public SQLiteConnection GetSQLiteConnection()
{
return new SQLiteConnection(Path.Combine(DataPath.ConfigDir, DataPath.DataBaseName));
}
/// <summary>
/// Close the active <see cref="SQLiteConnection"/>.
/// </summary>
public void CloseDB()
{
GetSQLiteConnection().Close();
}
#endregion
#region READ
/// <summary>
/// Creates Tables for all available <see cref="IMapping"/> - Classes if they are not available.
/// This is defined manually and need to be updated when a new class of type <see cref="IMapping"/> is added.
/// </summary>
public void CreateTables()
{
var db = GetSQLiteConnection();
db.BeginTransaction();
db.CreateTable<ColorData>(CreateFlags.ImplicitPK);
db.Commit();
db.Close();
Log(this, "Tables created if not existing.");
}
/// <summary>
/// Returns a <see cref="List{T}"/> of all available objects in the selected table.
/// You select a table by type.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public List<T> ReadAllFrom<T>() where T : IMapping, new()
{
var db = GetSQLiteConnection();
db.BeginTransaction();
var query = db.Table<T>();
List<T> result = query.ToList();
db.Commit();
db.Close();
return result;
}
/// <summary>
/// Returns on object of type <see cref="IFileMapping"/> with given Id.
/// </summary>
/// <param name="id"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetFileObjectById<T>(int id) where T : IFileMapping, new()
{
var db = GetSQLiteConnection();
db.BeginTransaction();
var result = db.Table<T>().First(obj => obj.Id == id);
db.Commit();
db.Close();
return result;
}
#endregion
#region WRITE
/// <summary>
/// Insert or replace the given dataObject in the selected table.
/// You select a table by type.
/// </summary>
/// <param name="data"></param>
/// <typeparam name="T"></typeparam>
public void InsertOrReplaceInto<T>(T data) where T : IMapping
{
var db = GetSQLiteConnection();
db.BeginTransaction();
db.InsertOrReplace(data);
db.Commit();
db.Close();
}
/// <summary>
/// Insert or Replace a list of <see cref="IEnumerable{T}"/> in the selected table.
/// You select a table by type.
/// </summary>
/// <param name="data"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public int InsertOrReplaceAllInto<T>(IEnumerable<T> data) where T : IMapping
{
var db = GetSQLiteConnection();
db.BeginTransaction();
var result = db.InsertAll(data, "OR REPLACE");
db.Commit();
db.Close();
return result;
}
#endregion
#region DELETE
/// <summary>
/// Delete the given dataObject in the selected table.
/// You select a table by type.
/// </summary>
/// <param name="data"></param>
/// <typeparam name="T"></typeparam>
public void Delete<T>(T data) where T : IMapping
{
var db = GetSQLiteConnection();
db.BeginTransaction();
db.Delete(data);
db.Commit();
db.Close();
Log(this, "Deleted one Object");
}
#endregion
}
I did not create a PR, no. Just the patch.
I am explicitly using sqlite-net-extensions, which relies on attributes.
Brent
On Jul 24, 2020, at 8:56 AM, Alex Mizuk notifications@github.com wrote:

Is there a related PR for this fix? I too an unable to create tables on IOS (everything works great in editor on Mac). Or at least could @time2smile please post the entire solution in context here? Thank you.I implemented what gtino offered plus the small addition from my post.
But what helped me most was the omission of the attributes in my mappings.
In the following I have attached your example data type and in addition I have attached my SQliteDBManager, with which I implement all my communication to the database.
The IMapping and IFileMapping are the interfaces of my data objects.
The important thing is that the primary key is not set by the attribute but by the parameter when creating the table.
The [SerielizedField] attribute does not cause any problems.
I hope that this helps.
`
[Serializable]
public class ColorData : IFileMapping, IEquatable
{region Properties
public int Id
{
get => id;
set => id = value;
}public string Type { get => type; set => type = value; } public string Name { get => name; set => name = value; } public string GetFolderPath() { return DataPath.ColorsDir; } #endregion #region Serializeable Fields [SerializeField] private int id; [SerializeField] private string name; [SerializeField] private string type; #endregion #region Equality public bool Equals(ColorData other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Id == other.Id && Name == other.Name && Type == other.Typel; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((ColorData) obj); } public override int GetHashCode() { unchecked { var hashCode = (Id != null ? Id.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (Type != null ? Type.GetHashCode() : 0); return hashCode; } } #endregion public override string ToString() { return $"{nameof(Id)}: {Id} " + $"{nameof(Name)}: {Name} " + $"{nameof(Type)}: {Type} " ; }}
public class SQLiteDBManager
{
#region Open and Close Connection/// <summary> /// Returns an active <see cref="SQLiteConnection"/> to local SQLDataBase. /// </summary> /// <returns></returns> public SQLiteConnection GetSQLiteConnection() { return new SQLiteConnection(Path.Combine(DataPath.ConfigDir, DataPath.DataBaseName)); } /// <summary> /// Close the active <see cref="SQLiteConnection"/>. /// </summary> public void CloseDB() { GetSQLiteConnection().Close(); } #endregion #region READ /// <summary> /// Creates Tables for all available <see cref="IMapping"/> - Classes if they are not available. /// This is defined manually and need to be updated when a new class of type <see cref="IMapping"/> is added. /// </summary> public void CreateTables() { var db = GetSQLiteConnection(); db.BeginTransaction(); db.CreateTable<ColorData>(CreateFlags.ImplicitPK); db.Commit(); db.Close(); Log(this, "Tables created if not existing."); } /// <summary> /// Returns a <see cref="List{T}"/> of all available objects in the selected table. /// You select a table by type. /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public List<T> ReadAllFrom<T>() where T : IMapping, new() { var db = GetSQLiteConnection(); db.BeginTransaction(); var query = db.Table<T>(); List<T> result = query.ToList(); db.Commit(); db.Close(); return result; } /// <summary> /// Returns on object of type <see cref="IFileMapping"/> with given Id. /// </summary> /// <param name="id"></param> /// <typeparam name="T"></typeparam> /// <returns></returns> public T GetFileObjectById<T>(int id) where T : IFileMapping, new() { var db = GetSQLiteConnection(); db.BeginTransaction(); var result = db.Table<T>().First(obj => obj.Id == id); db.Commit(); db.Close(); return result; } #endregion #region WRITE /// <summary> /// Insert or replace the given dataObject in the selected table. /// You select a table by type. /// </summary> /// <param name="data"></param> /// <typeparam name="T"></typeparam> public void InsertOrReplaceInto<T>(T data) where T : IMapping { var db = GetSQLiteConnection(); db.BeginTransaction(); db.InsertOrReplace(data); db.Commit(); db.Close(); } /// <summary> /// Insert or Replace a list of <see cref="IEnumerable{T}"/> in the selected table. /// You select a table by type. /// </summary> /// <param name="data"></param> /// <typeparam name="T"></typeparam> /// <returns></returns> public int InsertOrReplaceAllInto<T>(IEnumerable<T> data) where T : IMapping { var db = GetSQLiteConnection(); db.BeginTransaction(); var result = db.InsertAll(data, "OR REPLACE"); db.Commit(); db.Close(); return result; } #endregion #region DELETE /// <summary> /// Delete the given dataObject in the selected table. /// You select a table by type. /// </summary> /// <param name="data"></param> /// <typeparam name="T"></typeparam> public void Delete<T>(T data) where T : IMapping { var db = GetSQLiteConnection(); db.BeginTransaction(); db.Delete(data); db.Commit(); db.Close(); Log(this, "Deleted one Object"); } #endregion}
`—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or unsubscribe.
I encounter this issue today. After read the code and c# doc, it's curious that it always uses CustomAttributeData and constructor args to instance an Attribute object. You can simply replace it with a single GetCustomAttribute call and get the Attribute object directly. It functions as the original code and avoids access attribute constructor arguments.
Is the original code all for compatibility?