Get name from TableAttribute Linq2DB - c#

I'm using Linq2DB, LinqToDB.Mapping to map the tables from database.
The class name has a Data Annotation named "Table" to specify the name of the table in de DB. For example:
[Table(#"EXAMPLE_ONLINE")]
public class Example
{
[Column(#"ID_EXAMPLE")]
public ulong IdExample { get; set; }
[Column(#"DESCRIPTION")]
public string Description { get; set; }
}
How can I get that table name from that Data annotation? Is tehere something like a
Example.GetTableName()
Thank you

You can obtain this information from EntityDescriptor:
var tableName = db.MappingSchema.GetEntityDescriptor(typeof(Example)).TableName;
Or you can create extension method:
public static GetTableName<T>(this MappingSchema mappingSchema)
{
return db.MappingSchema.GetEntityDescriptor(typeof(T)).TableName;
}
Also TableName can be optined from ITable<T> interface:
var tableName = db.GetTable<Example>().TableName;

Related

Entity Framework: Unable to add a record

I am new to C# development and I am trying to write something that can insert a record in a DB. I have a simple test, which I hoped would insert a record into the database when I run it.
Model:
namespace Users.Models;
using System.ComponentModel.DataAnnotations.Schema;
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
public string HashedPassword { get; set; }
public DateTime Created { get; set; }
}
Test:
namespace Database.Tests;
using Users.Models;
using Xunit;
public class ReferrerTests
{
[Fact]
public void TestInsert()
{
User user = new()
{
Name = "Bob",
EmailAddress = "bob#email.com",
HashedPassword = "hgfj",
};
using MyDbContext ctx = new();
ctx.Users.Add(user);
}
}
Database context:
namespace Database;
using Users.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using Npgsql;
[DbConfigurationType(typeof(Config))]
[SuppressDbSetInitialization]
public class MyDbContext: DbContext
{
public MyDbContext(): base(MakeConnString()) {}
private static string MakeConnString()
{
// Will be moving these to a common location
string OptEnv(string key, string default_) =>
Environment.GetEnvironmentVariable(key) ?? default_;
string Env(string key) =>
Environment.GetEnvironmentVariable(key) ?? throw new MissingFieldException(key);
NpgsqlConnectionStringBuilder builder = new()
{
Host = Env("PGHOST"),
Port = int.Parse(OptEnv("PGPORT", "5432")),
SslMode = Enum.Parse<SslMode>(OptEnv("PGSSLMODE", "Require")),
TrustServerCertificate = true,
Database = OptEnv("PGDATABASE", "postgres"),
Username = OptEnv("PGUSER", "postgres"),
Password = Env("PGPASSWORD")
};
return builder.ConnectionString;
}
public DbSet<User> Users { get; set; }
}
When running this code I get:
System.NullReferenceException: Object reference not set to an instance of an object.
I think I must have something that is preventing the mapping to my database, but I have been unable to figure it out.
EDIT
I think it's probably important I show the DDL of the table as well:
create table public.user
(
id integer generated always as identity primary key,
name text not null
constraint user_name_check
check (length(name) > 0),
email_address text not null unique
constraint user_email_address_check
check (email_address ~* '^.+#.+\..+$'),
-- Ideally use something like
-- https://www.postgresql.org/docs/13/pgcrypto.html
hash_password text not null
constraint user_password_hash_check
check (length(password_hash) > 0),
created timestamp with time zone default CURRENT_TIMESTAMP not null
constraint user_created_check
check (created <= CURRENT_TIMESTAMP)
);
alter table public."user"
owner to postgres;
EDIT 2:
Suggestions to use annotations to try to get the model to map directly to the DDL - still gives the same error, but this is our new model.
namespace Users.Models;
using System.ComponentModel.DataAnnotations.Schema;
[Table("user", Schema="public")]
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
[Column("email_address")]
public string EmailAddress { get; set; }
[Column("hash_password")]
public string HashedPassword { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[Column("created")]
public DateTime Created { get; set; }
}
I don't know about your DB but your model requires the Id column to have a value (it's not nullable) So you need to proivde a value in order to do that.
If your Id column type is Serial on the DB side, just decorate your Id column with :
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
Following #DubDub advice, removing the following line fixed it:
[SuppressDbSetInitialization]

store complex object in db using dapper

So I'm trying to store a record in the databse using dapper. I'm passing an object to the method where I have my query to store the recorde. Let me be more clear. Below is my model :
public class Foo
{
public long FooId { get; set; }
public Guid Foo2ID { get; set; }
public string Status { get; set; }
public Person Person { get; set; } = new Person();
}
public class Person
{
public string Type { get; set; }
public string Character { get; set; }
public DateTime Test { get; set; }
}
And this is my query :
public async Task<ActionResult> Create(Foo f)
{
using (var connection = _dbAccess.CreateConnection())
{
var sqlStatement = #"
INSERT INTO ReportRequests
(FooId
,Foo2Id
,Person
,Status)
VALUES
(#FooId
#,Foo2Id
#,Person
#,Status)";
await connection.ExecuteAsync(sqlStatement, f);
};
return Ok();
}
I'm trying to save a json in the Person column in the database. But I get this error :
The member x of type x cannot be used as a parameter value
Can anyone please give me an idea on how I can approach to this problem. It would be very helpful.
Thank you a lot :)
enter code hereFirst of all, you should consider whether you can use LINQ-like queries with dapper. It makes it both more readable and avoids having issues like that.
Back to your problem, from the code you posted it looks like you've misplaced the comas after the # symbol #,Foo2Id :
(#FooId
#,Foo2Id
#,Person
#,Status)
It should be:
(#FooId
#Foo2Id,
#Person,
#Status)

Mongodb collection as dynamic

I have an application that has two similar but different objects and I want to store those objects in the same collection. What is the best way to do this? And how can I query this collection?
Today my collections is represented by:
public IMongoCollection<Post> Posts
{
get
{
return _database.GetCollection<Post>("posts");
}
}
And I have this class:
public class Post
{
public string Id { get; set; }
public string Message { get; set; }
}
public class NewTypePost
{
public string Id { get; set; }
public string Image { get; set; }
}
So, today I just can save and query using Post class. Now I want to store and retrive the both classes, Post and NewTypePost.
I tried to change the class type from Post to dynamic. But when I did this, I could not query the collections.
MongoDB .NET driver offers few possibilites in such cases:
Polymorphism
You can build a hierarchy of classes and MongoDB driver will be able to determine a type of an object it gets retrieved from the database:
[BsonKnownTypes(typeof(Post), typeof(NewTypePost))]
public abstract class PostBase
{
[BsonId]
public string Id { get; set; }
}
public class Post: PostBase
{
public string Message { get; set; }
}
public class NewTypePost: PostBase
{
public string Image { get; set; }
}
MongoDB driver will create additional field _t in every document which will represent corresponding class.
Single Class
You can still have Post class and use BsonIgnoreIfNull attribute to avoid serialization exception. MongoDB .NET driver will set those properties to null if they don't exist in your database.
public class Post
{
[BsonId]
public string Id { get; set; }
[BsonIgnoreIfNull]
public string Message { get; set; }
[BsonIgnoreIfNull]
public string Image { get; set; }
}
BsonDocument
You can also drop strongly-typed approach and use BsonDocument class which is dynamic dictionary-like structure that represents your Mongo documents
var collection = db.GetCollection<BsonDocument>("posts");
More details here
dynamic
Specifying dynamic as generic parameter of ICollection you should get a list of ExpandoObject that will hold all the values you have in your database.
var collection = db.GetCollection<dynamic>("posts");
var data = collection.Find(Builders<dynamic>.Filter.Empty).ToList();
var firstMessage = data[0].Message; // dynamically typed code
Suppose I have the next conn to a test database:
var mongoClient = new MongoClient(new MongoClientSettings
{
Server = new MongoServerAddress("localhost"),
});
var database = mongoClient.GetDatabase("TestDb");
Then I can do something like:
var col = database.GetCollection<Post>("posts");
var col2 = database.GetCollection<NewTypePost>("posts");
To get two different instances of IMongoCollection but pointing to the same collection in the database. Further I am able to save to each collection in the usual way:
col.InsertOne(new Post { Message = "m1" });
col2.InsertOne(new NewTypePost { Image = "im1" });
Then, I'm also able to query from those collection base on the specific fields:
var p1= col.Find(Builders<Post>.Filter.Eq(x=>x.Message, "m1")).FirstOrDefault();
var p2 =col2.Find(Builders<NewTypePost>.Filter.Eq(x=>x.Image, "im1")).FirstOrDefault();
Console.WriteLine(p1?.Message); // m1
Console.WriteLine(p2?.Image); // im1
I don't know if that's what you want but it uses the same collection. BTW, change the Id properties to be decorated with [BsonId, BsonRepresentation(BsonType.ObjectId)]. Hope it helps.
Use the BsonDocument data type. It can do all of that. BsonDocument and dynamic back and forth is very convenient.
public class CustomObject{
public long Id{get;set;}
public string Name{get;set;}
public List<(string,object)> CollectionDynamic{get;set;}
}
// inserted in mongo
//public class CustomObject_in_Db{
// public long Id {get;set;}
// public string Name {get;set;}
// public string field2 {get;set;}
// public string field3 {get;set;}
// public string field4 {get;set;}
// public string field5 {get;set;}
// }
// something code... mapper(config)
Automapper.Mapper.CreateMap<BsonDocument,CustomObject>()
.ForMember(dest=>dest.Id, a=>a.MapFrom(s=>s.Id.GetValue(nameof(CustomObject.Id)).AsInt64)
.ForMember(dest=>dest.Name, a=>a.MapFrom(s=>s.Id.GetValue(nameof(CustomObject.Name)).AsString)
.ForMember(dest=>dest.CollectionDynamic, a=>a.MapFrom(s=>_getList(s));
// .......
private List<(string, object)> _getList(BsonDocument source){
return source.Elements.Where(e=>!typeof(CustomObject).GetProperties().Select(s=>s.Name).Any(a=>a ==e.Name)).Select(e=>e.Name, BsonTryMapper.MapToDotNetValue(e.Value)));
}

Understanding c# class markup like SQLite

using SQLite library, I can create a class like
namespace MyApp
{
[Table("mydatatable")]
public class MyData
{
[PrimaryKey, AutoIncrement, Column("_id")]
public int ID { get; set; }
[Unique, NotNull]
public Guid GUID { get; set; }
[MaxLength(256), NotNull]
public string Name { get; set; }
[NotNull]
public DateTime Created { get; set; }
public int MyNumber { get; set; }
}
}
Then I can create a Database class like
namespace MyApp
{
public class SQLiteDatabase
{
protected SQLiteConnection conn;
public SQLiteDatabase()
{
}
public void Attach(string dbName)
{
conn = new SQLiteConnection(dbName);
}
public void CreateTable<T>()
{
conn.CreateTable<T>();
}
public int Insert(Object T)
{
return conn.Insert(T);
}
}
}
This is all wonderful and makes using SQLite very easy. But how could I write my own code to do something different? For example, rather than write the data to SQLite database, let's say I wanted to convert the MyApp instance and send it over the nework.
namespace MyApp
{
public class MyDatabaseHandler
{
public void CreateTable<T>()
{
// How do I get the table name "mydatatable"?
// How do I get the column names and types?
// Once I get that information, I can send a POST to my server and create a table on the backend
}
public int Insert(Object T)
{
// How do I get the table name "mydatatable"?
// How do I get the column names and values of each data member?
// Once I get that information, I can send a POST to my server and insert the record.
}
}
}
https://msdn.microsoft.com/en-us/library/ms973893.aspx?f=255&MSPPError=-2147217396
Look into Serialization in .Net
You can convert your classes down to XML files and then pass the XML file across the network and load them at the new location into your class.
Additionally you could look into using TCP but regardless you're going to have to convert your data to some type of serialized class.
I think you need to use reflection. Try this code:
public class MyDatabaseHandler
{
public void CreateTable<T>()
{
// How do I get the table name "mydatatable"?
var type = typeof(T);
var tableAttribute = (TableAttribute)type.GetCustomAttributes(typeof(TableAttribute), inherit: false).FirstOrDefault();
var tableName = tableAttribute?.Name;
// How do I get the column names and types?
var columns = type.GetProperties()
.Select(p => new {type = p.PropertyType, name = p.Name })
.ToArray();
}
public int Insert(Object T)
{
var type = T.GetType();
// see previous method
}
}

Custom metadata from EFcontextprovider using POCO and ObjectCotext

I am new in breezejs and trying to develop an SPA with angular-breeze.
I have a class named POCOObjectContext which is inherited from the base class ObjectContext. My Database has a table named Customer and as well as I have a POCO named Customer. But I have some extra properties on the POCO Customer, like Email, SupplierName which are not the table columns. When I take the Metadat() from EFContextProvider it provides me only the columns which are in the table named Customer. But the context that holds the POCO named Customer, have all the properties i have declared. As a result in BreezeJS, while creating object from breeze.EntityManager, it is created according to the columns in the Customer Table, but i need these extra properties in the Metadata to get and save data from/to my database. Any help will be highly appreciated...
This is Context Class POCOObjectContext (tmpDataBaseEntities is ConnectionString)
public class POCOObjectContext : ObjectContext
{
private ObjectSet<Customer> customers;
public POCOObjectContext()
: base("name=tmpDataBaseEntities", "tmpDataBaseEntities")
{
customers = CreateObjectSet<Customer>();
}
public ObjectSet<Customer> Customers
{
get { return customers; }
}
}
This is POCO Customer which holds extra properties SupplierName and Email
public class Customer
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string SupplierID { get; set; }
//Extra Properties not in the Customer Table as Columns
public string SupplierName { get; set; }
public string Email { get; set; }
}
Finally the Breeze Controller
[BreezeController]
public class ReceiveServiceController : ApiController
{
EFContextProvider<POCOObjectContext> _pocoContext = new EFContextProvider<POCOObjectContext>();
ReceiveDal _rcvDal = new ReceiveDal();
[HttpGet]
public string Metadata()
{
var t = _pocoContext.Metadata();
return t; // It holds the properties info that match with POCO and Database Table.
}
}
As you have discovered, custom unmapped properties on the server are not included in the metadata definition sent to the client. You can, however, extend your client's Customer definition by doing something like this,
//Assuming you have camelCase naming convention enabled
function Customer() {
this.supplierName = '';
this.email = '';
}
entityManager.metadataStore.registerEntityTypeCtor('Customer', Customer);
Now when you call saveChanges, Breeze will include the above custom properties in the payload.
{"Id": 42, "Name": "Customer Name","__unmapped":{"supplierName":"Supplier Name", "email": "supplier#supplier.com"},...}
Then, on the server, you can examine and parse the JObject payload to retrieve the unmapped properties.
You can read more about extending Breeze entities on the client at http://www.breezejs.com/documentation/extending-entities
Hope this helps.

Categories