Name table in SQLite-net - c#

I'm building a Windows 8 C#/XAML app that uses SQLite as a storage database, and I'm trying to create multiple tables using the SQLite-net syntax.
From what I've researched so far, a table is created based off of a class. First, I've created an "Account" class via:
public class Account
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
public string Type { get; set;}
}
And then create a table and enter in initial data later on in the code via:
private static readonly string _dbPath =
Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "data.sqlite");
using (var db = new SQLite.SQLiteConnection(_dbPath))
{
db.CreateTable<Account>();
db.RunInTransaction(() =>
db.Insert(new Account()
{
Name = "MyCheckingAccount",
Type = "Checking",
})
);
}
I want to create multiple account tables, but the db.CreateTable<Account>() syntax just creates a table and the data is inserted into the columns with db.Insert(). I don't see where to enter the name of the table itself.
How do I create multiple tables, i.e. one named "BusinessAccounts" and another "PersonalAccounts" based off of the Account class?
Is there a way to do this with SQLite-net? Or do I need to write out the SQLite command explicitly somehow?

This answer seems to be outdated, in SQLite-net you can now use an attribute on a class to ovverride the table name, for example:
[SQLite.Table("table_customers")]
public class Customer
{
[MaxLength(3)]
public string code { get; set; }
[MaxLength(255)]
public string name { get; set; }
}
So it will create/update that table.

Sqlite-Net uses the class name to create the table, as well as to update the data. To do what you want, you'll need to create separate classes. One way to get around repeating common fields is to use inheritance:
public class Account
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
public string Type { get; set;}
}
public class BusinessAccounts : Account { }
public class PersonalAccounts : Account { }
To create tables:
db.CreateTable<BusinessAccounts>();
db.CreateTable<PersonalAccounts>();
To insert data:
db.Insert(new BusinessAccounts() {...});
db.Insert(new PersonalAccounts() {...});
Just a note that the above code is untested. You'll want to make sure that the tables are created correctly (e.g. with the proper primary key and autoincrement field).

