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

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;
}
}

Related

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); }
}
}

Add/Update list in database using Entity framework 6

I have three tables QuestionBank,Question and Answer. " QuestionBank " will have list of Question and " Question " will have list of " Answer ".
QUESTIONBANK :-
public class QuestionBank
{
public int id { get; set; }
public string Text { get; set; }
public string Chapter { get; set; }
public string Standard { get; set; }
public List<Question> Question { get; set; }
public QuestionBank()
{
this.Question = new List<Question>();
}
}
QUESTION :-
public class Question
{
public int id { get; set; }
public string Title { get; set; }
public string QuestionText { get; set; }
public List<Answer> Answer { get; set; }
public string CorrectAnswer { get; set; }
public Question()
{
this.Answer = new List<Answer>();
}
}
ANSWER :-
public class Answer
{
public int id { get; set; }
public string AnswerText { get; set; }
}
WEB API :- //Edited
private IRepository<QuestionBank> _QuestionBankRepository;
public QuestionController(IRepository<QuestionBank> QuestionBankRepository)
{
_QuestionBankRepository = QuestionBankRepository;
}
[HttpPost]
[Route("Ques/Add")]
public Boolean Add(QuestionBank AddQuetionBankData)
{
var isQuetionBankPresent = _QuestionBankRepository.GetAll(p => p.Text == AddQuetionBankData.Text && p.Standard == AddQuetionBankData.Standard && p.Chapter == AddQuetionBankData.Chapter).FirstOrDefault<QuestionBank>();
if (isQuetionBankPresent != null)
{
/* Add the data in Question and Answer tables */
return false;
}
else
{
/* Add the data in all three tables */
return true;
}
}
I have this database for the web api. Now I want to add the data in database through json { "QuestionBank": QuestionBank, "Question": Question, "Answer": Answer } if the row is present in QuestionBank i dont want to add that data in QuestionBank table and only add the data in Question and Answer table with respective foreign keys. I am using the entity frame work and mvc 5 web api. I am stuck at this point. Please if any thing is needed let me know. Thanks in advance.
The Entity Framework way to update is to to Context.Entry([your object]).State = System.Data.Entity.EntityState.Modified; providing that the object is of the right type.
public Boolean Add(QuestionBank AddQuetionBankData)
{
bool flag = false;
var question = this.MapToQuestion(AddQuetionBankData); //map the input to the EF Type Question
var anwer = this.MapToAnswer(AddQuetionBankData); //map the input to the EF Type Answer
var isQuetionBankPresent = _QuestionBankRepository.GetAll(p => p.Text == AddQuetionBankData.Text && p.Standard == AddQuetionBankData.Standard && p.Chapter == AddQuetionBankData.Chapter).FirstOrDefault<QuestionBank>();
if (isQuetionBankPresent != null)
{
_context.Entry(question).State = EntityState.Modified;
_context.Entry(answer).State = EntityState.Modified;
/* Add the data in Question and Answer tables */
flag = false;
}
else
{
_context.Entry(question).State = EntityState.Modified;
_context.Entry(answer).State = EntityState.Modified;
_context.Entry(AddQuetionBankData).State = EntityState.Modified;
/* Add the data in all three tables */
flag = true;
}
_context.SaveChanges();
return flag;
}
private Question MapTo Question(QuestionBank q) //do this for Answers too
{
var _q = _context.Question.Where(a=>a.Id == q.Id).SingleOrDefault();
if(_q!=null)
{
_q.id = q.id; //this is already true
_q.Title = q.Title;
_q.QuestionText = q.Standard; //I guess
}
return _q;
}
The EF updates the Entity (the class you pass to the method Entry()) accordingly to its Type.
Notice that the position of the SaveChanges(): it works like a stored procedure, you do all the updates and the SaveChanges() is like the SQL COMMIT command.
You should also wrap the SaveChanges in a try/catch to handle errors, and dispose the _context.
EDIT
This class has as dependency IRepository<Question>, IRepository<QuestionBank>, and IRepository<Answer>.
You should create an UpdateController(or PublishController or whatever) that gets the three dependencies in the constructor (better a Facade Service), and call the Add() method for each one of them.
If you access directly the raw Database object you could do like I did and use the Entry() method for each table.

