I want to design a orm tool for my daily work, but I'm always worry about the mapping of foreign key.
Here's part of my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace OrmTool
{
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute:Attribute
{
public string Name { get; set; }
public SqlDbType DataType { get; set; }
public bool IsPk { get; set; }
}
[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=false)]
public class TableAttribute:Attribute
{
public string TableName { get; set; }
public string Description { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
public class ReferencesAttribute : ColumnAttribut
{
public Type Host { get; set; }
public string HostPkName{get;set;}
}
}
I want to use Attribute to get the metadata of Entity ,then mapping them,but i think it's really hard to get it done;
public class DbUtility
{
private static readonly string CONNSTR = ConfigurationManager.ConnectionStrings["connstr"].ConnectionString;
private static readonly Type TableType = typeof(TableAttribute);
private static readonly Type ColumnType = typeof(ColumnAttribute);
private static readonly Type ReferenceType = typeof(ReferencesAttribute);
private static IList<TEntity> EntityListGenerator<TEntity>(string tableName,PropertyInfo[] props,params SqlParameter[] paras) {
return null;
}
private static SqlCommand PrepareCommand(string sql,SqlConnection conn,params SqlParameter[] paras) {
SqlCommand cmd = new SqlCommand();
cmd.CommandText = sql;
cmd.Connection = conn;
if (paras != null)
cmd.Parameters.AddRange(paras);
conn.Open();
return cmd;
}
}
I don't know how to do the next step, if every Entity has it's own foreign key,how do I get the return result ? If the Entity like this:
[Table(Name="ArtBook")]
public class ArtBook{
[column(Name="id",IsPk=true,DataType=SqlDbType.Int)]
public int Id{get;set;}
[References(Name="ISBNId",DataType=SqlDataType.Int,Host=typeof(ISBN),HostPkName="Id")]
public ISBN BookISBN{get;set;}
public .....more properties.
}
public class ISBN{
public int Id{get;set;}
public bool IsNative{get;set;}
}
If I read all ArtBooks from database and when I get a ReferencesAttribute how do I set the value of BookISBN?
in method EntityListGenerator ,I try to make a recursion that get mapping,but i don't know how to do next ,for each foreign key i'll read data from database? or get all foreign then mapping? those all too bad.
Related
I have this entity:
public class Genres
{
public int Id { get; set; }
[Required(ErrorMessage ="the field {0} is required")]
[StringLength(50)]
[FirstLetterUpperCase]
public string Name { get; set; }
}
And this DTO or model:
public class GenresDTO
{
public int Id { get; set; }
public string Name { get; set; }
}
I have initiated my mapper like this:
public class AutoMapperClass : Profile
{
public AutoMapperClass()
{
generateMapper();
}
private void generateMapper()
{
CreateMap<GenresDTO, Genres>().ReverseMap();
CreateMap<GenresCreationDTO, Genres>();
}
}
I have also written this part of code in my program.cs :
builder.Services.AddAutoMapper(typeof(IStartup));
I am using .NET 6 and Visual Studio, and when I run my project, I get an error that is mentioned in the title and its related to this section :
public async Task<ActionResult<List<GenresDTO>>> Get()
{
var genres = await dbContext.Genres.ToListAsync();
return mapper.Map<List<GenresDTO>>(genres);
}
which is in my Controller file, and I initiated the mapper like this :
private readonly ILogger<GenresController> ilogger;
private readonly ApplicationDBContext dbContext;
private readonly IMapper mapper;
public GenresController(ILogger<GenresController> ilogger,
ApplicationDBContext dbContext , IMapper mapper)
{
this.ilogger = ilogger;
this.dbContext = dbContext;
this.mapper = mapper;
}
Should be probably typeof(Program) in registration (assuming that you are using .Net6 where we have only Program.cs)
builder.Services.AddAutoMapper(typeof(Program))
If you have multiple projects in solution,then value used there should be a file in the assembly in which the mapping configuration resides.
I have a model Blueprint that requires a reference to an IdentityUser. When I run Add-Migration CreateBlueprintSchema the following error is thrown:
No suitable constructor was found for entity type 'Blueprint'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'author' in 'Blueprint(IdentityUser author, string name, string data)'.
How do I resolve this issue?
Blueprint.cs
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
namespace FactorioStudio.Models
{
public class Blueprint
{
[MaxLength(40)]
public string Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Data { get; set; }
[Required]
public IdentityUser Author { get; set; }
[Required]
public DateTime CreationDate { get; set; }
public Blueprint? ParentBlueprint { get; set; }
public Blueprint(IdentityUser author, string name, string data)
{
Author = author;
Name = name;
Data = data;
CreationDate = DateTime.UtcNow;
string hashSource = Author.Id +
Name +
CreationDate.ToString("s", System.Globalization.CultureInfo.InvariantCulture) +
Data;
using SHA1 sha1 = SHA1.Create();
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(hashSource));
string hash = BitConverter.ToString(hashBytes).Replace("-", String.Empty).ToLower();
Id = hash;
}
}
}
change your model like this:
public class Blueprint
{
[MaxLength(40)]
public string Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Data { get; set; }
[Required]
public IdentityUser Author { get; set; } = new IdentityUser();
//.......
}
or just add a non-parameter constructor in your model.
You can refer to this;
Edit=======================
For testing convenience, I manually added a foreign key(If I don't add Fk by myself, EF core will create shadow foreign key property, But i can't use it directly), Then I create a Blueprint without referring to an existing IdentityUser, You can see it will report a SqlException that show error in FK.
As I wrote in the comment, EF Core cannot set navigation properties using a constructor. Instead, use a custom value generator to create the hash for the ID when saving the entity.
public class BlueprintHashGenerator : ValueGenerator<string>
{
public override bool GeneratesTemporaryValues => false;
public override string Next(EntityEntry entry)
{
if (entry.Entity is not Blueprint bp)
{
throw new ApplicationException("Unexpected entity");
}
string hashSource = bp.Author.Id +
bp.Name +
bp.CreationDate.ToString("s", System.Globalization.CultureInfo.InvariantCulture) +
bp.Data;
using SHA1 sha1 = SHA1.Create();
byte[] hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(hashSource));
return BitConverter.ToString(hashBytes).Replace("-", String.Empty).ToLower();
}
}
then in model builder
builder.Entity<Blueprint>().Property(bp => bp.Id).HasValueGenerator<BlueprintHashGenerator>().ValueGeneratedNever();
It will generate the value on SaveChanges, so ensure all properties are set before calling save (Author, Name, CreationDate, Data).
Here is the console app VB.NET code:
Imports System.Data.Entity
Imports System.Linq
Imports System.Configuration
Module Module1
Sub Main()
Using db = New BloggingContext()
Console.Write("Enter a name for a new Blog: ")
Dim name = Console.ReadLine()
Dim blogt As New Blog()
With blogt
.Name = name
End With
db.Blogs.Add(blogt)
db.SaveChanges()
Dim query = From b In db.Blogs
Order By b.Name
Console.WriteLine("All blogs in the database:")
For Each item In query
Console.WriteLine(item.Name)
Next
Console.WriteLine("Press any key to exit...")
Console.ReadKey()
End Using
End Sub
Public Class Blog
Public Property BlogId() As Integer
Public Property Name() As String
End Class
Public Class BloggingContext
Inherits DbContext
Public Sub New()
MyBase.New("dbConnString")
End Sub
Public Blogs As DbSet(Of Blog)
End Class
End Module
My error points to this line in Main() 'db.Blogs.Add(blogt)' stating "System.NullReferenceException: Object reference not set to an instance of an object."
When I mouse over db.Blogs.Add(blogt), it tells me that db.Blogs is nothing. I converted the same code into C# and it works perfectly:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
namespace CodeFirstNewDatabaseSample
{
class Program
{
static void Main(string[] args)
{
using (var db = new BloggingContext())
{
// Create and save a new Blog
Console.Write("Enter a name for a new Blog: ");
var name = Console.ReadLine();
var blog = new Blog { Name = name };
db.Blogs.Add(blog);
db.SaveChanges();
// Display all Blogs from the database
var query = from b in db.Blogs
orderby b.Name
select b;
Console.WriteLine("All blogs in the database:");
foreach (var item in query)
{
Console.WriteLine(item.Name);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public virtual List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
public class BloggingContext : DbContext
{
public BloggingContext()
: base("dbConnString")
{
//Database.SetInitializer<BloggingContext>(new CreateDatabaseIfNotExists<BloggingContext>());
}
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
}
Not sure what's missing. I've read all the other EF posts regarding the NullReferenceException error when attempting to SaveChanges, but none have resolved the issue for me yet. Where's Waldo in this?
Blogs is not a property in the VB code, it should be:
Public Property Blogs As DbSet(Of Blog)
Without "Property" it's just a member variable, so it's not automatically initialized by the DbContext.
How can I map the class AttributeSet with Fluent NHibernate using a fluent mapping
public class AttributeSet : DictionaryBase
{
private readonly Dictionary<string, object> _cache;
public AttributeSet()
{
_cache = new Dictionary<string, object>();
}
public object this[string index]
{
get
{
return _cache[index];
}
set
{
_cache[index] = value;
}
}
}
public class Entity
{
protected Entity()
{
Attributes = new AttributeSet();
}
public virtual int Id { get; set; }
public virtual string Label { get; set; }
public virtual string Description { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual AttributeSet Attributes { get; set; }
}
I don't think there's a way to map your indexer directly, but you can expose the underlying dictionary type and map that instead.
If you don't want to expose the dictionary as public you can map it as a private property instead as explained here. For mapping a dictionary, see here.
An example might be
HasMany<object>(Reveal.Member<AttributeSet>("Cache"))
.KeyColumn("AttributeSetId")
.AsMap<string>(idx => idx.Column("Index"), elem => elem.Column("Value"))
.Access.CamelCaseField(Prefix.Underscore)
Mapping the object type in the dictionary might be interesting, I don't know if NHibernate will be able to translate it directly to an underlying database type.
I've been trying to get a WCF service to access a file database that is stored on a local file system to no avail currently.
I created a database called data.mdf in visual studio 2010 and ran this sql query on it.
create table Person
(PersonID int identity (1000,1) not null,
Name nvarchar(50) not null,
Address nvarchar(max)
create table ImportantPerson
(ImportantPersonID int indentity(1000,1) not null,
Name nvarchar(50) not null,
Address nvarchar(max)
and it successfully created the tables I wanted so I proceeded to create my DataContract:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Data.Linq;
using System.Data.Linq.Mapping;
namespace MyDataContract
{
public class DataDB: DataContext
{
public Table<Person> Persons;
public Table<Important> Importants;
public DataDB(string connstr)
: base(connstr)
{
}
}
[DataContract]
[Table(Name = "Person")]
public class Person
{
private int _ID;
private String _Name;
private String _Address
public Person()
{
}
public Person(String Name, String Address)
{
_Name = Name;
_Address = Address;
}
[DataMember]
[Column(IsPrimaryKey = true, Storage = "PersonID", DbType="int not null", IsDbGenerated = true)]
public int PersonIdentification
{
get{ return _PersonID;}
set{ _PersonID = value;}
}
[DataMember]
[Column(Storage = "Name")]
{
get{ return _Name;}
set{ _Name = value;}
}
[DataMember]
[Column(Storage = "Address")]
{
get{ return _Address;}
set{_Address= value;}
}
public class Person
{
private int _ID;
private String _Name;
private String _Address
public ImportantPerson()
{
}
public ImportantPerson(String Name, String Address)
{
_Name = Name;
_Address = Address;
}
[DataMember]
[Column(IsPrimaryKey = true, Storage = "ImportantPersonID", DbType="int not null", IsDbGenerated = true)]
public int PersonIdentification
{
get{ return _PersonID;}
set{ _PersonID = value;}
}
[DataMember]
[Column(Storage = "Name")]
{
get{ return _Name;}
set{ _Name = value;}
}
[DataMember]
[Column(Storage = "Address")]
{
get{ return _Address;}
set{_Address= value;}
}
When I try to instantiate a connection to the database
MyDataContract.DataDB data = new MyDataContract.DataDB(#"c:\data\data.mdf");
I get an exception
Bad Storage property: 'PersonID' on
member
'MyDataContract.Person.PersonIdentification'.
Can someone help me figure out what's wrong? From everything I've read this should work although I could miss something.
The storage member must be private. You don't need the "Name=" tag if the public name is the same as the column name.
Ladislav Mrnka is right but that was caused by a copy paste error. I did solve it on my own, so in case someone has the same problem is will post how to fix it explicitly.
To fix it i had to also put the name of the field, and change the Storage attribute like so for :
the table person has the "Name" and in the Person Object has the private instance variable "_Name"
the property must be set up as:
[DataMember]
[Column(Name="Name",Storage="_Name")]
public String Name
{
get{...}
set{...}
}
Also should mention that I only successfully got it to work when the intance variable was private and the property was public.
I'm missing ImportantPerson class in your code with Table attribute pointing to correct DB table and your code contains Person class twice.