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]
Related
I am cassandra for custom logging my .netcore project, i am using CassandraCSharpDriver.
Problem:
I have created UDT for params in log, and added list of paramUDT in Log table as frozen.
But i am getting error: Non-frozen UDTs are not allowed inside collections. I don't know why ia m getting this error because i am using Frozen attribute on list i am using in Log Model.
logSession.Execute($"CREATE TYPE IF NOT EXISTS {options.Keyspaces.Log}.{nameof(LogParamsCUDT)} (Key text, ValueString text);");
Here is model:
public class Log
{
public int LoggingLevel { get; set; }
public Guid UserId { get; set; }
public string TimeZone { get; set; }
public string Text { get; set; }
[Frozen]
public IEnumerable<LogParamsCUDT> LogParams { get; set; }
}
Question where i am doing wrong, is my UDT script not correct or need to change in model.
Thanks in advance
I've tried using that model and Table.CreateIfNotExists ran successfully.
Here is the the code:
public class Program
{
public static void Main()
{
var cluster = Cluster.Builder().AddContactPoint("127.0.0.1").Build();
var session = cluster.Connect();
session.CreateKeyspaceIfNotExists("testks");
session.ChangeKeyspace("testks");
session.Execute($"CREATE TYPE IF NOT EXISTS testks.{nameof(LogParamsCUDT)} (Key text, ValueString text);");
session.UserDefinedTypes.Define(UdtMap.For<LogParamsCUDT>($"{nameof(LogParamsCUDT)}", "testks"));
var table = new Table<Log>(session);
table.CreateIfNotExists();
table.Insert(new Log
{
LoggingLevel = 1,
UserId = Guid.NewGuid(),
TimeZone = "123",
Text = "123",
LogParams = new List<LogParamsCUDT>
{
new LogParamsCUDT
{
Key = "123",
ValueString = "321"
}
}
}).Execute();
var result = table.First(l => l.Text == "123").Execute();
Console.WriteLine(JsonConvert.SerializeObject(result));
Console.ReadLine();
table.Where(l => l.Text == "123").Delete().Execute();
}
}
public class Log
{
public int LoggingLevel { get; set; }
public Guid UserId { get; set; }
public string TimeZone { get; set; }
[Cassandra.Mapping.Attributes.PartitionKey]
public string Text { get; set; }
[Frozen]
public IEnumerable<LogParamsCUDT> LogParams { get; set; }
}
public class LogParamsCUDT
{
public string Key { get; set; }
public string ValueString { get; set; }
}
Note that I had to add the PartitionKey attribute or else it wouldn't run.
Here is the CQL statement that it generated:
CREATE TABLE Log (
LoggingLevel int,
UserId uuid,
TimeZone text,
Text text,
LogParams frozen<list<"testks"."logparamscudt">>,
PRIMARY KEY (Text)
)
If I remove the Frozen attribute, then this error occurs: Cassandra.InvalidQueryException: 'Non-frozen collections are not allowed inside collections: list<testks.logparamscudt>'.
If your intention is to have a column like this LogParams frozen<list<"testks"."logparamscudt">> then the Frozen attribute will work. If instead you want only the UDT to be frozen, i.e., LogParams list<frozen<"testks"."logparamscudt">>, then AFAIK the Frozen attribute won't work and you can't rely on the driver to generate the CREATE statement for you.
All my testing was done against cassandra 3.0.18 using the latest C# driver (3.10.1).
I'm trying to retrieve some entities using Entity Framework by querying an XML column. Entity Framework doesn't support this so I had to use raw SQL.
var people = context.People.SqlQuery("SELECT * FROM [People] WHERE [DataXML].value('Properties/Age', 'int') = 21").AsQueryable().AsNoTracking();
My person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Column("YearsSinceBirth")]
public int Age { get; set; }
[Column(TypeName = "xml")]
public string DataXML { get; set; }
}
This should work, however, it falls over when trying to map it back to an object. Specifically, it's falling over on the Age property, which has it's column name overridden to "YearsSinceBirth".
'The data reader is incompatible with the specified
'MyProject.CodeBase.DataModel.DbEntities.Person'. A member of the
type, 'Age', does not have a corresponding column in the data reader
with the same name.'
I'm guessing that Entity Framework doesn't map database column names to object property names and therefore is expecting the column to be named 'Age' rather than 'YearsSinceBirth'.
I don't want to have to list each column and their mapping in the SQL query (like SELECT YearsSinceBirth As Age) as the actual project I'm working on which has this column has a lot more columns and that would mean this query would break every time the schema changed (kinda defeating the purpose of Entity Framework).
If this is EF Core, your problem is not that SqlQuery() doesn't support mapping column names (it does). Rather your problem is that your table doesn't contain a column called YearsSinceBirth, and you are returning 'select *'.
If you have a column called YearsSinceBirth, this works fine. Although you will be retrieving the value in the YearsSinceBirth column, not the value in the XML document. EG
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
//using Microsoft.Samples.EFLogging;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;
namespace EFCore2Test
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Column("YearsSinceBirth")]
public int Age { get; set; }
[Column(TypeName = "xml")]
public string DataXML { get; set; }
}
public class Location
{
public string LocationId { get; set; }
}
public class Db : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Location> Locations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(local);Database=EFCoreTest;Trusted_Connection=True;MultipleActiveResultSets=true");
base.OnConfiguring(optionsBuilder);
}
}
class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
db.Database.EnsureDeleted();
//db.ConfigureLogging(s => Console.WriteLine(s));
db.Database.EnsureCreated();
var p = new Person()
{
Name = "joe",
Age = 2,
DataXML = "<Properties><Age>21</Age></Properties>"
};
db.People.Add(p);
db.SaveChanges();
}
using (var db = new Db())
{
var people = db.People.FromSql("SELECT * FROM [People] WHERE [DataXML].value('(/Properties/Age)[1]', 'int') = 21").AsNoTracking().ToList() ;
Console.WriteLine(people.First().Age);
Console.ReadLine();
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}
You can use a pattern similar to this to project entity attributes from an XML or JSON column:
public class Person
{
private XDocument xml;
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public int Age
{
get
{
return int.Parse(xml.Element("Properties").Element("Age").Value);
}
set
{
xml.Element("Properties").Element("Age").Value = value.ToString();
}
}
[Column(TypeName = "xml")]
public string DataXML
{
get
{
return xml.ToString();
}
set
{
xml = XDocument.Parse(value);
}
}
}
You can dynamically create select query with aliases, if they needed, with the help of reflection and ColumnAttribute checking:
public string SelectQuery<T>() where T : class
{
var selectQuery = new List<string>();
foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var attr = prop.GetAttribute<ColumnAttribute>();
selectQuery.Add(attr != null ? $"{attr.Name} as {prop.Name}" : prop.Name);
}
return string.Join(", ", selectQuery);
}
Usage:
var people = context.People.SqlQuery($"SELECT {SelectQuery<Person>()} FROM [People] WHERE [DataXML].value('Properties/Age', 'int') = 21")
.AsQueryable().AsNoTracking();
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<>);
public class Person
{
[Required]
public int? KupaId { get; set; }
[ForeignKey("KupaId")]
public Kupa Kupa { get; set; }
public int? newKupaId { get; set; }
[ForeignKey("newKupaId")]
public Kupa NewKupa { get; set; }
}
public class Kupa
{
public int Id { get; set; }
[Index("Ix_uniqueId", IsUnique = true)]
public int ? uniqueId { get; set; }
}
public class MyController:Controller
{
public Json EditKupa(Expression<Func<Person,bool>> criteria )
{
using (IKupotRepository<Person> _IPersonRepository = new SQlRepository<Person>())
{
Person personToEdit=_IPersonRepository.SingleOrDefault(criteria,GetIncludeProperties());
> //Getting the new kupa obj from db
newKupa = GetKupa(UniqueId);
<//changing the unique property to null
personToEdit.Kupa.ToremId = null;
personToEdit.Kupa.State = State.Modified;
personToEdit.NewKupa = newKupa;
>//Assign the unique id property the value that was in the first Kupa
personToEdit.NewKupa.ToremId = 1;
personToEdit.newKupaId = newKupa.Id;
personToEdit.NewKupa.State = State.Modified;
_IPersonRepository.SaveChanges();
}
}
when calling saveChanges() getting an exception :unique key violation , when looking at the sql profiler i can see that EF 6 generates an update query for both the Kupa object but it tries to update the NewKupa.uniqueId before updating the Kupa.uniqueId ?
Assuming you are using SQL Server as a database server this is happening because you allow NULL values in that column and NULL = NULL is NULL so if you have multiple rows with NULL on that column you'll get the error.
To implement this in SQL statements will be like this:
CREATE UNIQUE NONCLUSTERED INDEX Idx_UniqueId_NotNull
ON Kupa(uniqueId)
WHERE uniqueId IS NOT NULL;
However, to do this in EF there is no easy way, but there is a workaround in this SO answer here.
I try to add view from this controller . I only need this view to show data not for insert or update or delete
public ActionResult Index()
{
var CartObj = ShoppingCart.GetCart(this.HttpContext);
var classshop = new New
{
CartItems = CartObj.GetCartItems(),
CartTotal = CartObj.GetSum()
};
return View(classshop);
}
namespace MusicStore.Models
{
public class ShoppingCart
{
MusicStoreEntities dbo = new MusicStoreEntities();
string ShoppingCartID { get; set; }
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(HttpContextBase Context)
{
var cart = new ShoppingCart();
cart.ShoppingCartID = cart.GetCardId(Context);
return cart;
}
public static ShoppingCart GetCart(Controller controller)
{
return GetCart(controller.HttpContext);
}
public List<Cart> GetCartItems()
{
return dbo.Carts.Where(a => a.CartId == ShoppingCartID).ToList();
}
public decimal? GetSum()
{
decimal? Sum = (from items in dbo.Carts
where items.CartId == ShoppingCartID
select (int)items.Count * items.album.Price).Sum();
return Sum ?? decimal.Zero;
}
}
}
and then I got this error:
there was an error running the selected code generator:
'unable to retrieve metadata for 'Musicstore.Model.new'
one or more validation error were detected during model generation
musicstore,models.New :entity type'New' has no key defined .
define the key of entityType
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MusicStore.Models
{
public class New
{
public List<Cart> CartItems { get; set; }
public decimal? CartTotal { get; set; }
}
}
There are two options here. First, if this class is mapped to a table in your database, every model in entity framework requires a primary key. Add this into your model:
[Key]
public int Id { get; set; }
This creates a new property called Id and the [Key] attribute makes it a primary key. Technically you don't need the attribute as EF will pick up Id property and use it as a key, but I prefer to be explicit.
Alternatively, if you don't want this class to be a table in your database, add the NotMapped attribute to the class like this:
[NotMapped]
public class New
{
public List<Cart> CartItems { get; set; }
public decimal? CartTotal { get; set; }
}
I know this is old, but I just ran across this issue.
What happen is when I created a class, CreateEmployeeViewModel, inside the Models folder Visual Studio "smartly" put a line in my DB Context class
public System.Data.Entity.DbSet<eManager.Web.Models.CreateEmployeeViewModel>
CreateEmployeeViewModels { get; set; }
So a table was created on the next update-migration. Removing this line removed the requirement for a key field.
Note: You may also have to add the line AutomaticMigrationDataLossAllowed = true; to your DBMigrationConfiguration Class if the table was created.