Just to add that with SQLite-net, you can change the attribute of the class by implementing an initialisation overload and setting the SQLite.TableAttribute like this:
[Table("Account")]
public class Account
{
[PrimaryKey]
[AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public Account(string name = null)
{
if (!string.IsNullOrEmpty(name))
{
TableAttribute attrname = TypeDescriptor.GetAttributes(this)(0);
attrname.Name = name;
}
}
}
The name defaults to account, but if you initialize the class with a string, it sets the attribute thus you can then create a table with that name.

Related

How do I use a database view as a model in C# MVC

I am using a DATABASE-FIRST approach in C# MVC and all of my generated models are in a sub-folder Models>Generated. One of these models is called SourceSystem which contains the field definitions of the table and the related table entities.
public partial class SourceSystem
{
[Key]
[Column("ID")]
public int Id { get; set; }
[StringLength(100)]
public string SystemName { get; set; } = null!;
[Column("ROWSTAMP")]
public byte[] Rowstamp { get; set; } = null!;
[StringLength(100)]
public string? LinkedServerName { get; set; }
[StringLength(100)]
public string? DatabaseName { get; set; }
[StringLength(20)]
public string? DefaultSourceSchema { get; set; }
[StringLength(20)]
public string? DefaultTargetSchema { get; set; }
[InverseProperty("SourceSystem")]
public virtual ICollection<Domain> Domains { get; } = new List<Domain>();
[InverseProperty("SourceSystem")]
public virtual ICollection<EventProfile> EventProfiles { get; } = new List<EventProfile>();
}
As part of the application there are also a number of synonyms created which will link back to the source databases (based on the Linked Server Name, Database Name and Default Source Schema. This list of synonymns does not live in MY database but are in the msdb database so I have a view that enables me to generate a dataset of the synonyms and associate them back to the SourceSystem table. For note, DelimitedSpit8K takes a string and spits it up, into a record set. Because synonyms use a 2/3/4 part naming convention, I have to reverse them as I need to definately have the last two parts (schema and object name) but the first two (linked server name and database) are optional. Note also that the schema for the view is pow, not the default dbo.
CREATE VIEW pow.Synonymn AS
SELECT
SYN.object_id AS [SystemID]
,SYN.name AS [Synonym]
,SCH.name AS [SourceSchema]
,SYN.base_object_name
,REPLACE(REPLACE(REVERSE(object_name.Item),'[',''),']','') AS [object_name]
,REPLACE(REPLACE(REVERSE(object_schema.Item),'[',''),']','') AS [object_schema]
,REPLACE(REPLACE(REVERSE(object_db.Item),'[',''),']','') AS [object_db]
,REPLACE(REPLACE(REVERSE(object_linked_server.Item),'[',''),']','') AS [object_linked_server]
,SS.ID AS [SourceSystem_Id]
FROM
sys.synonyms AS SYN
JOIN
sys.schemas AS SCH ON SCH.schema_id = SYN.schema_id
JOIN
pow.SourceSystem AS SS ON SS.DefaultTargetSchema = SCH.name
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_name]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_schema]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_db]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_linked_server]
WHERE
object_name.ItemNumber =1
AND
object_schema.ItemNumber = 2
AND
object_db.ItemNumber = 3
AND
(
object_linked_server.ItemNumber IS NULL
OR
object_linked_server.ItemNumber = 4
)
I have manually added a model to my models folder (not Models>Generated):
using Overwatch_API.Models.Generated;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
namespace Overwatch_API.Models;
//[Table("Synonym", Schema = "pow")]
public partial class Synonym
{
[Key]
[Column("SystemID")]
public int SystemID { get; set; }
[Column("Synonym")]
public string SynonymName { get; set; }
[Column("SourceSystemTargetSchema")]
public string SourceSchema { get; set; } = null!;
[Column("SourceSystemId")]
public int SourceSystem_Id { get; set; }
public string base_object_name { get; set; }
public string object_name { get; set; }
public string object_schema { get; set; }
public string object_db { get; set; }
public string object_linked_server { get; set; }
[ForeignKey("SourceSystemId")]
[InverseProperty("Synonyms")]
//[JsonIgnore]
public virtual SourceSystem SourceSystem { get; set; } = null!;
}
and I have modified the database context:
using Overwatch_API.Models;
...
public virtual DbSet<Synonym> Synonyms { get; set; }
...
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView(nameof(Synonym))
.HasKey(t => t.SystemID);
});
and I have updated the ViewModel for the SourceSystemVM:
using Overwatch_API.Models;
...
public class SourceSystemVM
{
public int Id { get; set; }
[DisplayName("System Name")]
public string SystemName { get; set; }
[DisplayName("Linked Server")]
public string? LinkedServerName { get; set; }
[DisplayName("Linked Database")]
public string? DatabaseName { get; set; }
[DisplayName("Source Schema")]
public string? DefaultSourceSchema { get; set; }
[DisplayName("Target Schema")]
public string? DefaultTargetSchema { get; set; }
public ICollection<DomainVM> domains { get; set; }
public ICollection<Synonym> synonyms { get; set; }
public SourceSystemVM(SourceSystem ss)
{
Id = ss.Id;
SystemName = ss.SystemName;
LinkedServerName = ss.LinkedServerName;
DatabaseName = ss.DatabaseName;
DefaultSourceSchema = ss.DefaultSourceSchema;
DefaultTargetSchema = ss.DefaultTargetSchema;
domains = new List<DomainVM>();
synonyms = new List<Synonym>();
}
}
When I start the server and run Swagger, and choose the api endpoint
https://localhost:7001/api/SourceSystems
I get the following error message:
InvalidOperationException: The [InverseProperty] attribute on property 'Synonym.SourceSystem' is not valid. The property 'Synonyms' is not a valid navigation on the related type 'SourceSystem'. Ensure that the property exists and is a valid reference or collection navigation.
I am not sure what part of the configuration I have got wrong. I don't want to touch the SourceSytem.cs in the Models>Generated folder as it will get overwritten if the DF models are re-generated. Do I need to create a new partial class in the models folder to extend the generated model, and if so, what would that look like and how do I disambiguate between the Models>SourceSystem.cs and the Models>Generated>SourceSystem.cs when referencing it in the VM and DTOs. Or am I missing an entire concept somewhere?
For context, the Synonym collection is used for view (read) only. The functionality to add a new synonym will have to be managed through a call to a SQL stored procedure, but I need to understand what I have screwed up here first :)
UPDATE
I have added the partial class to the Models folder:
using Overwatch_API.Models;
using Overwatch_API.Models.Generated;
using System.ComponentModel.DataAnnotations.Schema;
namespace Overwatch_API.Models.Generated
{
public partial class SourceSystem
{
[InverseProperty("SourceSystem")]
public virtual ICollection<Synonym> Synonyms { get; } = new List<Synonym>();
}
}
and updated the context:
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView("Synonym", "pow");
});
and now the API doesn't throw an error message but the synonym array is empty and I'm not sure why: Whether the relationship between the Synonym and SourceSystems is not defined correctly or if the view is not being found/executed to return the details.
UPDATE 2: As per the question from Alex:
I have set up the following in the dbContext:
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView("Synonym", "pow");
entity.HasOne(d => d.SourceSystem)
.WithMany(p => p.Synonyms)
.HasForeignKey(x => x.SourceSystemId);
});
The Profiler is showing the following queries being run. For the Synonymns API [HttpGet]
SELECT [s].[SystemID], [s].[SourceSchema], [s].[SourceSystem_ID], [s].[Synonym], [s].[base_object_name], [s].[object_db], [s].[object_linked_server], [s].[object_name], [s].[object_schema]
FROM [pow].[Synonym] AS [s]
For the SourceSystem API [HttpGet]
SELECT [s].[ID], [s].[DatabaseName], [s].[DefaultSourceSchema], [s].[DefaultTargetSchema], [s].[LinkedServerName], [s].[ROWSTAMP], [s].[SystemName], [d].[ID], [d].[DomainName], [d].[ROWSTAMP], [d].[SourceSystem_ID]
FROM [pow].[SourceSystem] AS [s]
LEFT JOIN [pow].[Domain] AS [d] ON [s].[ID] = [d].[SourceSystem_ID]
ORDER BY [s].[ID]
Domain is another collection within SourceSystem but it unrelated to the Synonyms. A single join here would create a cartesion collection with both the Domains and Synonymns being repeated. Could this be the problem? The data fetch would either need to do an N+1 query or bring back the cartesian collection and then filter distinct. If so, how do I get around the problem. Is there a way to lazy-load the synonymns in MVC. I could just load them all in the front end (React/Next) and apply a filter in JS to only show the ones connected with the selected SourceSystem but this is spreading the logic about throughout the application stack.
OK. So I worked it out and it is non-trivial so hoping that this helps someone else.
SourceSystems contains multiple ICollections (Domains and Synonyms amongst them) however these create cyclic dependencies so the get uses a SourceSystemViewModel which uses a DomainViewModel which does not contain the cyclic reference back to the SourceSystem. I had to add the Synonyms to the SourceSystemViewModel, but as the Synonyms also contain the cyclic reference I have to create a SynonymViewModel as well.
and then in the SourceSystemsController, when executing the _context.SourceSystems you have to tell it to .Include("ChildCollection") which I had not done.
var ssList = _context.SourceSystems
.Include("Domains")
.Include("Synonyms")
.ToList();
Once this is included you then have to specifically iterate through the ssList, and for each SourceSystemDTO, iterate through both the Domains list and the Synonyms list and map the list items into the relevant arrays.
[HttpGet]
[ResponseType(typeof(List<SourceSystemVM>))]
public async Task<IEnumerable<SourceSystemVM>> GetSourceSystems()
{
List<SourceSystem> ss = _context.SourceSystems.ToList();
IEnumerable<SourceSystemVM> ssDTO = from s in ss select new SourceSystemVM(s);
var ssList = _context.SourceSystems
.Include("Domains")
.Include("Synonyms")
.ToList();
var ssDTOList = new List<SourceSystemVM>();
ssList.ForEach(ss =>
{
var ssDTO = new SourceSystemVM(ss);
foreach (var domain in ss.Domains)
{
var domainDTO = new DomainVM(domain);
ssDTO.domains.Add(domainDTO);
}
foreach (var synonym in ss.Synonyms)
{
var synonymDTO = new SynonymVM(synonym);
ssDTO.synonyms.Add(synonymDTO);
}
ssDTOList.Add(ssDTO);
});
return ssDTOList;
}

