EF How to relate already loaded entities? - entity-framework-4

I have the following methods on the server (RIA services):
public IQueryable<Customer> GetCustomers()
{
return ObjectContext.Customers;
}
public IQueryable<Customer> GetCustomersWithInvoicesAndInvoiceItemsAsync()
{
return ObjectContext.Customers
.Include("Invoices.InvoiceItems");
}
These items are loaded on client as IEnumerable<>, lets name the CollectionA and CollectionB. My problem is next: If I add/remove a Customer in CollectionA, CollectionB will not be aware of it. Editing is working as expected, since both of collections contain same entities. Is there a way I can load Invoices separately, and relate them in the client, so I would work only on one collection of customers, and not two?

Service-side (beside the GetCustomers method):
public IQueryable<Invoice> GetInvoicesAndInvoiceItemsAsync()
{
return ObjectContext.Invoices.Include("InvoiceItems");
}
Client-side, after getting customers and invoices in separate calls (as IEnumerables):
var query = customers.Join(invoices, c => c.CustomerId, i => i.CustomerId,
(c,i) => Tuple.Create(c, i) );
This will give you a list of Customer - Invoice pairs. Or use GroupJoin:
var query = customers.GroupJoin(invoices, c => c.CustomerId, i => i.CustomerId,
(c,i) => Tuple.Create(c, i) );
This will give you a list of Customer - Invoices (plural) pairs.

Related

How pass a LINQ expression to a function that returns a IQueryable

I have a few methods that act on a query one that is for a simple where filter an the other with an expression to be used in the where filter and the third also has an expression as a parameter.
public static IQueryable<Employee> FilterExpression(this IQueryable<Employee> employees,
Expression<Func<Employee, Boolean>> expression)
{
return employees.Where(expression);
}
public static IQueryable<Employee> Active(this IQueryable<Employee> employees)
{
return employees.Where(e => e.IsActive);
}
The first two execute fine without any problems.
var active = db.Employees.Active().Take(5).ToList();
var activeExpression = db.Employees.Take(5).FilterExpression((e) => e.IsActive).ToList();
The next one is being called in another extension method that returns a delegate for a select on a tasks collection.
public static Expression<Func<SpwTask, TaskView>> Tasks(this TaskService service)
{
return (x) => new TaskView()
{
NotifyList = db.TaskContacts.JoinedEmployees
(t => t.TaskId == x.Id).Select(Projections.SimpleEmployees)
};
}
public static IQueryable<Employee> JoinedEmployees(this IQueryable<TaskContact> contacts,
Expression<Func<TaskContact, Boolean>> expression)
{
var id = ServicesRoot.Company.Id;
return from c in contacts.Where(expression)
join e in db.Employees on c.EmployeeId equals e.Id
where e.CompanyId == id
select e;
}
The calling code looks like this
// db is the DbContext and the Tasks is the extension method
...
return db.Tasks.Select(this.Tasks());
...
The error I get is this:
System.NotSupportedException: 'LINQ to Entities does not recognize the method 'System.Linq.IQueryable1[SafetyPlus.Data.Models.Employee] JoinedEmployees(System.Linq.IQueryable1[SafetyPlus.Data.TaskContact], System.Linq.Expressions.Expression1[System.Func2[SafetyPlus.Data.TaskContact,System.Boolean]])' method, and this method cannot be translated into a store expression.'
Are there any ways to work around this problem? It would be really nice to reused this an other similar queries. It seems like this should work.
Inline version:
public static Expression<Func<SpwTask, TaskView>> Tasks(this TaskService service)
{
return (x) => new TaskView()
{
NotifyList =
(from c in service.db.TaskContacts.
where c.TaskId == x.Id
join e in db.Employees on c.EmployeeId equals e.Id
where e.CompanyId == id
select e).Select(Projections.SimpleEmployees)
};
}
When I run the code inline version of the query I get a single SQL produced which is optimized and what I want but is not reusable.
I had thought the Compile() solution given below was the answer that I needed, at first, but upon looking at the SQL Profiler I realize that each row of the outer task query is running a single query for each task instead of the single query for the whole dataset. This pretty much defeats the purpose of reusing the query.
You should pass it as a delegate without invoking it in the Select and compile before use.
Try this:
db.Tasks.Select(Tasks.Compile());

How to read from a table that is mapped with ModelBuilder in mvc c#

