Can linq2sql do this without a lot of custom code? - asp.net

Just dipping my toes into Linq2sql project after years of rolling my own SQL Server DB access routines.
Before I spend too much time figuring out how to make linq2sql behave like my custom code used to, I want to check to make sure that it isn't already "built" in behavior that I can just use by setting up the relationships right in the designer...
Very simple example:
I have two tables: Person and Notes, with a 1 to many relationship (1 Person, many notes), linked by Person.ID->Note.PersonID.
I have a stored procedure (all data access is done via SP's and I plan on continuing that) which makes the Link2SQL a bit more work for me.
sp_PersonGet(#ID int) which returns the person record and sp_PersonNotesGet(#PersonID) which returns a set of related notes for this person.
So far so good, I have an object:
Dim myPerson As Person = db.PersonGet(pnID).Single
and I can access my fields: myPerson.Name, myPerson.Phone etc.
and I can also do a
Dim myNotes As Notes = db.PersonNotesGet(pnID)
to get a set of notes and I can iterate thru this list like:
For Each N As Note In myNotes
( do something)
Next
This all works fine...BUT....What I would prefer is that if I call:
myPerson = db.PersonGet(pnID)
that I also end up with a myPerson.Notes collection that I can iterate thru.
For Each N As Note In myPerson.Notes
( do something)
Next
Basically, Linq2SQl would need to call 2 stored procedures each time a Person record is created...
Is this doable "out of the box", or is this something I need to code around for myself?

This is normally what we would call child collections and they can be eager loaded or lazy loaded. Read these:
http://davidhayden.com/blog/dave/archive/2009/01/08/QuickExamplesLINQToSQLPerformanceTuningPerformanceProfilingORMapper.aspx
http://www.thinqlinq.com/default/Fetching-child-records-using-Stored-Procedures-with-LINQ-to-SQL.aspx

It uses partial classes. You can add your own "Notes" property to your Person class and initialize it in it's GETter function. This would be better than populating the notes every time you load a person record.

I believe that you can do this more or less out of the box, although I haven't tried it -- I don't use stored procedures with LINQ. What you would need to do is change the Insert/Delete/Update methods from using the runtime to use your stored procedures. Then you'd create an Association between your two entity tables which would create an EntitySet of Notes on the Person class and a EntityRef of Person on the Notes class. You can set this up to load automatically or using lazy loading.
The only tricky bit, as far as I can see, is the change from using the runtime generated methods to using your stored procedures. I believe that you have to add them into the data context as methods (by dropping it on your table from the server explorer in the designer) before it is available to use instead.

Related

How to handle an exception in VB code if the stored procedure doesn't return one of the fields?

I have a VB function which calls a stored procedure to get 2 kinds of reports (actives users/inactive users). The stored procedure returns the data (columns) based on the report we choose. If we choose to get active users report, the sproc doesn't return an Inactive_Date for the users, but it will return the same field, Inactive_Date field for Inactive users report. I'm getting an error when I choose to get active users report, because the sproc doesn't return Inactive_Date field, but my VB.NET code is same for both active/inactive users reports. Here are some of the ways I tried to resolve, but no luck.
If IsDBNull(dr("Inactive_Date")) Then
Result.Inactive_Date = Nothing
Else
Result.Inactive_Date = SafeStr(dr("Inactive_Date"))
End If
and
If IsDBNull(dr("Inactive_Date")) Then
Result.Inactive_Date = DateTime.Now
Else
Result.Inactive_Date = SafeStr(dr("Inactive_Date"))
End If
You're getting the exception because you're basically trying to access a dictionary entry that doesn't exist. You have a few options.
Have your stored procedure for Active Users return Inactive_Date as null. This would allow you to use code you have posted in VB with no changes.
Create a function that checks if the datareader has that column before you attempt to access it, as in this answer: Check for column name in a SqlDataReader object - this would be akin to checking if the dictionary has the key first
Write two separate functions in your VB code - one for the active report and one for the inactive report. Or, add a parameter to your function to check if this is a request for an active user, and only try to access the "Inactive_Date" field if that parameter is set to true.
If it were up to me, I'd update the stored procedure (option 1). That would probably be the least amount of work, shouldn't hurt anything semantically (unless you have queries that SELECT * from the results of that procedure), and requires no other changes to your codebase. The only other problem I can see is confusion that might arise from having a function in your VB code that's used for both active and inactive users.
The second option will involve iterating through all the fields, and that just feels wrong to me (seems like it's asking for performance issues if used incorrectly). It's the least invasive though if you can't change the stored proc.
The third option probably creates more problems elsewhere in your code base. That said, you probably shouldn't have one function do two very different things - I'd avoid the parameterized version if you end up going down this route, and go for two separate functions.
From your code and the fact that your variable is named dr, I am going to assume that you are using a DataReader.
The DataReader class exposes the column names as indexed values.
Dim columnNames = Enumerable.Range(0, dr.FieldCount).Select(dr.GetName).ToList()
Will call the method dr.GetName(index) for all columns, returning a list of column names you can check using columnNames.Contains("yourColumnName")
There is also dr.GetOrdinal, but that will throw an exception if the column doesn't exist.

Prevent null updates in detached entity framework objects

Using EF6 Framework4.5 – Creating my first n-tier app and first EF experience. I have CRUD working but one issue that I have a work-a-around but don’t like it. There must be a better way.
When data object is returned from my UI layer to the DAL layer, it has been detached, so I flag EntityState as “Modified.” But then it updates all columns in the db. Values that were not loaded in the form view (and not submitted) obviously are null and updated to such in the db.
1) My first solution does work:
Store the object in session in the UI layer and loop through the object updating edited values when the form is submitted. Thus, original values are passed back unchanged and updated to original values. I don’t think this would be best practice though.
2) The solution I think I want:
I am looking for a helper function in the DAL layer to loop through all values in the returned object and flag only non-null values as “IsModified” before calling SaveChanges.
I have found examples in C# on how to check for changed value but not null. (I am still a vb guy anyway. Don't hate.)
A) Is solution #2 a good way to do this?
B) Has anyone a piece of good to help me?
Thank you.
BTW, this is my best stab at it so far: (Errors on “CurrentValues”)
Public Overridable Function MarkEntriesModified(entity As Object)
Dim dbEntityEntry = DbContext.Entry(entity)
'Ensure only non-null values are inserted
For Each [property] In dbEntityEntry.CurrentValues.PropertyNames
If Not IsDBNull(dbEntityEntry.CurrentValues.GetValue(Of Object)([property])) Then
dbEntityEntry.[Property]([property]).IsModified = True
End If
Next
Return entity
Try this architecture. So if you are using EF then I suppose you have edmx updated and correctly representing your database objects.
eg: Say you wan to update Customer data in customer table
Create a Customer class.
when you extract a particular customer, get an instance of the
customer created.
pass this instance to the UI
and pass the instance back to Business layer to save
this way you don't loose anything in between.
some sample code

