Performance issue regarding EF 4.1, code first and TPH - entity-framework

I'm trying to create a datamodel using EF 4.1 and code first.
My Poco classes looks like this:
public abstract class AttributeBase
{
public int Id { get; set; }
public string Name { get; set; }
public string AttributeDescription { get; set; }
public int Length { get; set; }
public bool AllowNull { get; set; }
public bool DisplayInWeb { get; set; }
}
public class StringListAttribute : AttributeBase
{
public ICollection<StringValue> Values { get; set; }
public StringListAttribute()
{
Values = new List<StringValue>();
}
}
public class StringValue
{
public int StringValueId { get; set; }
public string Value { get; set; }
}
The basic idea is that StringListAttribute inherits from the AttributeBase class (other classes also inherits from the AttributeBase class, but I didn't include them in this post)
The StringListAttribute class have a collection of StringValues. (a zero-to-many relationship). What I try to achieve in my MyContextExtention class is that whenever I want a list of AttributeBase objects from the database, I want to get all the StringListAttribute objects and to populate their StringValue collection in one go. I don't want any lazy loading. I want to retrieve as much data I can in as few queries as possible.
My Context class inherits from DbContext and I created an extension method on my BraArkivContext class
public class MyContext : DbContext
{
public DbSet<AttributeBase> Attributes { get; set; }
public MyContext()
{
Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<StringListAttribute>()
.HasMany(t=>t.Values).WithOptional().WillCascadeOnDelete();
base.OnModelCreating(modelBuilder);
}
}
public static class MyContextExtension
{
private const string Values = "Values";
public static List<AttributeBase> AttributesWithData1(this MyContext context)
{
var attributeBases = new List<AttributeBase>();
var stringListAttributes = context.Attributes.Include(Values).OfType<StringListAttribute>().ToList();
attributeBases.AddRange(stringListAttributes);
return attributeBases;
}
}
My test project (I'm using nUnit) )is nothing much to write home about and contains these methods:
public class TestHelper
{
public static StringListAttribute CreateStringListAttribute(string name, int listSize)
{
var listAttribute = new StringListAttribute();
listAttribute.Name = name;
for (int i = 0; i < listSize; i++)
{
var attr1 = new StringValue();
attr1.Value = String.Format("StringValue_{0}", i);
listAttribute.Values.Add(attr1);
}
return listAttribute;
}
}
[Test]
public void AttributeBase_GetAllAttributesWithData1()
{
//First add some data to the database
var numberOfObjects = 100;
var name = "StringListAttribute_ReadMultipleStringsListsFromDb_" + Guid.NewGuid().ToString();
var listAttributes = TestHelper.CreateStringLists(name, numberOfObjects);
using (var context = new MyContext())
{
foreach (var stringListAttribute in listAttributes)
{
context.Attributes.Add(stringListAttribute);
}
context.SaveChanges();
}
Stopwatch sw = new Stopwatch()
var attributes = new List<AttributeBase>();
using (var context = new MyContext())
{
sw.Start();
attributes = context.AttributesWithData1().ToList();
sw.Stop();
}
Debug.WriteLine(String.Format("Total time for {0} attributes is: {1}.", attributes.Count, sw.Elapsed));
Assert.IsNotNull(attributes);
}
When running the test method "AttributeBase_GetAllAttributesWithData1" The sql statement that is executed looks like this:
SELECT
[Project1].[Id] AS [Id],
[Project1].[C1] AS [C1],
[Project1].[Name] AS [Name],
[Project1].[AttributeDescription] AS [AttributeDescription],
[Project1].[Length] AS [Length],
[Project1].[AllowNull] AS [AllowNull],
[Project1].[DisplayInWeb] AS [DisplayInWeb],
[Project1].[Document_Id] AS [Document_Id],
[Project1].[C2] AS [C2],
[Project1].[StringValueId] AS [StringValueId],
[Project1].[Value] AS [Value],
[Project1].[StringListAttribute_Id] AS [StringListAttribute_Id]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[AttributeDescription] AS [AttributeDescription],
[Extent1].[Length] AS [Length],
[Extent1].[AllowNull] AS [AllowNull],
[Extent1].[DisplayInWeb] AS [DisplayInWeb],
[Extent1].[Document_Id] AS [Document_Id],
'0X0X' AS [C1],
[Extent2].[StringValueId] AS [StringValueId],
[Extent2].[Value] AS [Value],
[Extent2].[StringListAttribute_Id] AS [StringListAttribute_Id],
CASE WHEN ([Extent2].[StringValueId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
FROM [dbo].[AttributeBases] AS [Extent1]
LEFT OUTER JOIN [dbo].[StringValues] AS [Extent2] ON [Extent1].[Id] = [Extent2].[StringListAttribute_Id]
WHERE [Extent1].[Discriminator] = 'StringListAttribute'
) AS [Project1]
ORDER BY [Project1].[Id] ASC, [Project1].[C2] ASC
When I run this sql statement in SQL Server Management Studio it's quite fast, but the Entity Framework is taking a very long time to process the result and return the poco classes populated with data.
Is there any other way to model the AttributeBase, StringListAttribute and StringValue classes and the relation between them to decrease the processing time in EF?

Related

EF Core Queryable<T>. Count() returns different number than Queryable<T> .ToList().Count(). Is this even possible or is it a bug?

I have a query that grabs some filtered data, but it's giving me some strange results. See the attached image with the VS Code debugger (the var sourceis a Queryable, something like _dbContext.ModelName)
var count= await source.CountAsync();
is giving a different result than
var count2 = (await source.ToListAsync()).Count();
How is this even possible? With these results, everything I thought I knew about EF becomes a lie.
The same is true for the sync methods.
Can anyone explain to me in which scenario is this possible? Could it be a bug in EF Core 3.1?
Context of the program: side project, DataBase is not accessed by anyone, just by me. There is no other operations in this scenario
edit : the variable source has an Include, so it is _dbContext.ModelName.Include(b=>b.OtherModel). When I remove the Include, it works.
edit2 The ModelName.OtherModel property is null in some cases, but OtherModel.Id (the primary key) cannot be null, so, I guess, when Include performs the Join, excludes the occurrences of ModelName that haven't an OtherModel. Could be this?
Under normal circumstances, with referential integrity intact, this cannot happen.
Take a look at the following code, where both count operations will correctly return a result of 3:
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class IceCream
{
public int IceCreamId { get; set; }
public string Name { get; set; }
public int IceCreamBrandId { get; set; }
public IceCreamBrand Brand { get; set; }
}
public class IceCreamBrand
{
public int IceCreamBrandId { get; set; }
public string Name { get; set; }
public virtual ICollection<IceCream> IceCreams { get; set; } = new HashSet<IceCream>();
}
public class Context : DbContext
{
public DbSet<IceCream> IceCreams { get; set; }
public DbSet<IceCreamBrand> IceCreamBrands { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseMySql(
"server=127.0.0.1;port=3306;user=root;password=;database=So63071963",
b => b.ServerVersion("8.0.20-mysql"))
//.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63071963")
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IceCream>()
.HasData(
new IceCream {IceCreamId = 1, Name = "Vanilla", IceCreamBrandId = 1},
new IceCream {IceCreamId = 2, Name = "Chocolate", IceCreamBrandId = 2},
new IceCream {IceCreamId = 3, Name = "Matcha", IceCreamBrandId = 3});
modelBuilder.Entity<IceCreamBrand>()
.HasData(
new IceCreamBrand {IceCreamBrandId = 1, Name = "My Brand"},
new IceCreamBrand {IceCreamBrandId = 2, Name = "Your Brand"},
new IceCreamBrand {IceCreamBrandId = 3, Name = "Our Brand"});
}
}
internal static class Program
{
private static void Main()
{
//
// Operations with referential integrity intact:
//
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
// Does not use INNER JOIN. Directly uses COUNT(*) on `IceCreams`:
// SELECT COUNT(*)
// FROM `IceCreams` AS `i`
var databaseSideCount = context.IceCreams
.Include(s => s.Brand)
.Count();
// Does use INNER JOIN. Counts using Linq:
// SELECT `i`.`IceCreamId`, `i`.`IceCreamBrandId`, `i`.`Name`, `i0`.`IceCreamBrandId`, `i0`.`Name`
// FROM `IceCreams` AS `i`
// INNER JOIN `IceCreamBrands` AS `i0` ON `i`.`IceCreamBrandId` = `i0`.`IceCreamBrandId`
var clientSideCount = context.IceCreams
.Include(s => s.Brand)
.AsEnumerable() // or ToList() etc.
.Count();
Debug.Assert(databaseSideCount == 3);
Debug.Assert(clientSideCount == 3);
Debug.Assert(databaseSideCount == clientSideCount);
}
}
}
Here it is also not possible to damage the referential integrity, because it is guarded by a foreign key constraint in the database.
If you create your database on your own however (using a custom crafted SQL script) and leave out the foreign key constraint, but still let EF Core believe that there is one in place, and then violate the referential integrity by using a non existing ID in a foreign key column, you can get different results for database-side (here 3) and client-side (here 2) count operations:
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class IceCream
{
public int IceCreamId { get; set; }
public string Name { get; set; }
public int IceCreamBrandId { get; set; }
public IceCreamBrand Brand { get; set; }
}
public class IceCreamBrand
{
public int IceCreamBrandId { get; set; }
public string Name { get; set; }
public virtual ICollection<IceCream> IceCreams { get; set; } = new HashSet<IceCream>();
}
public class Context : DbContext
{
public DbSet<IceCream> IceCreams { get; set; }
public DbSet<IceCreamBrand> IceCreamBrands { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseMySql(
"server=127.0.0.1;port=3306;user=root;password=;database=So63071963",
b => b.ServerVersion("8.0.20-mysql"))
//.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63071963")
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
}
internal static class Program
{
private static void Main()
{
//
// Operations with referential integrity violated:
//
using var context = new Context();
// Manually create MySQL database with a missing reference between
// the Matcha ice cream and any brand.
context.Database.ExecuteSqlRaw(
#"
DROP DATABASE IF EXISTS `So63071963`;
CREATE DATABASE `So63071963`;
USE `So63071963`;
CREATE TABLE `IceCreamBrands` (
`IceCreamBrandId` int NOT NULL AUTO_INCREMENT,
`Name` longtext CHARACTER SET utf8mb4 NULL,
CONSTRAINT `PK_IceCreamBrands` PRIMARY KEY (`IceCreamBrandId`)
);
CREATE TABLE `IceCreams` (
`IceCreamId` int NOT NULL AUTO_INCREMENT,
`Name` longtext CHARACTER SET utf8mb4 NULL,
`IceCreamBrandId` int NOT NULL,
CONSTRAINT `PK_IceCreams` PRIMARY KEY (`IceCreamId`)
);
INSERT INTO `IceCreamBrands` (`IceCreamBrandId`, `Name`) VALUES (1, 'My Brand');
INSERT INTO `IceCreamBrands` (`IceCreamBrandId`, `Name`) VALUES (2, 'Your Brand');
INSERT INTO `IceCreams` (`IceCreamId`, `IceCreamBrandId`, `Name`) VALUES (1, 1, 'Vanilla');
INSERT INTO `IceCreams` (`IceCreamId`, `IceCreamBrandId`, `Name`) VALUES (2, 2, 'Chocolate');
/* Use non-existing brand id 0: */
INSERT INTO `IceCreams` (`IceCreamId`, `IceCreamBrandId`, `Name`) VALUES (3, 0, 'Matcha');
");
// Does not use INNER JOIN. Directly uses COUNT(*) on `IceCreams`:
// SELECT COUNT(*)
// FROM `IceCreams` AS `i`
var databaseSideCount = context.IceCreams
.Include(s => s.Brand)
.Count();
// Does use INNER JOIN. Counts using Linq:
// SELECT `i`.`IceCreamId`, `i`.`IceCreamBrandId`, `i`.`Name`, `i0`.`IceCreamBrandId`, `i0`.`Name`
// FROM `IceCreams` AS `i`
// INNER JOIN `IceCreamBrands` AS `i0` ON `i`.`IceCreamBrandId` = `i0`.`IceCreamBrandId`
var clientSideCount = context.IceCreams
.Include(s => s.Brand)
.AsEnumerable() // or ToList() etc.
.Count();
Debug.Assert(databaseSideCount == 3);
Debug.Assert(clientSideCount == 2);
Debug.Assert(databaseSideCount != clientSideCount);
}
}
}

How To Insert Data In FluentAPI Mapping Table

I have a A Table, B Table and AB (Mapping Table)
A
public class A
{
public int AID{ get; set; }
[JsonIgnore]
public virtual ICollection<B> Bs { get; set; }
}
B
public class B
{
public int BID { get; set; }
[JsonIgnore]
public virtual ICollection<A> As { get; set; }
}
ApplicationDbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<B>()
.HasMany(s => s.As)
.WithMany(c => c.Bs)
.Map(cs =>
{
cs.MapLeftKey("AID");
cs.MapRightKey("BID");
cs.ToTable("AB");
});
}
Now things are perfectly fine, but how do I insert in this AB Mapping table?
If I try to create AB as like below, it generates two tables, AB and AB1 with same column name and all.
public class AB
{
public int ABID { get; set; }
public string AID { get; set; }
public int BID { get; set; }
}
So is there any way to do CRUD in FluentAPI Mapping Table?
If not, then can I force FluentAPI to map from Existing table? In this case I'll manually manage Employee and will change the mapping code to use existing table.
I'm unable to find any of the solution.
Edit: Since the question was changed, I'm writing up a more thorough answer. The answer to your question remains the same, however:
Now things are perfectly fine, but how do I insert in this AB Mapping
table?
You don't!
This is exactly the kind of thing that EF is good at. Instead of managing a link table yourself, now you just end up with the actual object you want. So, if you want to add a link between an A and B, all you do is add a B to the Bs collection on that A. You don't ever insert directly into the AB table, because who cares about that? That table is there so we can have relationships between different As and Bs, that's it. So, Entity Framework will create the table for it's own use, but not present it to you, because that's not how EF works: you work with your objects and let EF handle the database.
That's why when you try to define the table yourself, it creates two: it's already making a table called AB, but you're asking for another one. It can't have exactly the same name so it appends a '1' to the end of it. Since you've already used FluentAPI to define the apping, let EF worry about how to implement the mapping: all you need to care about is that you've now got a way to have an A with a set of Bs, or vice versa.
Since this still sounds confusing with names 'A' and 'B', below is the Program class for a console app that will illustrate this; all you need to do is start a fresh console app, replace the Program class with this one, install the entity framework package, and run enable-migrations -enableautomaticmigrations -force. I recommend you use this to add some objects and relate them, and then go have a look at your database: you will see the 'AB' table, with records that were added. This might help explain it better.
class Program
{
static bool quit = false;
static void Main(string[] args)
{
string s = "Please select an option:" +
"\n1: Insert an A" +
"\n2: Insert a B" +
"\n3: Add a B to an A" +
"\n4: Add an A to a B" +
"\n5: Print all As" +
"\n6: Print all Bs" +
"\n7: Print AB Table" +
"\nx: Quit.";
while (!quit)
{
Console.WriteLine();
Console.WriteLine(s);
var k = Console.ReadKey();
DoStuff(k);
}
}
private static void DoStuff(ConsoleKeyInfo i)
{
switch (i.Key)
{
case ConsoleKey.D1:
//add an A
AddA(GetName());
break;
case ConsoleKey.D2:
//add a B
AddB(GetName());
break;
case ConsoleKey.D3:
// link a B to an A
LinkB(GetBtoLink(),GetAtoLink());
break;
case ConsoleKey.D4:
//link an A to an B
LinkA(GetAtoLink(), GetBtoLink());
break;
case ConsoleKey.D5:
// print As
WriteA();
break;
case ConsoleKey.D6:
//print Bs
WriteB();
break;
case ConsoleKey.D7:
// print AB
WriteAB();
break;
case ConsoleKey.X:
quit = true;
break;
}
}
private static int GetAtoLink()
{
string x;
int z;
do
{
Console.Clear();
Console.WriteLine("Please enter the ID of the A you want to use and then press enter.");
WriteA();
x = Console.ReadLine();
} while (!int.TryParse(x, out z));
return z;
}
private static int GetBtoLink()
{
string x;
int z;
do
{
Console.Clear();
Console.WriteLine("Please enter the ID of the B you want to use and then press enter.");
WriteB();
x = Console.ReadLine();
} while (!int.TryParse(x, out z));
return z;
}
private static void WriteB()
{
Console.WriteLine("{0,10}{1,15}", "ID", "Name");
using (var db = new Context())
{
foreach (var a in db.Bs)
{
Console.WriteLine("{0,10}{1,15}", a.BID, a.Name);
}
}
}
private static void WriteA()
{
Console.WriteLine("{0,10}{1,15}", "ID", "Name");
using (var db = new Context())
{
foreach (var a in db.As)
{
Console.WriteLine("{0,10}{1,15}", a.AID, a.Name);
}
}
}
private static void WriteAB()
{
Console.WriteLine("{0,10}{1,10}", "AID", "BID");
using (var db = new Context())
{
// this is the only way we need to do this, because it's many to many,
// if an A is linked to a B, then that B is by definition linked to that A as well.
foreach (var a in db.As)
{
foreach (var b in a.Bs)
{
Console.WriteLine("{0,10}{1,10}", a.AID, b.BID);
}
}
}
}
private static void LinkB(int bToUse, int aToUse)
{
using (var db = new Context())
{
var a = db.As.First(x => x.AID == aToUse);
var b = db.Bs.First(y => y.BID == bToUse);
a.Bs.Add(b);
db.SaveChanges();
}
}
private static void LinkA(int aToUse, int bToUse)
{
using (var db = new Context())
{
var a = db.As.First(x => x.AID == aToUse);
var b = db.Bs.First(y => y.BID == bToUse);
b.As.Add(a);
db.SaveChanges();
}
}
private static string GetName()
{
Console.WriteLine("Please enter a name");
return Console.ReadLine();
}
private static void AddA(string input)
{
using (var db = new Context())
{
db.As.Add(new A {Name = input});
db.SaveChanges();
}
}
private static void AddB(string input)
{
using (var db = new Context())
{
db.Bs.Add(new B { Name = input });
db.SaveChanges();
}
}
}
public class A
{
public int AID { get; set; }
public string Name { get; set; }
public virtual ICollection<B> Bs { get; set; }
}
public class B
{
public int BID { get; set; }
public string Name { get; set; }
public virtual ICollection<A> As { get; set; }
}
public class Context : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<B>()
.HasMany(s => s.As)
.WithMany(c => c.Bs)
.Map(cs =>
{
cs.MapLeftKey("AID");
cs.MapRightKey("BID");
cs.ToTable("AB");
});
}
public DbSet<A> As { get; set; }
public DbSet<B> Bs { get; set; }
}
Old Answer: You've defined an ICollection<ApplicationUser> called Employees in Company, and mapped to it with FluentAPI. This creates a table called 'Employees' as expected. You don't have to create another class called Employees; as far as Entity Framework is concerned, you've already told it to create a table called Employees. This is why
I think the step you're missing is defining your DbSet<>.
Using your code, and running Add-Migration, this is the definition I get for the Employees table:
CreateTable(
"dbo.Employees",
c => new
{
UserID = c.Int(nullable: false),
CompanyID = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.UserID, t.CompanyID })
.ForeignKey("dbo.ApplicationUsers", t => t.UserID, cascadeDelete: true)
.ForeignKey("dbo.Companies", t => t.CompanyID, cascadeDelete: true)
.Index(t => t.UserID)
.Index(t => t.CompanyID);
Which seems to correlate with what you wanted.
To finish it off, add (if you haven't already) this to your ApplicationDbContext file:
public DbSet<ApplicationUser> Employees;
public DbSet<Company> Companies;
Then to add an employee, you create a new ApplicationUser and add it like
ApplicationUser user = new ApplicationUser();
// do whatever here to give it the right data
ApplicationDbContext ctx = new ApplicationDbContext();
ctx.Employees.Add(user);
The Employees table itself you shouldn't ever have to interact with.
EF will manage that you don't need to insert into the mapping table directly, have a look at this sample that I have in my project:
public class Organization : Entity<int>
{
public string Name { get; set; }
public string Address { get; set; }
public string MainContact { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
//navigation property
public virtual ICollection<DevelopmentalGoal> DevelopmentalGoals { get; set; }
public virtual ICollection<ServiceActivity> ServiceActivities { get; set; }
}
public class DevelopmentalGoal : Entity<int>
{
public string Name { get; set; }
public string Icon { get; set; }
//navigation property
public virtual ICollection<Organization> Organizations { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Organization>().ToTable("Organization", "ServiceLearning")
.HasKey(t => t.ID);
modelBuilder.Entity<DevelopmentalGoal>().ToTable("DevelopmentalGoal", "ServiceLearning")
.HasKey(t => t.ID);
modelBuilder.Entity<Organization>()
.HasMany(t => t.DevelopmentalGoals)
.WithMany(t=> t.Organizations)
.Map(m =>
{
m.ToTable("OrganizationDevelopmentalGoal", "ServiceLearning");
m.MapLeftKey("OrganizationID");
m.MapRightKey("DevelopmentalGoalID");
});
}
public int SaveOrganization(OrganizationViewModel viewModel, IUserContext currentUser)
{
Organization organization;
{
if (viewModel.ID == 0)
{
organization = ObjectMapper.MapTo<Organization>(viewModel);
_context.Set<Organization>().Add(organization);
}
else
{
organization = _context.Set<Organization>()
.SingleOrDefault(t =>
t.ID == viewModel.ID
);
organization.Name = viewModel.Name;
organization.Address = viewModel.Address;
organization.MainContact = viewModel.MainContact;
organization.Phone = viewModel.Phone;
organization.Website = viewModel.Website;
UpdateOrganizationDevelopmentalGoals(organization, viewModel);
}
try
{
CommitChanges();
}
catch (DbUpdateException ex)
{
if (ex.IsDuplicateException())
throw new KeystoneDuplicateException("A Organization with the same name already exists.");
throw ex;
}
}
return organization.ID;
}
private void UpdateOrganizationDevelopmentalGoals(Organization organization, OrganizationViewModel viewModel)
{
var originalIdList = organization.DevelopmentalGoals.Select(d => d.ID).Distinct().ToList();
var modifiedIdList = viewModel.DevelopmentalGoal.Where(d => d.Selected == true).Select(d => d.ID).Distinct().ToList();
//Remove deleted Developmetal Goals.
foreach (var id in originalIdList.Except(modifiedIdList))
organization.DevelopmentalGoals.Remove(organization.DevelopmentalGoals.Single(d => d.ID == id));
//Add new Developmetal Goals.
foreach (var id in modifiedIdList.Except(originalIdList))
{
//Add director relationship without having to load entity.
var d = new DevelopmentalGoal { ID = id };
_context.Set<DevelopmentalGoal>().Attach(d);
organization.DevelopmentalGoals.Add(d);
}
}
As you can see in the UpdateOrganizationDevelopmentalGoals method I do not insert or delete data from the mapping table directly, I insert and delete from the organization.DevelopmentalGoals and as I've already defined the mapping table in fluent API on "OnModelCreating" then EF knows how to manage the relations.

Using sets of Entity Framework entities at runtime

I have an EF6 setup against a sql server db with about 60 tables in it.
I have entities for each table. What i'm trying to do is run the same method against a set of these entities that will be known at runtime.
The method is a qa/qc routine that does some data check on particular fields that are assured to be in each table.
I guess what i want to do is make the entity a parameter to the method so i can call it consecutive times.
I would also want to make a set of entities to pass as the parameter.
something like this:
List<string> entList = new List<string>(){"Table1","Table2","Table3"};
foreach (entName in entList)
{
//create an entity with the string name
//call myQAQCMethod with the entity
}
MyQAQCMethod (entity SomeEntity)
{
//run against this entity
doQAQC(SomeEntity);
}
Can this be done? Is it a job for reflection?
EDIT
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
is precisely want i want to do. The thing is I want to avoid typing this loop 60 times. I think i'm looking for a way to "feed" a set of entities to this single method.
Also, thank you very much for helping me. I'm learning a lot.
You need to abstract an interface (entity framework won't even notice):
interface IQaQcable
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
public class EntityA : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
public class EntityB : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
// in some unknown utility class
void MyQaQcMethod<T>(T entity) where T : IQaQcable
{
doSomethingWithIQaQcableProperties(entity.CommonInt, entity.CommonString);
}
// in some unknown test class
void Test()
{
var entities = new List<IQaQcable> { new EntityA(), new EntityB() };
foreach (var e in entities)
MyQaQcMethod(e);
}
Now, you could extract a base class from which each derives that actually implements the CommonInt and CommonString properties for each entity needing them, but that can get kind of tricky with Table-Per-Type/Table-Per-Hierarchy, so I'd start with this, and then consider introducing either an abstract or concrete base class as an improvement.
EDIT
Maybe your looking for something simpler than I first thought, based on your last comment.
Let's give ourselves what the DbContext for this might look like:
class Context : DbContext
{
public virtual DbSet<EntityA> EntityAs { get; set; }
public virtual DbSet<EntityB> EntityBs { get; set; }
}
So, it could just be that you wish to do this:
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
Keeping in mind, if there is some set of properties in common across entity classes, you could still do something like the following:
IEnumerable<T> MyQaQcMethod(IQueryable<T> entities, T referenceEntity) where T : IQaQcAble
{
return entities.Where(e => SomePredicate(e, referenceEntity));
}
void Test()
{
using (var context = new Context())
{
// EntityA implements IQaQcAble
var resultsForA = MyQaQcMethod(context.EntityAs, defaultEntity).ToArray();
// so does EntityB, so can call with either
var resultsForB = MyQaQcMethod(context.EntityBs, defaultEntity).ToArray();
}
}
Keep in mind, to avoid modifying the generated entity classes, you could implement the interface members — and the interface — in a separate source file using partial classes. E.g.
// IQaQcAble.cs
internal interface IQaQcAble
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
// a class whose existing property names match the interface
public partial class EntityA : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return CommonInt; }
set { CommonInt = value; }
}
string IQaQcAble.CommonString
{
get { return CommonString; }
set { CommonString = value; }
}
}
// a class whose property names differ
public partial class EntityB : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return SomeOtherInt; }
set { SomeOtherInt = value; }
}
string IQaQcAble.CommonString
{
get { return SomeOtherInt.ToString(); }
set { SomeOtherInt = Convert.ToInt32(value); }
}
}