Quick way of mapping a stored procedure in C# EF (using Database.SqlQuery)

I have the following in a using statement:
var result = await db.Database.SqlQuery<ModelLookup>("EXEC prGetModel", new {ManufacturerId = manufacturerId}).ToListAsync();
And a ModelLookup class :
public class ModelLookup
{
public int ManufacturerId { get; set; }
public int ModelId { get; set; }
public string ModelDesc { get; set; }
}
Some of the DB columns have a different name.
Is there a quick way (or data annotation) that I can use to take a column from the database and map to the desired field?
(without having to add the procedure to a DbContext)
Thanks,

Getting only a part of a specific Ravendb document

Is it possible to load only a part of a Ravendb document.
I want to fetch a document by id, but only some fields should be fetched.
I know I can use session.Query with a Selectcall, but then I can't query on the id of the document so I have to use session.Loadinstead, but this fetches the whole document.
Do I need to create an index for this?
You can use something called Results Transformers to achieve this.
Say you have an entity "Customer"
public class Customer
{
public string Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
}
and you want to load only the Id and the Name property, you define a Results Transformer:
public class CustomerNameTransformer : AbstractTransformerCreationTask<Customer>
{
public CustomerNameTransformer()
{
TransformResults = results => from customer in results
select new CustomerNameViewModel
{
Id = customer.Id,
Name = customer.Name
};
}
}
and your "view model":
public class CustomerNameViewModel
{
public string Id { get; set; }
public string Name { get; set; }
}
With this, you can access the Customer entity as a "Customer Name ViewModel" in several ways:
//Load and transform by one id
CustomerNameViewModel viewModel = session.Load<CustomerNameTransformer, CustomerNameViewModel>("customers/1");
//Load and transform by several id's
CustomerNameViewModel[] viewModels = session.Load<CustomerNameTransformer, CustomerNameViewModel>(new[]{ "customers/1", "customers/2"});
//Query and transform
List<CustomerNameViewModel> viewModels = session.Query<Customer>()
.TransformWith<CustomerNameTransformer, CustomerNameViewModel>()
.ToList();
Results Transformers are executed server side before the data is returned to the client. They are created by the same IndexCreation task that creates the index definitions on the server.
You can read more about Results Transformers in the documentation:
http://ravendb.net/docs/article-page/2.5/csharp/client-api/querying/results-transformation/result-transformers
Hope this helps!

