How To Insert Data In FluentAPI Mapping Table - entity-framework

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.

Related

Add include on DbContext level

I want to implement something similar to lazy loading, but don't understand how to implement that. I want to force entity framework core include navigation property for all queries for type which implements my interface
public interface IMustHaveOrganisation
{
Guid OrganisationId { get; set; }
Organisation Organisation { get; set; }
}
public class MyEntity : IMustHaveOrganisation {
public Guid OrganisationId { get; set; }
public virtual Organisation Organisation { get; set; }
}
Without lazy loading I need to add .Include(x=>x.Organisation) to each query literally , and I can't use implementation of lazy loading provided by Microsoft. I need kind of custom implementation of that with loading just one property.
Or even force DbContext somehow to Include that property, it also fine for me.
How can I achieve that?
You can make this work by rewriting the expression tree, before it gets translated by EF Core.
To make this work in a way, where you don't have to specify anything additional in the query, you can hook into the very beginning of the query pipeline and inject the Include() call as needed.
This can be done, by specifying a custom IQueryTranslationPreprocessorFactory implementation.
The following fully working console project demonstrates this approach:
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class Organisation
{
public int OrganisationId { get; set; }
public string Name { get; set; }
}
public interface IMustHaveOrganisation
{
int OrganisationId { get; set; }
Organisation Organisation { get; set; }
}
public class MyEntity : IMustHaveOrganisation
{
public int MyEntityId { get; set; }
public string Name { get; set; }
public int OrganisationId { get; set; }
public virtual Organisation Organisation { get; set; }
}
public class CustomQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
{
private readonly QueryTranslationPreprocessorDependencies _dependencies;
private readonly RelationalQueryTranslationPreprocessorDependencies _relationalDependencies;
public CustomQueryTranslationPreprocessorFactory(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
_dependencies = dependencies;
_relationalDependencies = relationalDependencies;
}
public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
=> new CustomQueryTranslationPreprocessor(_dependencies, _relationalDependencies, queryCompilationContext);
}
public class CustomQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
{
public CustomQueryTranslationPreprocessor(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
}
public override Expression Process(Expression query)
{
query = new DependenciesIncludingExpressionVisitor().Visit(query);
return base.Process(query);
}
}
public class DependenciesIncludingExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitConstant(ConstantExpression node)
{
// Call Include("Organisation"), if SomeEntity in a
// DbSet<SomeEntity> implements IMustHaveOrganisation.
if (node.Type.IsGenericType &&
node.Type.GetGenericTypeDefinition() == typeof(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<>) &&
node.Type.GenericTypeArguments.Length == 1 &&
typeof(IMustHaveOrganisation).IsAssignableFrom(node.Type.GenericTypeArguments[0]))
{
return Expression.Call(
typeof(EntityFrameworkQueryableExtensions),
nameof(EntityFrameworkQueryableExtensions.Include),
new[] {node.Type.GenericTypeArguments[0]},
base.VisitConstant(node),
Expression.Constant(nameof(IMustHaveOrganisation.Organisation)));
}
return base.VisitConstant(node);
}
}
public class Context : DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public DbSet<Organisation> Organisations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Register the custom IQueryTranslationPreprocessorFactory implementation.
// Since this is a console program, we need to create our own
// ServiceCollection for this.
// In an ASP.NET Core application, the AddSingleton call can just be added to
// the general service configuration method.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.AddSingleton<IQueryTranslationPreprocessorFactory, CustomQueryTranslationPreprocessorFactory>()
.AddScoped(
s => LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.BuildServiceProvider();
optionsBuilder
.UseInternalServiceProvider(serviceProvider) // <-- use our ServiceProvider
.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=62849896")
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>(
entity =>
{
entity.HasData(
new MyEntity {MyEntityId = 1, Name = "First Entity", OrganisationId = 1 },
new MyEntity {MyEntityId = 2, Name = "Second Entity", OrganisationId = 1 },
new MyEntity {MyEntityId = 3, Name = "Third Entity", OrganisationId = 2 });
});
modelBuilder.Entity<Organisation>(
entity =>
{
entity.HasData(
new Organisation {OrganisationId = 1, Name = "First Organisation"},
new Organisation {OrganisationId = 2, Name = "Second Organisation"});
});
}
}
internal static class Program
{
private static void Main()
{
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var myEntitiesWithOrganisations = context.MyEntities
.OrderBy(i => i.MyEntityId)
.ToList();
Debug.Assert(myEntitiesWithOrganisations.Count == 3);
Debug.Assert(myEntitiesWithOrganisations[0].Name == "First Entity");
Debug.Assert(myEntitiesWithOrganisations[0].Organisation.Name == "First Organisation");
}
}
}
Even though no explicit Include() is being made in the query in Main(), the following SQL is being generated, that does join and retrieve the Organisation entities:
SELECT [m].[MyEntityId], [m].[Name], [m].[OrganisationId], [o].[OrganisationId], [o].[Name]
FROM [MyEntities] AS [m]
INNER JOIN [Organisations] AS [o] ON [m].[OrganisationId] = [o].[OrganisationId]
ORDER BY [m].[MyEntityId]

