If I have a class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
And I try to insert this into a corresponding MySQL table using Dapper/DapperExtensions:
var person = new Person { Id = 10, Name = "Bobby" };
connection.Insert(person);
Assuming the Id column in MySQL is set to AUTO_INCREMENT and that the table is empty. Then somewhere along the way the Id value of 10 is changed into 1.
Is it possible, using Dapper/DapperExtensions, to insert the correct Id of 10?
I turned out that the solution was very simple. The solution was using a custom classmapper. (The EntityBase class, in the code below, is a base class for all database entities in my system)
public class PrimaryKeyAssignedClassMapper<T> : ClassMapper<T> where T : EntityBase
{
public PrimaryKeyAssignedClassMapper()
{
base.Map(m => m.Id).Key(KeyType.Assigned);
base.AutoMap();
}
}
And then at somepoint before calling the Insert method, I added
DapperExtensions.DapperExtensions.DefaultMapper = typeof(PrimaryKeyAssignedClassMapper<>);
Related
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]
How can I interchangeably use entity base Id or database column Id
public partial class Orders : EntityBase<int>
{
protected Orders() { }
public int Orderid { get; set; }
public override int Id { get => base.Id; set => base.Id = Orderid; }
}
Entitybase:
public abstract class EntityBase<T> : IEntity<T>
{
public virtual T Id { get; set; }
}
Question: Can I map this Id of EntityBase and db column's primary key(for eg: order entity's key -> orderId) to sync values ( In app code, either user set Id of base or orderId of entity both should contain same value and also when retrieved also these values, at a given time, should return same value. Is there any way to achieve above synch feature via fluent API in the OnModelCreating() method?
P.S: If you have not understood the question, please say which part does not have clarification, instead of using authority :)
Basically If I understand your question correctly, you want to have a base class with an Id, but in SQL you've named your Id Column differently. You want to Alias your updates so that when you set they Id, they also set the DB Id.
This is more of an architecture, thing, if you really needed to you could write tool that helps you scaffold automatically, but that seems buggy and more complex.
Instead I'll try to fix this problem by modifying your architecture.
A simple way to fix this problem, is just to work with Id's
public partial class Orders : EntityBase<int>
{
protected Orders() { }
[Column("Orderid")]
public int Id { get; set; }
}
You can map a column directly to the Id property, and avoid the whole issue all together.
If you're set on having both OrderId and Id
public partial class Orders : EntityBase<int>
{
protected Orders() { }
public int Orderid { get; set; }
public override int Id { get => Orderid; set => Orderid = value; }
}
but using that architecure is generally a bad idea, It's messy since your EntityBase Is Generic an seems error prone.
If i understand right you need that your Orders.Orderid is an autoincremental-PrimaryKey for your DB table right?
Supposing you're using SQLite (if not probably is similar but not the same words) you can do something like that:
public partial class Orders : EntityBase<int> {
[PrimaryKey, AutoIncrement]
public int Orderid { get; set; }
protected Orders() { }
public override int Id { get => base.Id; set => base.Id = Orderid; }
}
When you create/update your table using the orders "model" SQLite should do the rest.
L-
I am building a WinRT application with SQLite.
I am running the following method within my C# code in order to retrieve all columns from a table ProductSubCategory which have equal Parent ID
I am using latest version of SQLite.Net and SQLITE.NET.ASync
public async Task<IEnumerable<ProductSubCategory>> GetProductSubCategoryAsync(int ParentCategoryId)
{
List<ProductSubCategory> lst = new List<ProductSubCategory>();
var DBconn = await _sqliteService.OpenSQLiteConnection();
{
//retrive test data
lst = await DBconn.Table<ProductSubCategory>().Where(c=>c.ParentSubCategoryId==ParentCategoryId).ToListAsync();
}
return lst;
}
Class Definition :
public class ProductCategory:ObservableObject
{
[PrimaryKey]
public int ProductCategoryId { get; set; }
public string Name { get; set; }
public string RowGuid { get; set; }
public DateTime ModifiedDate { get; set; }
}
When the code execute it returns the correct amount of records based on the filter but not all return field value get returned.
For instance all my table field of type INT as well as primary key is not updated in my object.
Any reason for that and solution to get it to work? I need to retrieve all those column data.
Does it depend on the way field gets created ?
I manage to find out the problem.
My table database column field which was not returning data was named ProductCategoryID and my object name was ProductCategoryId
Which means that when to get field from DB, field name are case sensitive but what is strange is that if your insert data they are not.
I have simply rename my table field name to be ProductCategoryID and now I get the data correctly
I just wan't to know if it's possible to assign a customized primary key only when the ObjectContext.SaveChanges() is called?
I'm trying to search for any solutions but it seems like I'am the only one thinking about this.
It is the same as what is happening when you have an identity column which the EF will automatically assign a primary key once the ObjectContext.SaveChanges() is called but instead of the primary key being an auto identity column, I want to customize my own primary key.
Thanks in advance guys.
Edit: Additional Details
example I have this class:
public class Transaction()
{
public string ID { get; set; }
public DateTime TransactionDate { get; set; }
}
I wan't to add the class in the objectset like this:
Transaction trans = new Transaction()
{
ID = null,
TransactionDate = DateTime.Now
};
ObjectSet.AddObject(trans);
Notice that I haven't assign the ID yet, I want it to be assigned only when the user click save which will call the
ObjectContext.SaveChanges();
One the user call this, I will get a new primary key and assign it to the trans instance.
Not ideal but consider changing your model to this. Your POCO now contains some logic which is not good but it allows you to customize the save to create your id from the third party class library. The string ID column will resolve to a primary key of type nvarchar(128) by default. Your ID will be null prior to the save.
public abstract class Entity
{
public virtual void OnSave();
}
public class Transaction : Entity
{
public string ID { get; set; }
public DateTime TransactionDate { get; set; }
public override void OnSave()
{
ID = Guid.NewGuid().ToString(); //call to class library
}
}
You can hook into the SaveChanges method by overriding it in your DbContext
public class MyContext : DbContext
{
public DbSet<Transaction> Trans { get; set; }
public MyContext () : base()
{
}
public override int SaveChanges()
{
var changedEntities = ChangeTracker.Entries();
foreach (var changedEntity in changedEntities)
{
var entity = (Entity)changedEntity.Entity;
entity.OnSave();
}
return base.SaveChanges();
}
}
I have table called Customer with
CustomerID
Name
Salary etc.
I have added Customer table object to dbml, now on top of Customer table columns I need to add IsProcessed column.
I have added it but it throws exception while retrieving data as "invalid column IsProcessed"
Do i need to create separate POCO object and add extra column
Later fill in the new list with POCO object from db list.
Any alternative solution? Please advise
You can extend class generated from DBML by creating partial class in new file :
public partial class Customer
{
public bool IsProcessed { get; set; }
}
put codes above in new class file and set it's namespace the same as your DBML generated Customer class.
This is common pattern to be able to extend generated class without worrying the extension codes overridden if DBML file regenerated.
[For Reference]
If the models get out of sync with the database and saving the EDMX file and running the “Update Model from Database…” feature doesn’t work, then consider this link
http://blog.jongallant.com/2012/08/entity-framework-manual-update.html#.Uwrul_mSzkA
public class CustomerData
{
public int CustomerID { get; set; }
public string Name { get; set; }
public double Salary { get; set; }
public bool IsProcessed { get; set; }
}
LINQ query:
List<CustomerData> GetData()
{
var data = from cus in context.Customer
select new CustomerData{
CustomerID = cus.CustomerID,
Name = cus.Name,
Salary = cus.Salary
IsProcessed = Your custom field data
};
return data.ToList();
}