How to handle duplicates in disconnected object graph?

I'm having a problem updating a disconnected POCO model in an ASP.NET application.
Lets say we have the following model:
Users
Districts
Orders
A user can be responsible for 0 or more districts, an order belongs to a district and a user can be the owner of an order.
When the user logs in the user and the related districts are loaded. Later the user loads an order, and sets himself as the owner of the order. The user(and related districts) and order(and related district) are loaded in two different calls with two different dbcontexts. When I save the order after the user has assigned himself to it. I get an exception that saying that acceptchanges cannot continue because the object's key values conflict with another object.
Which is not strange, since the same district can appear both in the list of districts the user is responsible and on the order.
I've searched high and low for a solution to this problem, but the answers I have found seems to be either:
Don't load the related entities of one of the objects in my case that would be the districts of the user.
Don't assign the user to the order by using the objects, just set the foreign key id on the order object.
Use nHibernate since it apparently handles it.
I tried 1 and that works, but I feel this is wrong because I then either have to load the user without it's districts before relating it to the order, or do a shallow clone. This is fine for this simple case here, but the problem is that in my case district might appear several more times in the graph. Also it seems pointless since I have the objects so why not let me connected them and update the graph. The reason I need the entire graph for the order, is that I need to display all the information to the user. So since I got all the objects why should I need to either reload or shallow clone it to get this to work?
I tried using STE but I ran in to the same problem, since I cannot attach an object to a graph loaded by another context. So I am back at square 1.
I would assume that this is a common problem in anything but tutorial code. Yet, I cannot seem to find any good solution to this. Which makes me think that either I do not under any circumstance understand using POCOs/EF or I suck at using google to find an answer to this problem.
I've bought both of the "Programming Entity Framework" books from O'Reilly by Julia Lerman but cannot seem to find anything to solve my problem in those books either.
Is there anyone out there who can shed some light on how to handle graphs where some objects might be repeated and not necessarily loaded from the same context.
The reason why EF does not allow to have two entities with the same key being attached to a context is that EF cannot know which one is "valid". For example: You could have two District objects in your object graph, both with a key Id = 1, but the two have different Name property values. Which one represents the data that have to be saved to the database?
Now, you could say that it doesn't matter if both objects haven't changed, you just want to attach them to a context in state Unchanged, maybe to establish a relationship to another entity. It is true in this special case that duplicates might not be a problem. But I think, it is simply too complex to deal with all situations and different states the objects could have to decide if duplicate objects are causing ambiguities or not.
Anyway, EF implements a strict identity mapping between object reference identity and key property values and just doesn't allow to have more than one entity with a given key attached to a context.
I don't think there is a general solution for this kind of problem. I can only add a few more ideas in addition to the solutions in your question:
Attach the User to the context you are loading the order in:
context.Users.Attach(user); // attaches user AND user.Districts
var order = context.Orders.Include("Districts")
.Single(o => o.Id == someOrderId);
// because the user's Districts are attached, no District with the same key
// will be loaded again, EF will use the already attached Districts to
// populate the order.Districts collection, thus avoiding duplicate Districts
order.Owner = user;
context.SaveChanges();
// it should work without exception
Attach only the entities to the context you need in order to perform a special update:
using (var context = new MyContext())
{
var order = new Order { Id = order.Id };
context.Orders.Attach(order);
var user = new User { Id = user.Id };
context.Users.Attach(user);
order.Owner = user;
context.SaveChanges();
}
This would be enough to update the Owner relationship. You would not need the whole object graph for this procedure, you only need the correct primary key values of the entities the relationship has to be created for. It doesn't work that easy of course if you have more changes to save or don't know what exactly could have been changed.
Don't attach the object graph to the context at all. Instead load new entities from the database that represent the object graph currently stored in the database. Then update the loaded graph with your detached object graph and save the changes applied to the loaded (=attached) graph. An example of this procedure is shown here. It is safe and a very general pattern (but not generic) but it can be very complex for complex object graphs.
Traverse the object graph and replace the duplicate objects by a unique one, for example just the first one with type and key you have found. You could build a dictionary of unique objects that you lookup to replace the duplicates. An example is here.