EF Core: Only part of the model is saved to the database

I try to use EF core, but only a part of my model is saved to the database.
This is my model:
public class EngineType
{
public string Name { get; set; }
}
public class Car
{
public long CarId { get; set; }
public string Name { get; set; }
public EngineType Engine { get; set; }
}
The CarId and the Name is saved, but not the EngineType.
This is the test I use, but actual.Engine is always null:
[TestMethod]
public void WhenIAddAndSaveANewCarThenItIsAddedToDB()
{
using var target = new EFCoreExampleContext();
using var concurrentContext = new EFCoreExampleContext();
var expected = new Car() {CarId = 0815, Name = "Isetta", Engine = new EngineType() { Name = "2Takt" }};
target.Cars.Add(expected);
target.SaveChanges();
var actual = concurrentContext.Cars.Single();
Assert.AreEqual(1, concurrentContext.Cars.Count());
Assert.IsNotNull(actual.Engine);
Assert.AreEqual(expected, actual);
}
My Context looks like this:
public class EFCoreExampleContext : DbContext
{
public DbSet<Car> Cars { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase(databaseName: "Add_writes_to_database");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EngineType>(
d =>
{
d.HasKey(e => e.Name);
d.Property(e => e.Name).IsRequired();
});
modelBuilder.Entity<EngineType>(
d =>
{
d.HasKey(e => e.Name);
});
modelBuilder.Entity<Car>(
d =>
{
d.HasKey(e => e.CarId);
d.Property<DateTime>("LastChanged").IsRowVersion().ValueGeneratedOnAddOrUpdate();
d.Property<string>("EngineForeignKey");
d.HasOne(e => e.Engine)
.WithMany()
.HasForeignKey("EngineForeignKey")
.IsRequired();
});
}
}
Any idea what am I doing wrong (or which existing topic answers this question - I even didn't have the right search words to find it).
Thanks!
I think there is no issue with saving. Entity Framework does not do eager loading by default. So you have to explicitly include any navigational properties that should be in result. Try this when you are fetching actual,
using Microsoft.EntityFrameworkCore;
var actual = concurrentContext.Cars.Include(c => c.Engine).Single();

Location of Include method influences success or failure

I am writing a unit test to test a method that gets data using Entity Framework and LINQ. I'm using mocked DbSets in my test. The method is returning data from the Orders DbSet, along with a navigation property from the related Customers DbSet, using the extension System.Data.Entity.Include method.
I've run into a weird situation in which, depending on where I call Include, either A) the test succeeds or B) I get an exception. Therein lies my question.
(This is a simplification of the actual code. I realize that these tests, as written below, are silly and pointless.)
public class Order
{
// Primary key
public string OrderId { get; set; }
// Foreign key to Customers table
public string CustomerId { get; set; }
// Navigation property
public virtual Customer Customer {get; set; }
}
public class Customer
{
// Primary key
public string CustomerId { get; set; }
}
public class MyContext : DbContext
{
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasKey(p => new { p.OrderId });
modelBuilder.Entity<Order>()
.HasRequired(p => p.Customer)
.WithMany();
modelBuilder.Entity<Customer>()
.HasKey(p => new { p.CustomerId });
}
// ...
}
[TestClass]
public class MyTests
{
// Creates a mock DbSet that can be used for Entity Framework contexts
private static Mock<DbSet<TEntity>> CreateMockDbSet<TEntity>(IEnumerable<TEntity> models) where TEntity : class
{
Mock<DbSet<TEntity>> dbSet = new Mock<DbSet<TEntity>>();
IQueryable<TEntity> queryable = models.AsQueryable();
dbSet.As<IQueryable<TEntity>>().Setup(e => e.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<TEntity>>().Setup(e => e.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<TEntity>>().Setup(e => e.GetEnumerator()).Returns(queryable.GetEnumerator());
dbSet.As<IQueryable<TEntity>>().Setup(e => e.Provider).Returns(queryable.Provider);
return dbSet;
}
// This test succeeds
[TestMethod]
public void GetOrders1()
{
Mock<DbSet<Customer>> customersDbSet = CreateMockDbSet(new List<Customer>
{
new Customer { CustomerId = "12345" }
});
Mock<DbSet<Order>> ordersDbSet = CreateMockDbSet(new List<Order>
{
new Order { OrderId = "0000000001", CustomerId = "12345" }
});
Mock<MyContext> context = new Mock<MyContext>();
context.Setup(e => e.Customers).Returns(customersDbSet.Object);
context.Setup(e => e.Orders).Returns(ordersDbSet.Object);
// This succeeds
List<Order> orders =
(from o in context.Object.Orders
select o).Include(p => p.Customer).ToList();
Assert.AreEqual(1, orders.Count);
}
// This test results in an exception that says "System.ArgumentNullException: Value cannot be null."
[TestMethod]
public void GetOrders2()
{
Mock<DbSet<Customer>> customersDbSet = CreateMockDbSet(new List<Customer>
{
new Customer { CustomerId = "12345" }
});
Mock<DbSet<Order>> ordersDbSet = CreateMockDbSet(new List<Order>
{
new Order { OrderId = "0000000001", CustomerId = "12345" }
});
Mock<MyContext> context = new Mock<MyContext>();
context.Setup(e => e.Customers).Returns(customersDbSet.Object);
context.Setup(e => e.Orders).Returns(ordersDbSet.Object);
// This fails
List<Order> orders =
(from o in context.Object.Orders.Include(p => p.Customer)
select o).ToList();
Assert.AreEqual(1, orders.Count);
}
}
The two tests are identical, except for the location of the Include method in my LINQ query. I've checked this with a real database attached, and both ways of writing the query result in the same SQL being executed.
Why does the first test method succeed, but the second one results in an exception?

Prevent EF from saving full object graph

I have a model as below
public class Lesson
{
public int Id { get; set; }
public Section Div { get; set; }
}
public class Section
{
public int Id { get; set; }
public string Name { get; set; }
}
I also have DB Context as below
public class MyContext : DbContext
{
public MyContext() : base("DefaultConnection")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Section> Sections { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Then I use the following code to call the database
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Entry(d.Div).State = EntityState.Detached;
c.Lessons.Add(d);
c.SaveChanges();
}
I am expecting this code to save just the Lesson object, not to save the full graph of Lesson and Section, but what happens is that it saves the full graph.
How do I prevent it from doing that?
When you add an entity to DbSet, entityframework will add all of its relative. You need to detach the entity you don't want to add, after adding parent entity to DbSet.
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Lessons.Add(d);
c.Entry(d.Div).State = EntityState.Detached;
c.SaveChanges();
}
if you want to add section, related to the lesson , you need to use the same context, or create a new context and load the lesson.
you can use this code
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Lessons.Add(d);
c.Entry(d.Div).State = EntityState.Detached;
c.SaveChanges();
//you can use this code
ed.Lesson = d;
// or this code
d.Div = ed;
c.Sections.Add(ed);
c.SaveChanges();
}

The entity or complex type cannot be constructed in a LINQ to Entities query [duplicate]

This question already has answers here:
The entity cannot be constructed in a LINQ to Entities query
(14 answers)
Closed 10 years ago.
I have two functions that look exactly the same except they create lists of two different objects. The two different objects look very much alike, but when I try to run one of the functions on one of the objects, I get the error message, "The entity or complex type cannot be constructed in a LINQ to Entities query.". Can someone explain to me what is happening in very simple terms? Also, can you tell me how to change my code so that it works? Thanks, Allan.
Function 1 (works):
public static List<ChartApp> ListChartApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new ChartApp()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Function 2 (throws error on "return appNames.ToList();"):
public static List<LoginApplication> ListApplications()
{
using (var db = new LatencyDBContext())
{
var appNames = db.LoginApplications.Select(item => new LoginApplication()
{
LoginApplicationID = item.LoginApplicationID,
LoginAppName = item.LoginAppName,
}).OrderBy(item => item.LoginAppName);
return appNames.ToList();
}
}
Classes:
public class ChartApp
{
public ChartApp()
{
this.LoginHistories = new List<ChartHist>();
}
public int? LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<ChartHist> LoginHistories { get; set; }
public int Step { get; set; }
}
public class LoginApplication
{
public LoginApplication()
{
this.LoginHistories = new List<LoginHistory>();
}
public int LoginApplicationID { get; set; }
public string LoginAppName { get; set; }
public virtual ICollection<LoginHistory> LoginHistories { get; set; }
}
Edit: Could the difference possibly be that one of the objects are mapped to the database?
public class LoginApplicationMap : EntityTypeConfiguration<LoginApplication>
{
public LoginApplicationMap()
{
// Primary Key
this.HasKey(t => t.LoginApplicationID);
// Properties
this.Property(t => t.LoginAppName)
.HasMaxLength(500);
// Table & Column Mappings
this.ToTable("LoginApplication");
this.Property(t => t.LoginApplicationID).HasColumnName("LoginApplicationID");
this.Property(t => t.LoginAppName).HasColumnName("LoginAppName");
}
}
My solution in this case was to just delete the non-working function and use the working one in all places. For, similar functions that are mapped, I use the following function to return values.
public static List<LoginEnvironment> ListEnvironments(bool allSelection)
{
using (var db = new LatencyDBContext())
{
//GET ALL THE ENVIRONMENT NAMES
var envNames = from e in db.LoginEnvironments
orderby e.LoginEnvName
select e;
//PUT ALL THE ENVIRONMENTS INTO A LOCAL LIST
var listEnv = new List<LoginEnvironment>();
if (allSelection)
{
var defaultAll = new LoginEnvironment();
defaultAll.LoginEnvironmentID = 0;
defaultAll.LoginEnvName = "All";
listEnv.Add(defaultAll);
}
foreach (var item in envNames)
{
var localEnv = new LoginEnvironment();
localEnv.LoginEnvironmentID = item.LoginEnvironmentID;
localEnv.LoginEnvName = item.LoginEnvName;
listEnv.Add(localEnv);
}
return listEnv;
}
}