Creating constant objects in C# EF CodeFirst? - c#

I have a class "Stage" that contains an Id, Name, and TimeSpan. I need to create some constant Stages that can be referenced throughout my entire application. The Stage table and Stages should be read-only once these constants are defined.
The Stage class:
public class Stage
{
[Key]
public virtual int StageId { get; set; }
public string Name { get; set; }
public TimeSpan Span { get; set; }
}
I've tried the following (defining two constants):
public class Stage
{
[Key]
public virtual int StageId { get; set; }
public string Name { get; set; }
public TimeSpan Span { get; set; }
public static class Values
{
public static readonly Stage ONE = new Stage()
{
StageId = 0,
Name = "ONE",
Span = new TimeSpan(0, 0, 0)
};
public static readonly Stage TWO = new Stage()
{
StageId = 1,
Name = "TWO",
Span = new TimeSpan(0, 0, 10)
};
}
But whenever I create a new instance of an entity that has a Stage, a new Stage is added to the db. I just need a few constant stages.
Related entity constructor that's creating new instances when it should just be a reference to an existing stage (as defined above):
public class Side
{
public Side()
{
Stage = Stage.Values.ONE;
}
public virtual Stage Stage { get; set; }
}
How can I create a handful of constant objects and reference them via Stage.Values.One syntax?

You must attach Stage.Values.ONE and Stage.Values.TWO to the context instances where you are working with entities that reference those two constant entities in order to prevent that new Stage objects are created in the database, like
context.Stages.Attach(Stage.Values.ONE)
I would prefer though to have "context-local" constant entities instead of global static objects to avoid possible problems that can occur when they are attached to two different context instances at the same time. Something like:
public class MyContext : DbContext
{
//...
public Stage StageONE
{
get
{
var stage = this.Stages.Local.SingleOrDefault(s => s.StageId == 0);
if (stage == null)
{
stage = new Stage()
{
StageId = stageId,
Name = "ONE",
Span = new TimeSpan(0, 0, 0)
};
this.Stages.Attach(stage)
}
return stage;
}
}
}
To be used like so:
using (var context = new MyContext())
{
var side = new Side { Stage = context.StageONE };
context.Sides.Add(side);
context.SaveChanges();
}
But it doesn't seem to make much sense to me to store something in the database that you never read from there and only use hard-coded in your application. You could just make Side.Stage a byte property and use 0 and 1 for it.

Related

Loading instance of entity takes more than 1 second

I ran into one interesting thing in EF. If we get child entity using base entity, loading entities takes more time. My model looks like this:
public abstract class BaseDocument
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public abstract class ComplexDocument : BaseDocument
{
public string AuthorName { get; set; }
}
public abstract class SimpleDocument : BaseDocument
{
public int Level { get; set; }
}
public abstract class OfficeDocument : ComplexDocument
{
public string OfficeName { get; set; }
}
public abstract class ClassDocument : SimpleDocument
{
public string HeadName { get; set; }
}
public class WordDocument : OfficeDocument
{
public int PagesCount { get; set; }
}
public class ExcelDocument : OfficeDocument
{
public int SheetsCount { get; set; }
}
public class TextDocument : ClassDocument
{
public int LinesCount { get; set; }
}
I am using the TPT approach. Here is the inheritance tree
Here is my context class:
public class Context : DbContext
{
public Context() : base(#"Server=(localdb)\MSSQLLocalDB;Database=EFSIX;Trusted_Connection=True;")
{
Database.CreateIfNotExists();
}
public DbSet<BaseDocument> BaseDocuments { get; set; }
public DbSet<ComplexDocument> ComplexDocuments { get; set; }
public DbSet<SimpleDocument> SimpleDocuments { get; set; }
public DbSet<OfficeDocument> OfficeDocuments { get; set; }
public DbSet<ClassDocument> ClassDocuments { get; set; }
public DbSet<ExcelDocument> ExcelDocuments { get; set; }
public DbSet<WordDocument> WordDocuments { get; set; }
public DbSet<TextDocument> TextDocuments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseDocument>().ToTable("BaseDocuments");
modelBuilder.Entity<ComplexDocument>().ToTable("ComplexDocuments");
modelBuilder.Entity<SimpleDocument>().ToTable("SimpleDocuments");
modelBuilder.Entity<OfficeDocument>().ToTable("OfficeDocuments");
modelBuilder.Entity<ExcelDocument>().ToTable("ExcelDocuments");
modelBuilder.Entity<WordDocument>().ToTable("WordDocuments");
modelBuilder.Entity<ClassDocument>().ToTable("ClassDocuments");
modelBuilder.Entity<TextDocument>().ToTable("TextDocuments");
}
public IQueryable<T> GetEntities<T>() where T : class
{
return Set<T>();
}
}
I'm creating some data:
static void CreateTestData()
{
using (Context context = new Context())
{
for (int i = 0; i < 20; i++)
{
ExcelDocument excel = new ExcelDocument()
{
Id = Guid.NewGuid(),
AuthorName = $"ExcelAuthor{i}",
Name = $"Excel{i}",
OfficeName = $"ExcelOffice{i}",
SheetsCount = (i + 1) * 10
};
context.ExcelDocuments.Add(excel);
WordDocument word = new WordDocument()
{
Id = Guid.NewGuid(),
AuthorName = $"WordAuthor{i}",
Name = $"Word{i}",
OfficeName = $"WordOffice{i}",
PagesCount = (i + 2) * 10
};
context.WordDocuments.Add(word);
TextDocument text = new TextDocument()
{
Id = Guid.NewGuid(),
Name = $"Text{i}",
LinesCount = (i + 3) * 10,
HeadName = $"Head{i}",
Level = i + 5
};
context.TextDocuments.Add(text);
}
context.SaveChanges();
}
}
I made some two methods for getting WordDocument from db. One of them using BaseDocument and another one using WordDocument. Both returns 20 instances of WordDocument:
static long ReadBaseDoc()
{
using (Context context = new Context())
{
var words= context.GetEntities<BaseDocument>().Where(e => e.Name.StartsWith("Word"));
Stopwatch stopwatch = Stopwatch.StartNew();
var instacnes = excel.ToList();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}
static long ReadWordDoc()
{
using (Context context = new Context())
{
var words = context.GetEntities<WordDocument>().Where(e => e.Name.StartsWith("Word"));
Stopwatch stopwatch = Stopwatch.StartNew();
var instacnes = words.ToList();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}
I tested moth method separately, several times, in average method ReadWordDoc takes 25ms and method ReadBaseDoc takes 52ms (instances are the same ).
It's not too big problem now, but when we have complex inheritance it takes more than 1 second. I created 10 classes and inherited from BaseDocument. After that I executed ReadBaseDoc and ReadWordDoc methods. ReadWordDoc took 25ms and ReadBaseDoc took 1023ms. Instances are the same, why ReadBaseDoc takes more time? What is the better way to avoid this kind of problems in EF?
Take a look here. There are ways to make EF faster, but in those complex scenarios ORM just creates more problems than it solves.
One way in your case would be to try to change the inheritance to TablePerType, MAYBE it will be a little bit faster.
Other way would be to locate the slow request and use Dapper for them - it will be much faster.
Last way would be to create a Repository with live cache that loads the full database into memory and keeps it up to date - this should be a singleton in an app. If you have more than one app using the same database, you need to hookup data change triggers.
In general, I would say for slow (and relatively simple) queries like yours, use Dapper + AutoMapper. Keep EF so that your database stays synchronized with your classes, but do not rely on it for queries.
If you really want to stick to ORM, I think you need to switch nHibernate. Haven't try it myself, but form what I read, it is superior in almost every possible way, that includes performance and startup time.

How to access properties inside multiple model

When joining multiple models, I can't access its properties in controller.
public class BirdModel
{
public IEnumerable<BirdFile> BirdFils { get; set; }
public IEnumerable<BirdFileDetail> BirdFileDetails { get; set; }
}
public partial class BirdFile
{
public int ID{ get; set; }
public string Name{ get; set; }
}
Is it possible to access like this
BirdModel b = new BirdModel();
b.BirdFile.ID
You problem with b.BirdFile.ID is that you are trying to access the property or a collection of objects that you have not initialised.
You need to create an instance of the encapsulating class, BirdModel then create an instance of your BirdFile collection and add values to it. From there you can get the specific "BirdFile" within your collection via iteration and then access its properties.
A small example below:
class Program
{
static void Main(string[] args)
{
var bm = new BirdModel();
bm.BirdFils = new List<BirdFile>
{
new BirdFile {ID = 1, Name = "Bird A"},
new BirdFile {ID = 2, Name = "Bird B"}
};
bm.BirdFils.ToList().ForEach(x => Console.WriteLine($"Name: {x.Name}, ID: {x.ID}"));
Console.ReadKey();
}
}
public class BirdModel
{
public IEnumerable<BirdFile> BirdFils { get; set; }
}
public partial class BirdFile
{
public int ID { get; set; }
public string Name { get; set; }
}
BirdModel contains a collection of BirdFile, so to access them you should write something like:
// create a new model
BirdModel b = new BirtdModel()
// create the instance of BirdFile list
b.BirdFils = new List<BirdFile>()
// add an item (just an example)
b.BirdFils.Add(new BirdFile{ ID = 1, Name = "Bird1"}
// Access to the previously created BirdFile
BirdFile bf = b.BirdFils[0]

Can't insert entity with foreign key in Entity Framework

I'm building Backend for Mobile Application with ASP.NET MVC Framework.
I have two Objects:
public class CarLogItem : EntityData
{
public CarLogItem(): base()
{
Time = DateTime.Now;
}
public DateTime Time { get; set; }
public int RPM { get; set; }
public int Speed { get; set; }
public int RunTime { get; set; }
public int Distance { get; set; }
public int Throttle { get; set; }
[ForeignKey("Trip")]
public String Trip_id { get; set; }
// Navigation property
public TripItem Trip { get; set; }
}
and
public class TripItem : EntityData
{
public TripItem() : base()
{
UserId = User.GetUserSid();
StartTime = DateTime.Now;
logItems = new List<CarLogItem>();
}
public string UserId { get; set; }
public List<CarLogItem> logItems {get;set;}
public DateTime StartTime { get; set; }
}
and I have controller, which add new CarLogItem to database.
public class CarLogItemController : TableController<CarLogItem>
{
// POST tables/CarLogItem
public async Task<IHttpActionResult> PostCarLogItem(CarLogItem item)
{
var lastItem = db.CarLogItems.OrderByDescending(x => x.Time).FirstOrDefault();
//lastItem = (Query().Where(logitem => true).OrderBy(logitem => logitem.Time)).Last();
//checking if lastItem.Trip isn't null because
// I have entities with Trip field is null, but all of them should have it.
if (lastItem != null && lastItem.Trip != null && item.RunTime > lastItem.RunTime)
{
item.Trip = lastItem.Trip;
}
//In order to test adding of new TripItem entity to database
// I compare item.RunTime with 120, so it always true
else if (lastItem == null || item.RunTime < 120) // < lastItem.RunTime)
{
var newTrip = new TripItem();
item.Trip = newTrip;
}
else
{
throw new ArgumentException();
}
CarLogItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
}
When I'm trying to add new CarLogItem with Trip = null it's ok, but when Trip is particular object it fails with following Exception:
The entity submitted was invalid: Validation error on property 'Id': The Id field is required
How properly to add new CarLogItem with nested TripItem?
I think that you need to populate the Id property on your TripItem, e.g.
var newTrip = new TripItem(){ Id = Guid.NewGuid() }
You need a primary key field in every entity class, like Id or CarLogItemId (ClassName + "Id"). Or just have a property with [Key] attribute:
[Key]
public string/int/Guid/any-db-supported-type MyProp { get; set; }
Entity Framework relies on every entity having a key value that it
uses for tracking entities. One of the conventions that code first
depends on is how it implies which property is the key in each of the
code first classes. That convention is to look for a property named
“Id” or one that combines the class name and “Id”, such as “BlogId”.
The property will map to a primary key column in the database.
Please see this for more details.
I also suspect this to be a problem:
public Lazy<CarLogItem> logItems { get; set; }
You don't have to mark navigation property as Lazy<>. It is already lazy (unless you have configuration that disables lazy loading). Please try to remove Lazy<> and see if it works this way.

Saving primary and foreign keytables at once in EF6

I have primary table and 3 foreign key tables and trying to save all at once. Some time some OK and some time giving error.
public partial class meeting_abstract
{
public int meeting_abstract_id { get; set; }
public int meeting_id { get; set; }
public System.DateTime submission_date { get; set; }
public virtual ICollection<abstract_author> abstract_author { get; set; }
public virtual ICollection<abstract_category> abstract_category { get; set; }
public virtual ICollection<abstract_questions> abstract_questions { get; set; }
}
var meeting_abstract = new meeting_abstract();
meeting_abstract.meeting_id = meetingAbstract.Meeting.meeting_id;
meeting_abstract.submission_date = DateTime.Now;
meeting_abstract.abstract_questions = new Collection<abstract_questions>();
var abstractQuestion = new abstract_questions();
abstractQuestion.meeting_question_id = Convert.ToInt32(meetingAbstract.AbstractTitleInEnglishId);
abstractQuestion.abstract_question_answer = meetingAbstract.AbstractTitleInEnglishText;
meeting_abstract.abstract_questions.Add(abstractQuestion);
abstractQuestion = new abstract_questions();
abstractQuestion.meeting_question_id = Convert.ToInt32(meetingAbstract.AbstractTitleInLanguageId);
abstractQuestion.abstract_question_answer = meetingAbstract.AbstractTitleInLanguageText;
meeting_abstract.abstract_questions.Add(abstractQuestion);
meeting_abstract.abstract_author.Add(meetingAbstract.primaryAuthor);
var abstractCategory = new abstract_category()
{
meeting_category_id = meetingCategory.meeting_category_id
};
meeting_abstract.abstract_category = new Collection<abstract_category>();
meeting_abstract.abstract_category.Add(abstractCategory);
_abstractRewriteEntities.meeting_abstract.Add(meeting_abstract);
_abstractRewriteEntities.SaveChanges();
Whats wrong here? I cannot save all together?
When adding more than one item, with a property defined as the identity, you will need to give those entities a unique key even though it will ultimately be set by the db. We do this in our project by defining a partial class for that entity, and in OnCreated making sure they have a unique value for their identity.
For example:
public partial class abstract_questions
{
private static int _newIdentity = 0;
partial void OnCreated()
{
//Set an identity value so when two new entities
// are created Entity Framework doesn't whinge
// on the server because of duplicate keys
_newIdentity = _newIdentity - 1;
// Set whatever is defined as the identity/pk property
this.Id = _newIdentity;
}

Not updating discriminator when property type changes

I've got an entity that has a property that's an abstract type. This creates a one-to-one relationship that uses table-per-hierarchy inheritance. Everything seems like it's working correctly.
I can create an Item and set the Base property to ConcreteOne; everything saves correctly. However, when I try to update Base to ConcreteTwo, EF updates the Base record in the database with the new user value, it doesn't update the discriminator for the type. So the extra data for ConcreteTwo gets persisted, but the discriminator still says ConcreteOne.
The following is a simple example that exposes the problem
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
App_Start.EntityFrameworkProfilerBootstrapper.PreStart();
Database.SetInitializer(new DropCreateDatabaseAlways<DataContext>());
// Create our item with ConcreteOne for Base
using (var context = new DataContext())
{
var item = new Item
{
Base = new ConcreteOne { Name = "Item", Data = 3 }
};
context.Items.Add(item);
context.SaveChanges();
}
// Update Base with a new ConcreteTwo
using (var context = new DataContext())
{
var item = context.Items.FirstOrDefault();
var newBase = new ConcreteTwo()
{
Item = item,
Name = "Item 3",
User = new User { Name = "Foo" }
};
// If I don't set this to null, EF tries to create a new record in the DB which causes a PK exception
item.Base.Item = null;
item.Base = newBase;
// EF doesn't save the discriminator, but DOES save the User reference
context.SaveChanges();
}
// Retrieve the item -- EF thinks Base is still ConcreteOne
using (var context = new DataContext())
{
var item = context.Items.FirstOrDefault();
Console.WriteLine("{0}: {1}", item.Name, item.Base.Name);
}
Console.WriteLine("Done.");
Console.ReadLine();
}
}
public class DataContext : DbContext
{
public DbSet<Item> Items { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Base Base { get; set; }
}
public abstract class Base
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public virtual Item Item { get; set; }
}
public class ConcreteOne : Base
{
public int Data { get; set; }
}
public class ConcreteTwo : Base
{
public virtual User User { get; set; }
}
}
When the changes are saved, EF generates the following SQL:
update [dbo].[Bases]
set [Name] = 'Item 3' /* #0 */,
[User_Id] = 1 /* #1 */
where (([Id] = 1 /* #2 */)
and [User_Id] is null)
So it's almost correct, but I'd expect to see [Discriminator] = 'ConcreteTwo' in the update statement. Are my expectations unfounded or am I doing something wrong?
As a test, I tried using table-per-type and the the entry was removed from the ConcreteOne table and added to the ConcreteTwo table as I would expect. So it works, but my real application has at least seven sub-types and the SQL statement to retrieve the Base property got really nasty. So I'd certainly like to accomplish this using TPH, if possible.
Update:
I've verified that the problem exists in EF5 as well as EF6.
This question is based on the expectation of an update taking place, which is seemingly a debatable expectation. Currently your best bet if the TPH hierarchy is not functioning as expected, and considering EF6 is currently in beta, is to start a discussion on the Codeplex forums.
Add this to your model:
public enum BaseType
{
ConcreteOne = 1,
ConcreteTwo = 2
}
public abstract class Base
{
...
public BaseType BaseType { get; set; }
...
}
And in the OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Base>()
.ToTable("Base");
modelBuilder.Entity<ConcreteOne>()
.Map(t => t.Requires(m => m.BaseType).Equals(BaseType.ConcreteOne))
.ToTable("ConcreteOne");
modelBuilder.Entity<ConcreteTwo>()
.Map(t => t.Requires(m => m.BaseType).Equals(BaseType.ConcreteTwo))
.ToTable("ConcreteTwo");
}
I would expect this to create a new instance (record) with a ConcreteTwo discriminator.
using (var context = new DataContext())
{
var item = context.Items.FirstOrDefault();
var newBase = new ConcreteTwo()
{
Name = "Item 3",
User = new User { Name = "Foo" }
};
item.Base = newBase;
context.SaveChanges();
}

Categories