What is wrong with this many-to-many relationship in EF code-first?

I have these schema in my database:
Tb1: { Id:int , NameTb1:varchar(50) }
Tb2: { Id:int , NameTb2:varchar(50) }
Tb1Tb2 { Tb1Id:int , Tb2Id:int }
Obviously Tb1Tb2 is a relationship table and I want to define a many-to-many relationship in EF code-first.
And these are the entity classes :
public class Tb1
{
public Tb1()
{
ListTb2 = new List<Tb2>();
}
public int Id { get; set; }
public string NameTb1 { get; set; }
public virtual ICollection<Tb2> ListTb2 { get; set; }
}
public class Tb2
{
public Tb2()
{
ListTb1 = new List<Tb1>();
}
public int Id { get; set; }
public string NameTb2 { get; set; }
public virtual ICollection<Tb1> ListTb1 { get; set; }
}
and mappings :
public class Tb1Map : EntityTypeConfiguration<Tb1>
{
public Tb1Map()
{
this.HasKey(x => x.Id);
this.HasMany(x => x.ListTb2)
.WithMany(xx => xx.ListTb1)
.Map
(
x =>
{
x.MapLeftKey("Tb1Id");
x.MapRightKey("Tb2Id");
x.ToTable("Tb1Tb2");
}
);
}
}
public class Tb2Map : EntityTypeConfiguration<Tb2>
{
public Tb2Map()
{
this.HasKey(x => x.Id);
}
}
When I use it in my app :
var sv1 = new TableService<Tb1>(_uow);
var sv2 = new TableService<Tb2>(_uow);
var t1 = new Tb1 { NameTb1 = "T111" };
sv1.Add(t1);
//var res1= _uow.SaveChanges();
var t2 = new Tb2 { NameTb2 = "T222" };
sv2.Add(t2);
//var res2 = _uow.SaveChanges();
t1.ListTb2.Add(t2);
var result = _uow.SaveChanges();
I get this error:
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
and inner exception is:
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_Tb1Tb2_Tb2". The conflict occurred in database "dbTest", table
"dbo.Tb2", column 'Id'.
Why do I get this error?
and what is the solution?
tnx
I remove the
sv2.Add(t2);
and it worked

