Dynamically generate a class in C# for SQLite - sqlite

I am using Xamarin.Forms .Net Standard 1.4 library and the SQLite nuget.
I create classes for my database tables/records as follow:
namespace MyNamespace
{
[Table("my_table")]
public class MyRecord
{
[PrimaryKey, AutoIncrement, Column("_id")]
public int ID { get; set; } = -1;
[Unique, NotNull, Column("GUID")]
public Guid GUID { get; set; } = Guid.NewGuid();
public string Field1 { get; set; }
public string Field1 { get; set; }
}
}
And I can create/insert the table/records with
{
SQLiteConnection conn = new SQLiteConnection(db_file);
conn.CreateTable<MyRecord>();
var record = new MyRecord {
Field1 = "test",
Field2 = "test"
};
conn.Insert(record);
conn.Dispose();
}
I find myself in the situation where one table does not have known columns at compile time, so I can't create a class to describe it. Is there an easy way to dynamically generate a class?
Suppose that during runtime I need to create a table with the following columns (all text) "NewField1", "NewField2". I realize I could generate the SQL command to data. But I lose the convenience of the wrapper from SQLite.
I would like to dynamically create a class and add the Properties as needed so I could still use CreateTable<> and Insert(), etc. Is this possible? Would it be efficient? Or am I better off just using a Dictionary and creating SQL commands.

Related

How to diagnose slow Entity Framework stored procedure call?

Problem: I'm calling a stored procedure through EF Core. When I run the stored procedure directly (via 'debug procedure'), it runs quickly, but it runs VERY slowly when called by EF's FromSqlRaw. So the problem appears to be when converting the returned data-table to a list of objects.
Setup:
Simple application with a list of blog posts. The stored procedure gets a hierarchical list of posts and associated users from a TPH table of posts, plus a table of users.
// Code is simplified, actually 8 parameters
SqlParameter depth_p = new SqlParameter("#depth", depth);
SqlParameter authorizedUserID_p = new SqlParameter("#authorizedUserID", authorizedUser.ID);
IQueryable<PostUser> query = postContext.PostUsers
.FromSqlRaw("Post.USP_ReadDebate #depth, #authorizedUserID",
parameters: new[] { depth_p, authorizedUserID_p });
List<PostUser> postUsers = query.ToList(); // This hangs.
26 columns are returned and converted by EF into the PostUser class.
PostUser holds 26 "ordinary" properties. No navigation properties, custom classes or any getters or setters that do any work.
public class PostUser
{
// Post fields
public Int32? ID { get; set; } // Primary Key
public String Text { get; set; }
public Guid OwnerID { get; set; }
public int? ParentID { get; set; } // nullable
public bool IsDisabled { get; set; }
public DateTime TimeCreated { get; set; }
public bool User_IsBanned { get; set; } = false;
// some others...
public PostUser() { }
}
Note: the stored procedure is very complex. It calls another stored procedure which fills a #spid table, then inserts the contents of that #SPID table into a table variable and returns that.
But again when debugged directly it returns quickly, so I think the problem is when EF Core is converting the returned data to the PostUser object.
Bottom Line: is there any way to get visibility into what EF Core is doing on the conversion to PostUser to find the problem?
Thank you!

Xamarin Linq insert/update Sqlite table record with generated column