SQLite-Net Extensions not inserting in cascade in some cases

I am using SQLite-Net PCL together with SQLite-Net extensions for the development of an application using Xamarin.
In my model I have an entity (let's call it A) which is connected to other four entities through one-to-many relationships (that are represented as lists in the model). In order to populate the tables recursively when inserting an object of A in the database I have defined the relations to use Cascade on both read, insert and delete.
In order to test if I did everything correctly I created an object of type A and populated the including lists, and finally I have inserted it into the database. The strange thing is that, for 2 of the 4 including lists the insertion went well, and all the connected objects are inserted. For other 2, instead, only the first object of the list is inserted in the database. To be clear, I am checking the database content directly with a db browser.
The following is an example of one of the objects for which only the first element of the list is inserted.
public class Username : Entity
{
public string Name
{
get;
set;
}
[ForeignKey(typeof(A))]
public int AId
{
get;
set;
}
public Username(string username)
{
Name = username;
}
}
This is instead one of the objects for which the insertion is correct.
public class AnAddress: Entity
{
public string Address
{
get;
set;
}
public AddressType Type
{
get;
set;
}
[ForeignKey(typeof(A))]
public int AId
{
get;
set;
}
}
To be clear, the base object Entity contains the definition of the primary key:
public abstract class Entity
{
[PrimaryKey, AutoIncrement]
public int Id
{
get;
set;
}
public Entity()
{
Id = -1;
}
}
And this is the way the relationships are defined:
public class A : Entity
{
public string Name
{
get;
set;
}
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<AnAddress> Addresses
{
get;
set;
}
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Username> Usernames
{
get;
set;
}
}
I then create an A object by initialising it with two lists (List and List) in the same identical way.
I finally insert the object in the database with
c.InsertWithChildren(entity, recursive: true));
Where entity is of type A and c is the connection object.
Do you have any clue about the motivation of this strange behaviour?

How do I update the value of a complex object within another object?

Given the following two classes, which are representing tables in a database, I want to set the statustype of MyClass to one of the two predefined values that are in the StatusType DB.
public class MyClass
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
public virtual StatusType StatusType { get; set; }
}
public class StatusType
{
public int Id { get; set; }
public virtual ICollection<MyClass> MyClasses{ get; set; }
DataType(DataType.MultilineText)]
public string Description { get; set; }
}
There is a third table (MyClass_StatusType) that is not represented by an entity that acts as a many to one intermediary.
My code:
MyClass mc = new MyClass();
mc.Description = "Description";
mc.StatusType.Id = 3;
db.MyClasses.Add(mc);
var id = db.SaveChanges();
There is a record in my StatusType with an ID of 3. When I run this code I get a null reference exception on the StatusType.
What is the correct way to set this value in MyClass?
You get the exception because of the access to StatusType in this line
mc.StatusType.Id = 3;
mc is a new object and has no StatusType defined.
1) The best way to correct this is to use the FK field StatusTypeId
mc.StatusTypeId = 3;
2) But if you can't use this field the other solution is the following:
You should instead of setting the Id (of an non existing StatusType) set it to the existing type. This makes a extra roundtrip to the DB to get the StatusType object.
var statusType = MyMethodToGetExistingStatusType(3);
mc.StatusType = statusType;
I don't know how you can get your instance of your Type but in the method MyMethodToGetExistingStatusType() you can handle this.
I hope this helps you?
You are missing your FK in the MyClass class. If you add
Public int StatusTypeId {get;set;}
This maps a FK for your entity. Currently you only have a navigation property.
Then rather than doing
mc.StatusType.Id = 3
You just do
mc.StatusTypeId = 3
This will save you from having to select from the StatusType table. Meaning a lot less code and one less trip to your SQL server.

Categories

Resources