Many to many relationship with junction table in Entity Framework?

I'm trying to create a many-to-many relationship in Entity Framework (code first), according to the following post: Database design for limited number of choices in MVC and Entity Framework?
However, I can't get it to work properly, and I'm sure I'm doing something very simple the wrong way. Here's the diagram I have no from my attempts:
The point of the junction table is that I need to have an extra property, Level, in the relationship, so I can't just go with a direct relationship between Consultant and Program. I added the ConsultantProgramLink entity manually in the designer, and then added associations to Program and Consultant respectively, selecting to add a FK for each, and then made them both primary keys. But when I do it like this it doesn't work as I expected:
If I had done a direct association between Consultant and Program, I would have been able to refer to, say, Consultant.Programs in my code. But that doesn't work now with the junction table. Is there any way to remedy this, or do I always have to go through the junction property (Consultant.ConsultantProgramLink.Programs)? In any case, even if I do try to go through the junction property it doesn't help. I can do Consultant.ConsultantProgramLink in my code, but another dot doesn't give me the navigation property Programs (which for some reason also became simply Program, why? Can I just rename them if I eventually get access to them at all?).
So what am I doing wrong? Why can't I access the properties through dot notation in my code?
Once you model a junction table as an entity you indeed lose direct many-to-many relation between Consultant and Program. That is how it works. You will either have direct many-to-many relation or additional properties in the junction table. Not both. If you want both you can try creating custom Programs property on Consultant and use linq query to get related programs:
public IEnumerable<Program> Programs
{
get
{
return this.ConsultantProgramLinks.Select(l => l.Program);
}
}
The example is also the explanation of your last problem. You can't have Program property on ConsultantProgramLink because it is a collection of related entities, not single entity (it should be called ConsultantProgramLinks). The property in ConsultantProgramLink entity is called simply Programbecause it represents single entity not collection.
Edit:
If you need each Program to be automatically associated with each Consultant you must enforce it when you are going to create new Program. Having junction table exposed as separate entity will probably allow you achieving it easily:
var program = new Program();
...
context.Programs.AddObject(program);
var ids = from c in context.Consultants
select c.Id;
foreach (var id in ids)
{
var link = new ConsultantProgramLink
{
ConsultantId = id,
Program = program
};
context.ConsultantProgramLinks.AddObject(link);
}
context.SaveChanges();
If you add new Consultant you will have to create links to all programs in the same way.
The disadvantage is that if you have for example 1000 consultants this construct will create 1001 database inserts where each insert will be executed in separate roundtrip to the database. To avoid it the only option is either use stored procedur or trigger on Program table.