entity framework 5 take and order by in include

I want to retrieve an object plus its filtered/ordered collection property using EF 5. However, my current code throws an exception:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties
Here is the class of the object I want to retrieve:
public class EntryCollection
{
[Key]
public int Id { get; set; }
public ICollection<Entry> Entries { get; set; }
...
}
And here is the definition of Entry:
public class Entry
{
[Key]
public int Id { get; set; }
public DateTime Added { get; set; }
...
}
I wanted to retrieve the EntryCollection which contains only the most recent entries, so here is the code I tried:
using (var db = new MyContext())
{
return db.EntryCollections
.Include(ec => ec.Entries.OrderByDescending(e => e.Added).Take(5))
.SingleOrDefault(ec => ec.Foo == "bar');
}
Any ideas?
You cant use OrderBy inside an include.
what about the following
using (var db = new MyContext())
{
return db.EntryCollections
.Where(ec => ec.Foo == "bar")
.Select(ec=> new Something{Entries = ec.Entries.OrderByDescending(e => e.Added).Take(5) }, /*some other properties*/)
.SingleOrDefault();
}
or do it in two seperate queries

How to maintain an ordered list in Entity Framework?

Changing order of elements in a simple list, doesn't stick in Entity Framework. The reason is pretty simple as the ordering information is never stored in the database.
Has anyone come across a generic implementation of ordered list which would work along with Entity Framework?
The requirement is that the user is allowed to reorder list of selected items, and the ordering of items need to be preserved.
Overview
Although there doesn't seem to be any 'magic' to implement this, there is a pattern that we have used to solve this problem, especially when dealing with hierarchies of objects. It boils down to three key things:
Build an Entity model separate from your Domain model. This has the benefit of providing a good separation of concerns, effectively allowing your domain model to be designed and changed without getting bogged down by persistence details.
Use AutoMapper to overcome the hassle of mapping between the Entity and Domain models.
Implement custom value resolvers to map the list in both directions.
The Magic
Because models often include hierarchical and cyclical references between objects, the following Map<>() method can be used to avoid StackOverflow errors during the custom mapping
private class ResolveToDomain : IValueResolver
{
ResolutionResult Resolve(ResolutionResult rr)
{
//...
((MappingEngine) rr.Context.Engine).Map<Product, ProductEntity>(rr.Context, subProduct)
//...
}
}
The Code
Domain Model. Note that the Subproducts list order is important.
class Product
{
public Product ParentProduct { get; set; }
public string Name { get; set; }
public IList<Product> Subproducts { get; set; }
}
Entity Model
class ProductEntity
{
public int Id { get; set; }
public ProductEntity ParentProduct { get; set; }
public string Name { get; set; }
public IList<ProductSubproductEntity> Subproducts { get; set; }
}
class ProductSubproductEntity
{
public int ProductId { get; set; }
public ProductEntity Product { get; set; }
public int Order { get; set; }
public ProductEntity Subproduct { get; set; }
}
Entity Framework Context
class Context : DbContext
{
public DbSet<ProductEntity> Products { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductEntity>()
.HasOptional(e => e.ParentProduct);
modelBuilder.Entity<ProductSubproductEntity>()
.HasKey(e => new {e.ProductId, e.Order})
.HasRequired(e => e.Product)
.WithMany(e => e.Subproducts)
.HasForeignKey(e => e.ProductId);
base.OnModelCreating(modelBuilder);
}
}
AutoMapper configuration
class Mappings : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Product, ProductEntity>()
.ForMember(m => m.Subproducts, a => a.ResolveUsing<ProductSubproductResolver>());
Mapper.CreateMap<ProductEntity, Product>()
.ForMember(m => m.Subproducts, a => a.ResolveUsing<ProductSubproductEntityResolver>());
base.Configure();
}
}
class ProductSubproductResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult rr)
{
var result = new List<ProductSubproductEntity>();
var subproductsSource = ((Product) rr.Context.SourceValue).Subproducts;
if (subproductsSource == null) return rr.New(null);
for (int i = 0; i < subproductsSource.Count; i++)
{
var subProduct = subproductsSource[i];
result.Add(new ProductSubproductEntity()
{
Product = (ProductEntity)rr.Context.DestinationValue,
Order = i,
Subproduct = ((MappingEngine) rr.Context.Engine).Map<Product, ProductEntity>(rr.Context, subProduct)
});
}
return rr.New(result);
}
}
class ProductSubproductEntityResolver: IValueResolver
{
public ResolutionResult Resolve(ResolutionResult rr)
{
var subproductEntitiesSource = ((ProductEntity) rr.Context.SourceValue).Subproducts;
if (subproductEntitiesSource == null) return rr.New(null);
var result = subproductEntitiesSource.OrderBy(p => p.Order).Select(p =>
((MappingEngine) rr.Context.Engine).Map<ProductEntity, Product>(rr.Context, p.Subproduct))
.ToList();
return rr.New(result);
}
}
Usage
private static IList<Product> CreateDomainProducts()
{
var result = new List<Product>();
var mainProduct1 = new Product()
{
Name = "T-Shirt"
};
var subProduct1 = new Product()
{
ParentProduct = mainProduct1,
Name = "T-Shirt (Medium)",
};
var subProduct2 = new Product()
{
ParentProduct = mainProduct1,
Name = "T-Shirt (Large)",
};
mainProduct1.Subproducts = new []
{
subProduct1,
subProduct2
};
var mainProduct2 = new Product()
{
Name = "Shorts"
};
result.Add(mainProduct1);
result.Add(mainProduct2);
return result;
}
static void Main(string[] args)
{
Mapper.Initialize(a => a.AddProfile<Mappings>());
Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
var products = CreateDomainProducts();
var productEntities = Mapper.Map<IList<ProductEntity>>(products);
using (var ctx = new Context())
{
ctx.Products.AddRange(productEntities);
ctx.SaveChanges();
}
// Simulating a disconnected scenario...
using (var ctx = new Context())
{
var productEntity = ctx.Products
.Include(p => p.Subproducts)
.Include(p => p.Subproducts.Select(p2 => p2.Subproduct))
.OrderBy(p=>p.Name)
.ToList();
var productsResult = Mapper.Map<IList<Product>>(productEntity);
// Should be 'T-Shirt (Medium)'
Console.WriteLine(productsResult[1].Subproducts[0].Name);
// Should be 'T-Shirt (Large)'
Console.WriteLine(productsResult[1].Subproducts[1].Name);
}
}
Voila. Hope that helps!
No magic here. If you want to persist a specific order of items in a list (other than a reproducible order by e.g. name) you must store a sequence number in the database.
There wont be an implementation of this for reordering on the database. The data in the database is physically ordered by default by the clustered index which is in essence ordering by the primary key.
Why do you want to do this? EF encourages all ordering to be done via LINQ queries.
If you are looking to optimize lookups you can create additional non-clustered indexes on the database by modifying the code generated for Migrations :
CreateTable(
"dbo.People",
c => new
{
ID = c.Int(nullable: false, identity: true),
Name = c.String()
})
.PrimaryKey(t => t.ID)
.Index(t => t.Name); // Create an index
Note that this will not impact the physical ordering in the database but will speed lookups, although this need to be balanced by slower writes/updates.
to find a solution for this challenge I faced to an article by the following link:
User-defined Order in SQL
this article analyzed different approaches for generating order index value during changing the order of the list. I found the algorithm mentioned in this article so performant by minimum limitation. this algorithm called True Fractions and it generates order index like the following figure:
I have prepared a code sample that I implement this approach by EF Core and InMemory database.
.NET Fiddle Code Sample