For a table defined
public class Tbl
{
[PrimaryKey]
public int Id { get; set; }
public string Col2 { get; set; }
public string Gen { get; set; }
}
Where Gen is a Sqlite stored generated always column
on _conn.Insert(instanceOfTbl)
I get 'cannot UPDATE generated column'.
Fair enough, but other than directly executing an equivalent sql statement which ignores Gen is there a way of flagging Gen as generated (or other method) to allow the insert/update? I don't think the [Ignore] column attribute is appropriate as Gen needs to be accessible for reading. Removing the Set accessor does prevent the behaviour but results in the property always being null when read.
I'm using VS, Xamarin, Nuget sqlite-net-pcl 1.7.335 targeting Android, if relevant.
I've decided to adopt this approach for now. Credit to Jason for pointing me towards it.
Split Tbl into base class Tbl and derived class TblEx. Move the generated column property to TblEx. Hardwire TblEx to map to Sqlite table Tbl. Provide TblEx method to retrieve only properties from base class Tbl.
public class Tbl
{
[PrimaryKey]
public int Id { get; set; }
public string Col2 { get; set; }
}
[SQLite.Table("Tbl")]
public class TblEx : Tbl
{
public string Gen { get; set; }
public Tbl GetTbl() {Tbl t = new Tbl(); t.Id = Id; t.Col2 = Col2; return t;}
}
Update record with primary key id
TblEx tEx = _conn.Table<TblEx>().First(t => t.Id == id);
// Generated field is available as class is TblEx
tEx.Col2 = tEx.Gen + "something";
//Update now works as only base class Tbl without reference to generated column is passed
_conn.Update(tEx.GetTbl());
An alternative way of retrieving the base class uses JSON serialize/deserialize which has the advantage of not having to explicitly assign property values but may come with a performance cost or type compatibility limitations (I'm not sure that the latter's true as I don't know much about JSON).
using System.Text.Json;
[SQLite.Table("Tbl")]
public class TblEx : Tbl
{
public string Gen { get; set; }
public Tbl GetTbl()
{return JsonSerializer.Deserialize<Tbl>(JsonSerializer.Serialize(this));}
}

EF Core custom results from stored procedure

I'm using EF Core which I believe is also known as EF 7? Anyways, I have a stored procedure that returns custom results that will not identify with any specific table. How am I supposed to access those results and how should I call the sql command?
Normally we have to use .FromSql but that is only available on entities, eg. _context.User.FromSql(). I don't have an entity for it.
So I tried building a dbset/entity for the results, but again, there is no associated table, and there is also no "Key". How am I supposed to parse the data then of the custom results?
You can create a fake entity for the result of your stored procedure. You can set any property as the Key, even if in the results of the stored procedure the key values are not unique.
For example if you have a table like the following :
CREATE TABLE [dbo].[banana_hoard]
(
[id] INT NOT NULL PRIMARY KEY IDENTITY (1,1),
[owner] NVARCHAR(64) NOT NULL,
[bananas] BIGINT NOT NULL
)
You can have a query that does not return the row id like this :
public class Program
{
public static void Main(string[] args)
{
using (var db = new MonkeyDbContext())
{
var sp_results = db.search.FromSql(#"execute <YOUR_STORED_PROC>");
str_result = String.Join("\n", sp_results.Select(a => JsonConvert.SerializeObject(a) ));
Console.WriteLine("stored proc result :\n" + str_result);
}
}
}
public class MonkeyDbContext : DbContext
{
public DbSet<StoredProcRow> search { get; set; }
protected override void OnConfiguring (DbContextOptionsBuilder builder)
{
builder.UseSqlServer(#"Server=(localdb)\monkey_db;Database=monkey_db;Trusted_Connection=True;");
}
}
public class StoredProcRow
{
[Key]
public string Owner { get; set; }
public long Bananas { get; set; }
}

How do I name a many-many table for EF6 and do I need to add special mapping for this?

I am using EF 6 and trying to map a many to many relationship. So far I have:
public partial class ObjectiveDetail
{
public ObjectiveDetail()
{
this.SubTopics = new List<SubTopic>();
}
public int ObjectiveDetailId { get; set; }
public string Text { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic
{
public SubTopic()
{
this.ObjectiveDetails = new List<ObjectiveDetail>();
}
public int SubTopicId { get; set; }
public int Number { get; set; }
public string Name { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
}
Our DBA is going to write the code for the many to many table. Should this be as follows
with a table name of ObjectiveDetailSubTopic or something completely different ?
CREATE TABLE [dbo].[ObjectiveDetailSubTopic] (
[ObjectiveDetailId] INT NOT NULL,
[SubTopicId] INT NOT NULL
);
Can someone tell me if this is the correct way to create the table. Also do I have to
add some code to map the ObjectiveDetail and SubTopic classes to the new join class so
EF will know what to do?
Our DBA is going to write the code for the many to many table. Should
this be as follows with a table name of ObjectiveDetailSubTopic or
something completely different ?
As long as you follow the SQL Database table naming conventions, the table name can be anything. I usually name the join table like yours, by connecting the two table names.
To create the join table using sql, see below:
CREATE TABLE [dbo].[ObjectiveDetailSubTopic](
ObjectiveDetailSubTopicId int identity primary key,
ObjectiveDetailId INT NOT NULL,
SubTopicId INT NOT NULL,
foreign key(ObjectiveDetailId) references ObjectiveDetail(ObjectiveDetailId ),
foreign key(SubTopicId) references SubTopic(SubTopicId )
);
But you don't need to create the join table by your own, Entity Framework will create it for you. You just need to mapping the relationship with the Fluent API in your DbContext class like below:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ObjectiveDetail>().
HasMany(c => c.SubTopics).
WithMany(p => p.ObjectiveDetails).
Map(m =>
{
m.MapLeftKey("ObjectiveDetailId ");
m.MapRightKey("SubTopicId ");
m.ToTable("ObjectiveDetailSubTopic");
});
}

The member with identity does not exist in the metadata collection. Parameter name: identity

We are using EF Code First 4.3.1.
We are developing an ASP.NET Web Role referring to multiple class libraries.
There are two class libraries each containing classes and an individual DBcontext.
Lets say the Library1 has classes A and B.
DBcon1: DbSet and DbSet
Lets say the Library2 has classes C and D.
Class C{
[Key]
public int CId{ get; set;}
[Required]
public virtual A referencedA {get; set;}
}
DBcon2: DbSet<C> and DbSet<D>
When I try to use the DBcon2 as such:
using (var con = new DBcon2())
{
C vr = new C();
vr.CId= 1;
vr.referencedA = DBCon1.As.First();
con.Cs.Add(vr);
con.SaveChanges();
}
I get an exception as:
"The member with identity does not exist in the metadata collection.
Parameter name: identity"
Both DBCon1 and DBcon2 are using the sane SQL Server Database "SampleDB".
Please point me in the right direction.
I got this error and fixed it by not trying to set the navigation property in the related table, just set the foreign key id instead
eg
public class Student()
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public int CourseId { get; set; }
public virtual Course Course { get; set; }
}
public class Course()
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Main code:
var myCourse = new Course();
var myCourseId = 1;
var student = new Student() {
CourseId = myCourseId
// Course = myCourse <-- this would cause the error
}
Might not be your issue but maybe it will point you in the right direction and hopefully will help someone else.
The exception is a bit cryptic, but pretty clear if you realise that a context needs information about entities (metadata) to be able to write sql statements. Thus, your DBcon2 context has no clue where to find the primary key of an A, because it has no metadata about A.
You could however set an integer property A_Id (or the like), but then you'll have to write custom code to resolve it to an A.
Another option is to merge (parts of) the contexts, if possible.

Resources