Using Entity Framework 4.0 with Code-First and POCO: How to Get Parent Object with All its Children?

I'm new to EF 4.0, so maybe this is an easy question. I've got VS2010 RC and the latest EF CTP. I'm trying to implement the "Foreign Keys" code-first example on the EF Team's Design Blog, http://blogs.msdn.com/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx.
public class Customer
{
public int Id { get; set;
public string CustomerDescription { get; set;
public IList<PurchaseOrder> PurchaseOrders { get; set; }
}
public class PurchaseOrder
{
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public DateTime DateReceived { get; set; }
}
public class MyContext : ObjectContext
{
public RepositoryContext(EntityConnection connection) : base(connection){}
public IObjectSet<Customer> Customers { get {return base.CreateObjectSet<Customer>();} }
}
I use a ContextBuilder to configure MyContext:
{
var builder = new ContextBuilder<MyContext>();
var customerConfig = _builder.Entity<Customer>();
customerConfig.Property(c => c.Id).IsIdentity();
var poConfig = _builder.Entity<PurchaseOrder>();
poConfig.Property(po => po.Id).IsIdentity();
poConfig.Relationship(po => po.Customer)
.FromProperty(c => c.PurchaseOrders)
.HasConstraint((po, c) => po.CustomerId == c.Id);
...
}
This works correctly when I'm adding new Customers, but not when I try to retrieve existing Customers. This code successfully saves a new Customer and all its child PurchaseOrders:
using (var context = builder.Create(connection))
{
context.Customers.AddObject(customer);
context.SaveChanges();
}
But this code only retrieves Customer objects; their PurchaseOrders lists are always empty.
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
}
What else do I need to do to the ContextBuilder to make MyContext always retrieve all the PurchaseOrders with each Customer?
You could also use:
var customers = context.Customers.Include("PurchaseOrders").ToList();
Or enable LazyLoading in the ContextOptions :
context.ContextOptions.LazyLoadingEnabled = true;
Just be careful with deferred loading if you are serializing the objects or you may end up querying the entire database.
Well the solution turned out to be simple, as I suspected it might. I called the context.LoadProperty() method for each individual customer:
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
foreach (var customer in customers)
{
context.LoadProperty<Customer>(customer, c => c.PurchaseOrders);
}
return customers;
}