With a model such as ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.ComponentModel.DataAnnotations;
namespace Singleton
{
public class Program
{
public static void Main(string[] args)
{
var builder = new ModelBuilder();
builder.Configurations.Add(new TemplateConfiguration());
builder.Configurations.Add(new UserConfiguration());
builder.Configurations.Add(new UnitConfiguration());
builder.Configurations.Add(new AttributeConfiguration());
var model = builder.CreateModel();
using (var context = new SampleDataContext(model))
{
bool updating = true;
if (updating)
{
var units = new List<Unit>
{
new Unit{ Name = "Unit1" },
new Unit{ Name = "Unit2" }
};
units.ForEach(x => { context.Units.Add(x); });
context.SaveChanges();
var templates = new List<Template>
{
new Template{
Name = "Default",
Attributes = new List<Attribute>
{
new Attribute
{
Unit = context.Units.Single( i => i.Name == "Unit1" )
}
}
}
};
templates.ForEach(x =>
{
context.Templates.Add(x);
});
context.SaveChanges();
var users = new List<User>
{
new User
{
Name = "Stacey"
},
new User
{
Name = "Daniel"
},
new User
{
Name = "Derek"
}
};
users.ForEach(x => { context.Users.Add(x); });
context.SaveChanges();
updating = !updating; // stop updating
}
if (!updating)
{
Single.Instance = context.Templates.Single(i => i.Name == "Default");
}
foreach (User user in context.Users)
{
Console.WriteLine(user.Template.Name); // does template requery?
}
}
}
}
public class Template
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Attribute> Attributes { get; set; }
}
public class TemplateConfiguration : EntityConfiguration<Template>
{
public TemplateConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
//// map the collection entity
HasMany(k => k.Attributes).WithRequired()
.Map("template.attributes",
(template, attribute) => new
{
Template = template.Id,
Attribute = attribute.Id
});
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("templates");
}
}
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
[StoreGenerated(StoreGeneratedPattern.None)]
public Template Template { get { return Single.Instance; } }
}
public class UserConfiguration : EntityConfiguration<User>
{
public UserConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("users");
}
}
public class Unit
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class UnitConfiguration : EntityConfiguration<Unit>
{
public UnitConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
Property(k => k.Name);
MapSingleType(c => new
{
c.Id,
c.Name
}).ToTable("units");
}
}
public class Attribute
{
public virtual int Id { get; set; }
public Unit Unit { get; set; }
}
public class AttributeConfiguration : EntityConfiguration<Attribute>
{
public AttributeConfiguration()
{
HasKey(k => k.Id);
Property(k => k.Id).IsIdentity();
// Initialize the Statistic
HasRequired(k => k.Unit);
// map the data type to the context so that it can be queried.
MapHierarchy(c => new
{
c.Id,
Unit = c.Unit.Id
}).ToTable("attributes");
}
}
public class Single
{
public static Template Instance;
}
public class SampleDataContext : DbContext
{
public SampleDataContext(DbModel model)
: base(model)
{
this.ObjectContext.ContextOptions.LazyLoadingEnabled = true;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public DbSet<Template> Templates { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Unit> Units { get; set; }
public DbSet<Attribute> Attributes { get; set; }
}
}
if I assign Singleton.Instance to a query from the DataContext, and then assign the Singleton.Instance to other objects in my code, will the SQL be run once, or each time it is accessed? Can anyone help me here to see if this pattern is going to save some SQL?
From context.SomeQuery, you're almost certainly just returning some kind of queryable object or other iterable (I'm not sure your example reflects how your EF solution is actually architected, I think some elements were lost for the sake of brevity). So, every time you access this singleton, you're just going to iterate over it (run the query) again.
I recommend you use a simple memoization revolving around 4.0 caching, try this.
The SQL will run each time you access it.
What you'r looking for is some kind of caching strategy.
Have a look at the following thread. I think it will help you: How to make Entity Framework cache some objects
Related
I have a scenario where I want to create an object (Calc) which takes some options as a constructor argument. It happens I have a serialized version of Calc, its properties and the options properties as another single object. Here's my code:
void Main()
{
var mockMapper = new MapperConfiguration(c =>
{
c.AddProfile<MapperProf>();
})
.CreateMapper();
}
public class MapperProf : Profile
{
public MapperProf()
{
CreateMap<Scen, Calc>()
.ConstructUsing(c => new Calc()) // I AM STUCK HERE
CreateMap<Scen, Opt>()
.ForMember(o => o.OptProp, o => o.MapFrom(o => o.OptProp));
}
}
public class Calc
{
public Calc(Opt opt)
{
OptProp = opt.OptProp;
}
public string CalcProp { get; set; }
private string OptProp { get; set; }
}
public class Opt
{
public string OptProp { get; set; }
}
public class Scen
{
public string CalcProp { get; set; }
public string OptProp { get; set; }
}
For various reasons I cannot access Calc.OptProp, I have to pass it in via the constructor argument.
In equivalent terms what I want to do in one shot is:
Calc c = mockMapper.Map<Calc>().ConstructUsing(c => new Calc(mockMapper.Map<Opt>(scen)));
That is, construct both the Calc and Opt from the same Scen.
In the ConstructUsing you can use ResulotionContext and then use mapper for create constructor like this:
public class MapperProf : Profile
{
public MapperProf()
{
CreateMap<Scen, Opt>().ForMember(o => o.OptProp, o => o.MapFrom(scen => scen.OptProp));
CreateMap<Scen, Calc>().ConstructUsing((scen, context) => new Calc(context.Mapper.Map<Scen, Opt>(scen)));
}
};
And for mapping scen:
var scen = new Scen() { CalcProp = "Calc", OptProp = "Opt" };
var calc = mockMapper.Map<Scen, Calc>(scen);
im new to EFCore and i don't understand whether it is necessary to specify all the properties in the OnModelCreating() method, because when i don't it does not automatically add them.
for example do i have to add this line for every property of my class in OnModelCreating() :
builder.Entity<Entity>().Property(p => p.PropertyName);
it appears that EFcore creates columns in the database only if i include them using the previous method , im sure it does not have to be this as i have followed the Ms Docs tutorial and i didn't have to do it, but this time i could not
here is my Model
public class Block
{
public int SequenceNumber {get ; private set;}
public virtual BlockHeader Header {get ; private set;}
public List<Transaction> Transactions {get ; private set;}
public Block(List<Transaction> transactions,int sequencenumber, BlockHeader header)
{
Transactions= transactions;
Header=header;
Header.SequenceNumber=sequencenumber;
SequenceNumber=sequencenumber;
}
public Block(){
}
}
public class BlockHeader
{
[MaxLength(256)]
public byte[] Hash {get ;}
[MaxLength(256)]
public byte[] PreviousHash { get;}
public DateTime timestamp { get; }
public int version{ get; }
[MaxLength(256)]
public byte[] MerkleRoot{ get; }
public int SequenceNumber {get ; set;}
//this property is used only to configuire the one to one relationship
public Block block{get;set;}
public BlockHeader (byte[] previoushash,int sequencenumber,List<Transaction> transactions)
{
timestamp = DateTime.Now;
PreviousHash = previoushash;
version = 1;
MerkleRoot = ComputeMerkleRoot(transactions);
Hash = ComputeBlockHash();
}
public BlockHeader(){
}
}
and here is Context class :
class BlockchainDB : DbContext
{
public BlockchainDB()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("server=localhost;database = Blockchain ;user = root ; password = sdeath");
}
protected override void OnModelCreating(ModelBuilder builder){
builder.Entity<Block>().HasKey(block => block.SequenceNumber);
builder.Entity<Block>().HasOne<BlockHeader>(block => block.Header)
.WithOne(header => header.block)
.HasForeignKey<BlockHeader>(header => header.SequenceNumber);
builder.Entity<BlockHeader>().Property(p => p.Hash).HasMaxLength(256);
builder.Entity<BlockHeader>().HasKey(header => header.Hash);
builder.Entity<BlockHeader>().Property(p => p.MerkleRoot);
builder.Entity<BlockHeader>().Property( p => p.PreviousHash);
}
public DbSet<Block> Block {get; set;}
public DbSet<BlockHeader> blockheader {get; set;}
}
Am i missing something ?
Here is a fully working example console project, that uses data annotations instead of the Fluent API as much as possible (which seems to be what you are looking for). I took the liberty to make some minor changes to the model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class Block
{
[Key]
public int SequenceNumber { get; private set; }
public BlockHeader Header { get; private set; }
public ICollection<Transaction> Transactions { get; private set; } = new HashSet<Transaction>();
public Block(int sequenceNumber, BlockHeader header, ICollection<Transaction> transactions)
{
Transactions = transactions;
Header = header;
Header.SequenceNumber = sequenceNumber;
SequenceNumber = sequenceNumber;
}
private Block() // <-- can be made private
{
}
}
public class BlockHeader
{
[Key]
public int SequenceNumber { get; internal set; }
[MaxLength(256)]
public byte[] Hash { get; private set; }
[MaxLength(256)]
public byte[] PreviousHash { get; private set; }
public DateTime TimeStamp { get; private set; }
public int Version { get; private set; }
[MaxLength(256)]
public byte[] MerkleRoot { get; private set; }
public Block Block { get; private set; }
public BlockHeader(byte[] previousHash, ICollection<Transaction> transactions)
{
Hash = ComputeBlockHash();
PreviousHash = previousHash;
TimeStamp = DateTime.Now;
Version = 1;
MerkleRoot = ComputeMerkleRoot(transactions);
}
private BlockHeader() // <-- can be made private
{
}
// Stub:
private byte[] ComputeMerkleRoot(ICollection<Transaction> transactions)
=> Array.Empty<byte>();
// Stub:
private byte[] ComputeBlockHash()
=> Array.Empty<byte>();
}
public class Transaction
{
[Key]
public int TransactionNumber { get; set; }
public int SequenceNumber { get; set; }
public Block Block { get; set; }
}
public class BlockchainDB : DbContext
{
public DbSet<Block> Blocks { get; set; }
public DbSet<BlockHeader> BlockHeaders { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseMySql(
"server=127.0.0.1;port=3306;user=root;password=;database=So63115500",
b => b.ServerVersion("8.0.20-mysql"))
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Block>(
entity =>
{
entity.HasOne(e => e.Header)
.WithOne(e => e.Block)
.HasForeignKey<BlockHeader>(e => e.SequenceNumber);
});
builder.Entity<Transaction>(
entity =>
{
entity.HasOne(e => e.Block)
.WithMany(e => e.Transactions)
.HasForeignKey(e => e.SequenceNumber);
});
}
}
internal static class Program
{
private static void Main()
{
using (var context = new BlockchainDB())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
//
// Let's add some sample data:
//
var transactionsBlock1 = new List<Transaction>
{
new Transaction {TransactionNumber = 1},
new Transaction {TransactionNumber = 2},
};
var transactionsBlock2 = new List<Transaction>
{
new Transaction {TransactionNumber = 3},
new Transaction {TransactionNumber = 4},
};
context.Blocks.AddRange(
new Block(1, new BlockHeader(Array.Empty<byte>(), transactionsBlock1), transactionsBlock1),
new Block(2, new BlockHeader(Array.Empty<byte>(), transactionsBlock2), transactionsBlock2));
context.SaveChanges();
}
using (var context = new BlockchainDB())
{
var blocksWithHeaderAndTransactions = context.Blocks
.Include(b => b.Header)
.Include(b => b.Transactions)
.ToList();
Debug.Assert(blocksWithHeaderAndTransactions.Count == 2);
Debug.Assert(blocksWithHeaderAndTransactions[0].SequenceNumber == 1);
Debug.Assert(blocksWithHeaderAndTransactions[0].Header != null);
Debug.Assert(blocksWithHeaderAndTransactions[0].Header.SequenceNumber == 1);
Debug.Assert(blocksWithHeaderAndTransactions[0].Transactions.Count == 2);
Debug.Assert(blocksWithHeaderAndTransactions[0].Transactions.First().SequenceNumber == 1);
Debug.Assert(blocksWithHeaderAndTransactions[0].Transactions.First().TransactionNumber == 1);
}
}
}
}
I used Pomelo.EntityFrameworkCore.MySql as the EF Core provider (and .UseMySql() instead of .UseMySQL()), which is faster, has way better support and a richer feature set than Oracle's own provider. (Full disclosure, I am the lead developer of the Pomelo provider.)
it appears that EFcore creates columns in the database only if i include them using the previous method , im sure it does not have to be this as i have followed the Ms Docs tutorial and i didn't have to do it, but this time i could not
Try with { get; set; } on all properties – ErikEJ
#ErikEJ what if i dont want to have a setter ?
EF Core will also work with properties that have private setters. You can also use Backing Fields instead.
You seem to care a lot about about the visibility of your properties and methods. You might be interested to know then, that the default constructor of the model classes doesn't need to be public.
I'm trying to recreate the sticky notes app in windows 10 using WPF.
I am using Entity Framework with an SQLite database. And EF can't retrieve an object which is the child of another. How can I do that?
I have basically 2 classes i wanna store in this database
StickyNote and SitckyNoteGroup. Each StickyNote has a StickyNoteGroup.
In order you to fully understand I'll post all the classes involved in my problem but here is the github repo if you want https://github.com/Kamigoro/StickyNotes.
namespace Todo.Models
{
public class StickyNote
{
public int Id { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public StickyNoteGroup Group { get; set; }
public override string ToString()
{
return $"Name : {Name}\nText : {Text}";
}
}
}
namespace Todo.Models
{
public class StickyNoteGroup
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public override string ToString()
{
return $"Name : {Name}\nColor : {Color}";
}
}
}
My class using EntityFramework looks like this and is called NoteContext
using Microsoft.EntityFrameworkCore;
namespace Todo.Models.DataAccess
{
public class NoteContext : DbContext
{
public DbSet<StickyNote> Notes { get; set; }
public DbSet<StickyNoteGroup> NoteGroups { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite("Data Source=Todo.db");
}
}
And finally the class that I use to do CRUD operations is called DataAccessor and looks like this.
namespace Todo.Models.DataAccess
{
public static class DataAccessor
{
public static List<StickyNote> GetStickyNotes()
{
List<StickyNote> notes;
using (var database = new NoteContext())
{
notes = database.Notes.ToList();
}
return notes;
}
public static void SaveStickyNote(StickyNote stickyNote)
{
using (var database = new NoteContext())
{
database.Notes.Add(stickyNote);
database.SaveChanges();
}
}
public static List<StickyNoteGroup> GetStickyNoteGroups()
{
List<StickyNoteGroup> noteGroups;
using (var database = new NoteContext())
{
noteGroups = database.NoteGroups.ToList();
}
return noteGroups;
}
public static void SaveStickyNoteGroup(StickyNoteGroup stickyNoteGroup)
{
using (var database = new NoteContext())
{
database.NoteGroups.Add(stickyNoteGroup);
database.SaveChanges();
}
}
}
}
My question is why this part of code tell me that there is no StickyNoteGroup for the current StickyNote, even though there is one in the sqlite database?
private void btnAddStickyNote_Click(object sender, RoutedEventArgs e)
{
StickyNoteGroup group = new StickyNoteGroup() { Name = "Test Group", Color = "Red" };
StickyNote note = new StickyNote() { Name = "Note 1", Text = "An attempt to write a note", Group = group };
DataAccessor.SaveStickyNote(note);
foreach (StickyNote currentNote in DataAccessor.GetStickyNotes())
{
Debug.WriteLine(currentNote.Group.ToString());
}
}
StickyNote Table:
StickyNoteGroup Table:
Thanks a lot for your answers. And if you have other comments to do on my code, they are welcomed, because i don't really know the good patterns to work with an ORM like that.
It seems that you need to add Include call:
public static List<StickyNote> GetStickyNotes()
{
List<StickyNote> notes;
using (var database = new NoteContext())
{
notes = database.Notes
.Include(n => n.Group)
.ToList();
}
return notes;
}
BTW you can return evaluated query result data (for example via ToList, ToArray, First and so on) from inside using statement:
public static List<StickyNote> GetStickyNotes()
{
using (var database = new NoteContext())
{
return database.Notes
.Include(n => n.Group)
.ToList();
}
}
I have the below simplified model:
public class Entity
{
public Guid Id { get; set; }
public IList<ComponentProperty> Components { get; private set; }
}
public class ComponentProperty
{
public string PropertyName { get; set; }
public string Value { get; set; }
}
I want to be able to find any entity whose property value string contains the search string, and to that end I wrote the following query, based on recommendation here, which implicitly uses Regex:
var query = _context.GetCollection<Entity>()
.AsQueryable()
.Where(t => t.Components.Any(c => c.Value.ToLower() == queryOptions.Filter));
This query produces the following json, and (incorrectly) returns 0 rows:
aggregate([{ "$match" : { "Components" : { "$elemMatch" : { "Value" : /^'green'$/i} } } }])
However, the hand-crafted query that produces the correct results is the below:
aggregate([{ "$match" : { "Components" : { "$elemMatch" : { "Value" : {$regex: '.*green.*' } } } } }])
Perhaps, there is something I have overlooked in my approach using the c# driver, and any pointers will be much appreciated.
change your where clause to this:
.Where(e => e.Components.Any(c => c.Value.ToLower().Contains(queryOptions.Filter)))
which produces this aggregation:
db.Entity.aggregate([
{
"$match": {
"Components.Value": /green/is
}
}
])
here's a test program:
using MongoDB.Entities;
using MongoDB.Entities.Core;
using System.Collections.Generic;
using System.Linq;
namespace StackOverFlow
{
public class Ntity : Entity
{
public IList<ComponentProperty> Components { get; set; }
}
public class ComponentProperty
{
public string PropertyName { get; set; }
public string Value { get; set; }
}
public static class Program
{
private static void Main()
{
new DB("test");
new Ntity
{
Components = new List<ComponentProperty> {
new ComponentProperty {
PropertyName = "test",
Value = "the RED color" }
}
}.Save();
new Ntity
{
Components = new List<ComponentProperty> {
new ComponentProperty {
PropertyName = "test",
Value = "the Green color" }
}
}.Save();
var result = DB.Queryable<Ntity>()
.Where(e => e.Components.Any(c => c.Value.ToLower().Contains("green")))
.ToList();
}
}
}
I have my models setup like this...
public class Model1 : IEquatable<Model1>
{
public int Model1Id { get; set; }
public string Name1 { get; set; }
public Model2 Model2 { get; set; }
public int Model2Id { get; set; }
public bool Equals(Model1 other)
{
return this.Model2.Equals(other.Model2)
&& this.Name1 == other.Name1;
}
}
public class Model2 : IEquatable<Model2>
{
public int Model2Id { get; set; }
public string Name2 { get; set; }
public bool Equals(Model2 other)
{
return this.Name2 == other.Name2;
}
}
public class ModelContext : DbContext
{
public DbSet<Model1> Model1 { get; set; }
public DbSet<Model2> Model2 { get; set; }
public ModelContext(DbContextOptions<ModelContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Model1>(b =>
{
b.HasOne(m1 => m1.Model2).WithMany().HasForeignKey(m1 => m1.Model2Id);
});
}
}
then I get a null reference exception when I do this...
static void Main(string[] args)
{
var myModel1 = new Model1
{
Name1 = "myName1",
Model2 = new Model2
{
Name2 = "myName2"
}
};
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
try
{
var options = new DbContextOptionsBuilder<ModelContext>()
.UseSqlite(connection)
.Options;
//create database
using(var ctx = new ModelContext(options))
{
ctx.Database.EnsureCreated();
}
//add model objects
using (var ctx = new ModelContext(options))
{
ctx.Database.EnsureCreated();
ctx.Model1.Add(myModel1);
ctx.SaveChanges();
}
//check if exists
using(var ctx = new ModelContext(options))
{
//exception here
bool isExists = ctx.Model1.Include(m1 => m1.Model2).Contains(myModel1);
Console.WriteLine(isExists);
}
}
finally
{
connection.Close();
}
Console.ReadKey();
}
I'm expeting the Model2 instance of my m1 to be populated when I call the Include but it is still null.
but If I add AsEnumerable() to my query like..
ctx.Model1.Include(m1 => m1.Model2).AsEnumerable().Contains(model1);
then everything works fine.
EDIT:
my question is... why do I need to call AsEnumerable()? I was expecting it to work without calling AsEnumerable()..
The difference is one is an entityframe work call the other is linq to objects
Entity Framework Does not understand contains for a CLR Object
public void AddIfNotExists(Model1 model1)
{
//No Need for the include this is executed in sql, assuming the model 2
//property has already been included in your model1 this should work fine
if(false == _context.Model1.Any(x => x.Name1 == model1.Name1
&& x.Model2.Name2 == model1.Model2.Name2))
{
_context.Model1.Add(model1);
}
}
I made this based off of your logic, but chances are you really just want to check if model1.id is the the model1 set. But I have no Idea what your architecture is doing so this is what you probably want