I have a Dbcontext where in its OnModelCreating() method I have something like this:
modelBuilder.Entity<CmsContentData>().HasMany<CmsKeyword>(m =>
m.CmsKeywords).WithMany(m => m.CmsContentDatas).Map(m =>
{
m.ToTable("CmsContentData_CmsKeywords");
m.MapLeftKey("CmsContentDataID");
m.MapRightKey("CmsKeywordID");
});
I want to read from the CmsContentData_CmsKeywords table but I wonder how ?
(there is no CmsContentData_CmsKeywords model in the project solution)
Apparently you have designed a many-to-many relation: every CmsContentData has zero or more CmsKeyWords, every CmsKeyword is used by zero or more CmsContentData.
In a relational database this many-to-many relationship is implemented using a junction table. This table is the one you mentioned in your DbContext.OnModelCreating.
You are right, you won't add this junction table as a DbSet in your DbContext. Your classes will be like:
class CmsContentData
{
public int Id {get; set;}
// every CmsContentData has zero or more CmsKeyWords (many-to-many)
virtual ICollection<CmsKeyWord> CmsKeyWords {get; set;}
... // other properties
}
class CmsKeyWord
{
public int Id {get; set;}
// every CmsKeyWord is used by zero or more CmsContentData (many-to-many)
virtual ICollection<CmsContentData> CmsContentData{get; set;}
... // other properties
}
class MyDbContext : Dbcontext
{
public DbSet<CmsContentData> CmsContentData {get; set;}
public DbSet<CmsKeyWord> CmsKeyWords {get; set;}
}
This is everything Entity Framework needs to know to detect that you designed a many-to-many relationship. It will create the two tables for you and the junction table, even without your code in OnModelCreating.
Only if you are not satisfied with the default identifiers for tables and columns you need the code in your OnModelCreating.
But if I have no reference to the junction table, how can I do the joins?
Answer: Don't (group)join, use the ICollections
Example: Get some CmsContentData with all (or some of) its CmsKeyWords:
var result = dbContext.CmsContextData
.Where(cmsContextData => ...) // only take certain cmsContextData
.Select(cmsContextData => new // only select properties you plan to use:
{
Id = cmsContextData.Id,
Name = cmsContextData.Name,
...
Keywords = cmsContextData.CmsKeywords
.Where(keyWord => keyWord.StartsWith(...)) // only select certain keywords
.Select(keyWord => new // only select properties you plan to use
{
Id = keyword.Id,
Text = keyWord.Text,
...
})
.ToList(),
});
Entity Framework is smart enough to detect that (group-)joins with your two tables and the junction table are needed.
The other way round:
Select all (or certain) CmsContentData that use some specific CmsKeyWords:
var result = dbContext.CmsKeyWords
.Where(keyWord => keyWord.StartsWith(...) // take only certain keywords
.Select(keyword => new // select only the properties you plan to use
{
Text = keyWord.Text,
...
CmsContextData = keyWord.CmsContextData // I only want specific context data
.Where(contextData => ...) // that use this Keyword
.select(contextData => new
{
... // ContextData properties you plan to use
});
});

How to get list of all ApplicationUsers who are in certain Role from database by LINQ expression?

I want to get:
list of ApplicationUsers who are in role "NormalUser" to anybody
list of all ApplicationUsers only to Admins only.
I did this:
// GET: ApplicationUsers
public ActionResult Index() {
// if you are Admin you will get all users
if (User.IsInRole("Admin"))
return View(db.Users.ToList());
//if you are somebody else(not Admin) you will see only list of NormalUsers
//HERE I GET ERROR
var list = db.Users.Where(x => UserManager.IsInRole(x.Id, "NormalUser")).ToList(); // here I get error
return View(list);
}
UserManager inside code above is: UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
But unfortunately my LINQ expresiion is incorrect. I get error:
LINQ to Entities does not recognize the method 'Boolean IsInRole[ApplicationUser,String](Microsoft.AspNet.Identity.UserManager`2[WebApplication2.Models.ApplicationUser,System.String], System.String, System.String)' method, and this method cannot be translated into a store expression.
Question: How to correctly get list of users who are in role "NormalUser"?
The UserManager.IsInRole function isn't supported at the database, but if your application can bear the weight of pulling the whole User table back from the database before applying your filter then you can just add a ToList between your Users table reference and your Where filter, i.e.
var list = db.Users.ToList().Where(x => UserManager.IsInRole(x.Id, "NormalUser")).ToList();
I reached here for a good quick answer but could not find one. So decided to put on what I got for any other visitor who comes here. To get the List of Users in any particular Role, one can use this code.
public async Task<ActionResult> Index()
{
List<ApplicationUser> appUsers=new List<ApplicationUser>();
await Task.Run( () =>
{
var roleManager = new RoleManager<IdentityRole>( new RoleStore<IdentityRole>( db ) );
var adminRole=roleManager.FindByName("Admin");
appUsers = db.Users.Where( x => x.Roles.Any( s => s.RoleId == adminRole.Id ) ).ToList();
} );
return View( appUsers );
}
It would be useful to know how Roles and Application users relate.
If the user can only belong to one role, it would be fine for you to do something like this:
var list = db.Users.ToList().Where(x => x.Role == "NormalUser").ToList();
Id the user can be part of multiple roles, it would look something more like this:
var list = db.Users.ToList().Where(x => x.Roles.Contains("NormalUser")).ToList();
Hope this helps.

How to update eagerly loaded entity with relations

I am using EF4 with ASP.Net MVC, and I wonder how am I to update entity which was loaded eagerly?
When I load the entity as follows:
static readonly Func<Entities, Guid, IQueryable<Call>> CallByGuid =
CompiledQuery.Compile<Entities, Guid, IQueryable<Call>>(
(db, callGuid) =>
db.Calls
.Where(c => !c.Removed && c.CallGUID.Equals(callGuid)));
public Call GetCall(Guid guid)
{
return CallByGuid.Invoke(_db, guid).Single();
}
and pass the entity to the View, edit it there and return back to Controller and save it as follows:
public Call UpdateCall(Call call)
{
_db.ObjectStateManager.ChangeObjectState(call, EntityState.Modified);
_db.Call_VoiceCall.ApplyCurrentValues(call);
_db.SaveChanges();
Call dbCall = FindCall(call.CallGUID);
return dbCall;
}
everything works fine, though quite slow. When I load the entity as follows:
static readonly Func<Entities, Guid, IQueryable<Call>> CallByGuid =
CompiledQuery.Compile<Entities, Guid, IQueryable<Call>>(
(db, callGuid) =>
db.Calls
.Include("Call_VoiceCall")
.Include("Call_Rating")
.Include("Memos")
.Include("Company")
.Include("PhoneService")
.Where(c => !c.Removed && c.CallGUID.Equals(callGuid)));
public Call GetCall(Guid guid)
{
return CallByGuid.Invoke(_db, guid).Single();
}
the list of entities loads very fast, but I cannot update an entity: what I get is
"Violation of PRIMARY KEY constraint 'PK_Call_5599FCED3AE4474F'.
Cannot insert duplicate key in object 'dbo.Call'. The duplicate key
value is (6d078f37-29e6-4e56-9853-d793994ce163). The statement has
been terminated."
or something like this.
Now, since it is almost impossible to google questions on the similar topic, I am doing something seriously wrong. What is it?

How to MapFrom a collection? Auto mapper

I have a linq to sql object that has some references to some other tables
I am trying to map it to a vm but nothing ever gets captures.
Mapper.CreateMap<A, G>();
// A is the linq to sql object
A.MyList // this is a collection that I am trying to get the value out of A.MyList.Id
// G is my View Model
public class G
{
public string AMyListId {get; set;}
}
vm = Mapper.Map<List<A>, List<G>>(aListOfAFromDb);
This always comes back from null. I thought I would have to do it manually so I tried
Mapper.CreateMap<A, G>().ForMember(dest => dest.AMyList , opt => opt.MapFrom(src =>????));
but since I am getting it from a list it won't give any of the properties to choose from.
Edit
I realized that I should not have a list of "MyList" it should be a one to one. I still am having problems trying to do what I want to do.
I have this
Mapper.CreateMap();
A.Vip.UserId // again my linq object is A
// G is my View Model
public class G
{
public string IsVip {get; set;}
}
vm = Mapper.Map<List<A>, List<G>>(aListOfAFromDb);
Mapper.CreateMap<A, G>().ForMember(dest => dest.IsVip, opt => opt.AddFormatter<VipFormatter>());
public class VipFormatter : IValueFormatter
{
public string FormatValue(ResolutionContext context)
{
bool value = (bool)context.SourceValue;
if (value)
{
return "Yes";
}
return "No";
}
}
yet nothing every gets bound. I am not sure why. Do I have to do change my property to "AVipUserId"? Or somehow tell it to map?
From what I can see in your code, and in addition to my comment above, you don't need AutoMapper for this one:
List<A> dbItems;
IEnumerable<G> results = dbItems.Select(x => x.MyList.MyListID);
In fact, you can't map A to G, because you're going to create multiple "G" objects for each "A" object.
Let me know if I misunderstood the question here.
UPDATE:
I would change "G" to use a boolean property and then do the following:
Mapper.CreateMap<A, G>().ForMember(dest => dest.IsVip, opt => opt.MapFrom(src => src.Vip == null));
Or, whatever logic you use to determine if it is a VIP or not.
How about:
List<G> items = // whatever
var result = items.Select(g => Mapper.Map<G, A>(g));

Resources