Linq to SQL Design question

Often I need to combine data from multiple tables and display the result in a GridView control.
I can write a Linq query inline in the Page_load event, return an anonymous type that combines all the fields that I need, and databind the result to the GridView control.
Problem: I use 'helper methods' as described by Scott Guthrie on his blog. Such a helper method cannot return an anonymous type. The query would have to be inline for this approach.
I can write a database view that returns the data that I need, and write a helper method with a query against this (new and known) type that it returns.
Problem: I will need a lot of views in my database schema, and I will introduce a lot of redundant aspects of my data. I also lose some of the advantage of using Linq - removing all business logic from the database.
I would like to take an approach that lets me keep the Linq queries in helper methods, yet allows me to access all the attributes that I need on the grid in their respective databinding expressions. Can this be done?
I asked the wrong question, as I frequently do. What prompted me to look into anonymous types was an apparent limitation of the GridView - my inability to use a databinding expression in an <asp:BoundField> (the DataField parameter only accepts column names of the table that the Linq query pulls in).
Turns out that in a TemplateField it is possible to use Eval and access members of the Linq data item, and Linq takes care of the query for me.
In other words, I can keep the query in my helper method, have it return a primary database table type (e.g. Account), and I bind the Accounts to the GridView.
In the databinding expressions I can access data members of the Account objects that reside in other tables, without having to explicitly pull them in in the query. Perfect.
I don't know if there is a viable way to achieve this using anonymous types. But I have a suggestion that will work in WinForms, but I am not sure about ASP.NET.
What you need is a type with properties where neither the number of properties, nor the types and names of the properties are known at compile time. One way to create such a thing is ICustomTypeDescriptor.
You have to create a type implementing this interface with an private backing store of objects backing the properties returned by the query for one row from the query. Then you implement GetProperties() to return one PropertyDescriptor per column and PropertyDescriptor.GetValue() and PropertyDescriptor.SetValue() to access the backing array.
By implementing PropertyDescriptor.Name you will get the correct column name; this will probably require another backing store storing the property names. And there is a lot more to implement, but in the end your new type will behave almost like a normal type - and now the if - if the control you are binding to knows about and uses ICustomTypeDescriptor.
UPDATE
I just found an bit of text stating that ASP.NET data binding knows and uses ICustomTypeDescriptor.
Scott's earlier post in the series talks about shaping the result set before inserting into a grid:
Part 3 - Querying our Database
Scroll down to "Shaping our Query Results".

Resources